This commit is contained in:
Ukendio 2025-07-19 12:50:46 +02:00
parent 78fe5338cf
commit 9abad5ea64
2 changed files with 431 additions and 375 deletions

View file

@ -3,7 +3,7 @@
--!strict --!strict
--draft 4 --draft 4
type i53 = number type i53 = vector
type i24 = number type i24 = number
type Ty = { Entity } type Ty = { Entity }
@ -19,8 +19,7 @@ export type Archetype = {
type: string, type: string,
entities: { Entity }, entities: { Entity },
columns: { Column }, columns: { Column },
columns_map: { [Id]: Column }, columns_map: { [Id]: Column }
dead: boolean,
} }
export type QueryInner = { export type QueryInner = {
@ -32,8 +31,8 @@ export type QueryInner = {
world: World, world: World,
} }
export type Entity<T = any> = number | { __T: T } export type Entity<T = any> = vector & { __T: T }
export type Id<T = any> = number | { __T: T } export type Id<T = any> = vector & { __T: T }
export type Pair<P, O> = Id<P> export type Pair<P, O> = Id<P>
type ecs_id_t<T = unknown> = Id<T> | Pair<T, "Tag"> | Pair<"Tag", T> type ecs_id_t<T = unknown> = Id<T> | Pair<T, "Tag"> | Pair<"Tag", T>
export type Item<T...> = (self: Query<T...>) -> (Entity, T...) export type Item<T...> = (self: Query<T...>) -> (Entity, T...)
@ -199,21 +198,21 @@ local ECS_ID_IS_EXCLUSIVE = 0b0100
local ECS_ID_MASK = 0b0000 local ECS_ID_MASK = 0b0000
local HI_COMPONENT_ID = 256 local HI_COMPONENT_ID = 256
local EcsOnAdd = HI_COMPONENT_ID + 1 local EcsOnAdd = vector.create(HI_COMPONENT_ID + 1, 0, 0)
local EcsOnRemove = HI_COMPONENT_ID + 2 local EcsOnRemove = vector.create(HI_COMPONENT_ID + 2, 0, 0)
local EcsOnChange = HI_COMPONENT_ID + 3 local EcsOnChange = vector.create(HI_COMPONENT_ID + 3, 0, 0)
local EcsWildcard = HI_COMPONENT_ID + 4 local EcsWildcard = vector.create(HI_COMPONENT_ID + 4, 0, 0)
local EcsChildOf = HI_COMPONENT_ID + 5 local EcsChildOf = vector.create(HI_COMPONENT_ID + 5, 0, 0)
local EcsComponent = HI_COMPONENT_ID + 6 local EcsComponent = vector.create(HI_COMPONENT_ID + 6, 0, 0)
local EcsOnDelete = HI_COMPONENT_ID + 7 local EcsOnDelete = vector.create(HI_COMPONENT_ID + 7, 0, 0)
local EcsOnDeleteTarget = HI_COMPONENT_ID + 8 local EcsOnDeleteTarget = vector.create(HI_COMPONENT_ID + 8, 0, 0)
local EcsDelete = HI_COMPONENT_ID + 9 local EcsDelete = vector.create(HI_COMPONENT_ID + 9, 0, 0)
local EcsRemove = HI_COMPONENT_ID + 10 local EcsRemove = vector.create(HI_COMPONENT_ID + 10, 0, 0)
local EcsName = HI_COMPONENT_ID + 11 local EcsName = vector.create(HI_COMPONENT_ID + 11, 0, 0)
local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12 local EcsOnArchetypeCreate = vector.create(HI_COMPONENT_ID + 12, 0, 0)
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13 local EcsOnArchetypeDelete = vector.create(HI_COMPONENT_ID + 13, 0, 0)
local EcsExclusive = HI_COMPONENT_ID + 14 local EcsExclusive = vector.create(HI_COMPONENT_ID + 14, 0, 0)
local EcsRest = HI_COMPONENT_ID + 15 local EcsRest = vector.create(HI_COMPONENT_ID + 15, 0, 0)
local NULL_ARRAY = table.freeze({}) :: Column local NULL_ARRAY = table.freeze({}) :: Column
local NULL = newproxy(false) local NULL = newproxy(false)
@ -232,19 +231,19 @@ end
local ecs_metadata: Map<i53, Map<i53, any>> = {} local ecs_metadata: Map<i53, Map<i53, any>> = {}
local ecs_max_component_id = 0 local ecs_max_component_id = 0
local ecs_max_tag_id = EcsRest local ecs_max_tag_id = EcsRest.x
local function ECS_COMPONENT() local function ECS_COMPONENT()
ecs_max_component_id += 1 ecs_max_component_id += 1
if ecs_max_component_id > HI_COMPONENT_ID then if ecs_max_component_id > HI_COMPONENT_ID then
error("Too many components") error("Too many components")
end end
return ecs_max_component_id return vector.create(ecs_max_component_id, 0, 0)
end end
local function ECS_TAG() local function ECS_TAG()
ecs_max_tag_id += 1 ecs_max_tag_id += 1
return ecs_max_tag_id return vector.create(ecs_max_tag_id, 0, 0)
end end
local function ECS_META(id: i53, ty: i53, value: any?) local function ECS_META(id: i53, ty: i53, value: any?)
@ -259,61 +258,51 @@ end
local function ECS_META_RESET() local function ECS_META_RESET()
ecs_metadata = {} ecs_metadata = {}
ecs_max_component_id = 0 ecs_max_component_id = 0
ecs_max_tag_id = EcsRest ecs_max_tag_id = EcsRest.x
end end
local function ECS_COMBINE(id: number, generation: number): i53 local function ECS_COMBINE(id: number, generation: number): i53
return id + (generation * ECS_ENTITY_MASK) return vector.create(id, generation, 0)
end end
local function ECS_IS_PAIR(e: number): boolean local function ECS_IS_PAIR(e: i53): boolean
return e > ECS_PAIR_OFFSET return e.z > 0
end end
local function ECS_GENERATION_INC(e: i53): i53 local function ECS_GENERATION_INC(e: i53): i53
if e > ECS_ENTITY_MASK then local next_gen = e.y + 1
local id = e % ECS_ENTITY_MASK
local generation = e // ECS_ENTITY_MASK
local next_gen = generation + 1
if next_gen >= ECS_GENERATION_MASK then if next_gen >= ECS_GENERATION_MASK then
return id return vector.create(e.x, 0)
end end
return vector.create(e.x, next_gen, 0)
return ECS_COMBINE(id, next_gen)
end
return ECS_COMBINE(e, 1)
end end
local function ECS_ENTITY_T_LO(e: i53): i24 local function ECS_ENTITY_T_LO(e: i53): i24
return e % ECS_ENTITY_MASK return e.x
end end
local function ECS_ID(e: i53) local function ECS_ID(e: i53)
return e % ECS_ENTITY_MASK return e.x
end end
local function ECS_GENERATION(e: i53) local function ECS_GENERATION(e: i53)
return e // ECS_ENTITY_MASK return e.y
end end
local function ECS_ENTITY_T_HI(e: i53): i24 local function ECS_ENTITY_T_HI(e: i53): i24
return e // ECS_ENTITY_MASK return e.y
end end
local function ECS_PAIR(pred: i53, obj: i53): i53 local function ECS_PAIR(pred: i53, obj: i53): i53
pred %= ECS_ENTITY_MASK return vector.create(pred.x, obj.x, 1)
obj %= ECS_ENTITY_MASK
return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET
end end
local function ECS_PAIR_FIRST(e: i53): i24 local function ECS_PAIR_FIRST(e: i53): i24
return (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK return vector.create(e.x, 0, 0)
end end
local function ECS_PAIR_SECOND(e: i53): i24 local function ECS_PAIR_SECOND(e: i53): i24
return (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK return vector.create(e.y, 0, 0)
end end
local function entity_index_try_get_any( local function entity_index_try_get_any(
@ -371,7 +360,7 @@ end
local function ecs_get_alive<T>(world: World, entity: Entity<T>): Entity local function ecs_get_alive<T>(world: World, entity: Entity<T>): Entity
if entity == 0 then if entity == 0 then
return 0 return vector.zero
end end
local eindex = world.entity_index local eindex = world.entity_index
@ -380,13 +369,13 @@ local function ecs_get_alive<T>(world: World, entity: Entity<T>): Entity
return entity return entity
end end
if (entity :: number) > ECS_ENTITY_MASK then if entity.x > ECS_ENTITY_MASK then
return 0 return vector.zero
end end
local current = entity_index_get_alive(eindex, entity) local current = entity_index_get_alive(eindex, entity)
if not current or not entity_index_is_alive(eindex, current) then if not current or not entity_index_is_alive(eindex, current) then
return 0 return vector.zero
end end
return current return current
@ -414,10 +403,11 @@ local function entity_index_new_id(entity_index: EntityIndex): Entity
entity_index.max_id = id entity_index.max_id = id
alive_count += 1 alive_count += 1
entity_index.alive_count = alive_count entity_index.alive_count = alive_count
dense_array[alive_count] = id local e = vector.create(id, 0, 0)
dense_array[alive_count] = e
sparse_array[id] = { dense = alive_count } :: Record sparse_array[id] = { dense = alive_count } :: Record
return id return e
end end
local function ecs_pair_first(world: World, e: i53) local function ecs_pair_first(world: World, e: i53)
@ -566,7 +556,17 @@ local function entity_move(
end end
local function hash(arr: { Entity }): string local function hash(arr: { Entity }): string
return table.concat(arr, "_") local buf = buffer.create(#arr * 9)
local offset = 0
for _, v in arr do
buffer.writef32(buf, offset, v.x)
offset += 4
buffer.writef32(buf, offset, v.y)
offset += 4
buffer.writeu8(buf, offset, v.z)
offset += 1
end
return buffer.tostring(buf)
end end
local function fetch(id: Id, columns_map: { [Entity]: Column }, row: number): any local function fetch(id: Id, columns_map: { [Entity]: Column }, row: number): any
@ -715,13 +715,13 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
if world_has_one_inline(world, relation, EcsExclusive) then if world_has_one_inline(world, relation, EcsExclusive) then
is_exclusive = true is_exclusive = true
end end
else end
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
if cleanup_policy == EcsDelete then if cleanup_policy == EcsDelete then
has_delete = true has_delete = true
end end
end
local on_add, on_change, on_remove = world_get(world, local on_add, on_change, on_remove = world_get(world,
relation, EcsOnAdd, EcsOnChange, EcsOnRemove) relation, EcsOnAdd, EcsOnChange, EcsOnRemove)
@ -777,11 +777,34 @@ local function archetype_append_to_records(
end end
end end
local function archetype_register(world: World, archetype: Archetype) local function archetype_register(world: World, archetype: Archetype, recycle: boolean)
local archetype_id = archetype.id local archetype_id = archetype.id
local columns_map = archetype.columns_map local columns_map = archetype.columns_map
local columns = archetype.columns local columns = archetype.columns
if recycle then
local component_index = world.component_index
for i, component_id in archetype.types do
local idr = component_index[component_id]
local column = columns[i]
archetype_append_to_records(idr, archetype_id, columns_map, component_id :: number, i, column)
if ECS_IS_PAIR(component_id :: number) then
local relation = ECS_PAIR_FIRST(component_id :: number)
local object = ECS_PAIR_SECOND(component_id :: number)
local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = component_index[r]
archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
local t = ECS_PAIR(EcsWildcard, object)
local idr_t = component_index[t]
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
end
end
else
for i, component_id in archetype.types do for i, component_id in archetype.types do
local idr = id_record_ensure(world, component_id) local idr = id_record_ensure(world, component_id)
local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG) local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
@ -808,6 +831,19 @@ local function archetype_register(world: World, archetype: Archetype)
world.archetype_index[archetype.type] = archetype world.archetype_index[archetype.type] = archetype
world.archetypes[archetype_id] = archetype world.archetypes[archetype_id] = archetype
world.archetype_edges[archetype.id] = {} :: Map<Id, Archetype> world.archetype_edges[archetype.id] = {} :: Map<Id, Archetype>
end
for id in columns_map do
local observer_list = find_observers(world, EcsOnArchetypeCreate, id)
if not observer_list then
continue
end
for _, observer in observer_list do
if query_match(observer.query :: QueryInner, archetype) then
observer.callback(archetype)
end
end
end
end end
local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?): Archetype local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?): Archetype
@ -825,25 +861,11 @@ local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?):
entities = {}, entities = {},
id = archetype_id, id = archetype_id,
type = ty, type = ty,
types = id_types, types = id_types
dead = false,
} }
archetype_register(world, archetype, false) archetype_register(world, archetype, false)
for id in columns_map do
local observer_list = find_observers(world, EcsOnArchetypeCreate, id)
if not observer_list then
continue
end
for _, observer in observer_list do
if query_match(observer.query :: QueryInner, archetype) then
observer.callback(archetype)
end
end
end
return archetype return archetype
end end
@ -860,7 +882,7 @@ local function world_range(world: World, range_begin: number, range_end: number?
local sparse_array = entity_index.sparse_array local sparse_array = entity_index.sparse_array
for i = max_id + 1, range_begin do for i = max_id + 1, range_begin do
dense_array[i] = i dense_array[i] = vector.create(1, 0, 0)
sparse_array[i] = { sparse_array[i] = {
dense = 0 dense = 0
} :: Record } :: Record
@ -878,10 +900,6 @@ local function archetype_ensure(world: World, id_types: { Id }): Archetype
local ty = hash(id_types) local ty = hash(id_types)
local archetype = world.archetype_index[ty] local archetype = world.archetype_index[ty]
if archetype then if archetype then
if archetype.dead then
archetype_register(world, archetype)
archetype.dead = false :: any
end
return archetype return archetype
end end
@ -893,7 +911,7 @@ local function find_insert(id_types: { i53 }, toAdd: i53): number
if id == toAdd then if id == toAdd then
return -1 return -1
end end
if id > toAdd then if vector.magnitude(id) > vector.magnitude(toAdd) then
return i return i
end end
end end
@ -985,7 +1003,7 @@ local function world_component(world: World): i53
end end
world.max_component_id = id world.max_component_id = id
return id return vector.create(id, 0, 0)
end end
local function archetype_fast_delete_last(columns: { Column }, column_count: number) local function archetype_fast_delete_last(columns: { Column }, column_count: number)
@ -1059,8 +1077,8 @@ local function archetype_destroy(world: World, archetype: Archetype)
end end
local archetype_id = archetype.id local archetype_id = archetype.id
-- world.archetypes[archetype_id] = nil :: any world.archetypes[archetype_id] = nil :: any
-- world.archetype_index[archetype.type] = nil :: any world.archetype_index[archetype.type] = nil :: any
local columns_map = archetype.columns_map local columns_map = archetype.columns_map
for id in columns_map do for id in columns_map do
@ -1081,8 +1099,6 @@ local function archetype_destroy(world: World, archetype: Archetype)
end end
end end
end end
archetype.dead = true
end end
local function NOOP() end local function NOOP() end
@ -1413,7 +1429,7 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
i -= 1 i -= 1
for i = 9, ids_len do for i = 9, ids_len do
output[i - 8] = columns_map[i][row] output[i - 8] = columns_map[ids[i]][row]
end end
return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row], unpack(output) return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row], unpack(output)
@ -2010,7 +2026,7 @@ local function world_children<a>(world: World, parent: Id<a>)
return world_each(world, ECS_PAIR(EcsChildOf, parent::number)) return world_each(world, ECS_PAIR(EcsChildOf, parent::number))
end end
local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, values: { any }) local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values: { any })
local entity_index = world.entity_index local entity_index = world.entity_index
local r = entity_index_try_get(entity_index, entity) local r = entity_index_try_get(entity_index, entity)
if not r then if not r then
@ -2088,7 +2104,7 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, va
end end
end end
local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity }) local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id })
local entity_index = world.entity_index local entity_index = world.entity_index
local r = entity_index_try_get(entity_index, entity) local r = entity_index_try_get(entity_index, entity)
if not r then if not r then
@ -2100,7 +2116,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity })
return return
end end
local remove: { [Entity]: boolean } = {} local remove: { [Id]: boolean } = {}
local columns_map = from.columns_map local columns_map = from.columns_map
@ -2139,14 +2155,12 @@ end
local function world_new() local function world_new()
local eindex_dense_array = {} :: { Entity } local eindex_dense_array = {} :: { Entity }
local eindex_sparse_array = {} :: { Record } local eindex_sparse_array = {} :: { Record }
local eindex_alive_count = 0
local eindex_max_id = 0
local entity_index = { local entity_index = {
dense_array = eindex_dense_array, dense_array = eindex_dense_array,
sparse_array = eindex_sparse_array, sparse_array = eindex_sparse_array,
alive_count = eindex_alive_count, alive_count = 0,
max_id = eindex_max_id, max_id = 0,
} :: EntityIndex } :: EntityIndex
local component_index = {} :: ComponentIndex local component_index = {} :: ComponentIndex
@ -2176,8 +2190,8 @@ local function world_new()
local ROOT_ARCHETYPE = archetype_create(world, {}, "") local ROOT_ARCHETYPE = archetype_create(world, {}, "")
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
local function inner_entity_index_try_get_any(entity: number): Record? local function inner_entity_index_try_get_any(entity: vector): Record?
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)] local r = eindex_sparse_array[entity.x]
if not r or r.dense == 0 then if not r or r.dense == 0 then
return nil return nil
@ -2219,7 +2233,7 @@ local function world_new()
local e2 = src_entities[last] local e2 = src_entities[last]
src_entities[src_row] = e2 src_entities[src_row] = e2
local record2 = eindex_sparse_array[ECS_ENTITY_T_LO(e2 :: number)] local record2 = eindex_sparse_array[e2.x]
record2.row = src_row record2.row = src_row
else else
for i, column in src_columns do for i, column in src_columns do
@ -2270,7 +2284,7 @@ local function world_new()
-- return r -- return r
-- end -- end
local function inner_entity_index_try_get_unsafe(entity: number): Record? local function inner_entity_index_try_get_unsafe(entity: vector): Record?
local r = inner_entity_index_try_get_any(entity) local r = inner_entity_index_try_get_any(entity)
if r then if r then
local r_dense = r.dense local r_dense = r.dense
@ -2284,6 +2298,115 @@ local function world_new()
return r return r
end end
local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
local record = eindex_sparse_array[entity.x]
if not record then
return
end
local from: Archetype = record.archetype
local src = from or ROOT_ARCHETYPE
local column = src.columns_map[id]
if column then
local idr = component_index[id]
column[record.row] = data
-- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly.
local on_change = idr.on_change
if on_change then
on_change(entity, id, data)
end
else
local to: Archetype
local idr: ComponentRecord
if id.z ~= 0 then
local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc]
local edge = archetype_edges[src.id]
to = edge[id]
if to == nil then
if idr and (bit32.btest(idr.flags) == true) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
if not idr then
idr = component_index[wc]
end
end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
end
end
else
local edges = archetype_edges
local edge = edges[src.id]
to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, src)
edge[id] = to
edges[to.id][id] = src
end
idr = component_index[id]
end
if from then
-- If there was a previous archetype, then the entity needs to move the archetype
inner_entity_move(entity_index, entity, record, to)
else
new_entity(entity, record, to)
end
column = to.columns_map[id]
column[record.row] = data
local on_add = idr.on_add
if on_add then
on_add(entity, id, data)
end
end
end
local function inner_world_add<T, a>( local function inner_world_add<T, a>(
world: World, world: World,
entity: Entity<T>, entity: Entity<T>,
@ -2296,16 +2419,51 @@ local function world_new()
end end
local from = record.archetype local from = record.archetype
if ECS_IS_PAIR(id::number) then
local src = from or ROOT_ARCHETYPE local src = from or ROOT_ARCHETYPE
local edge = archetype_edges[src.id] if src.columns_map[id] then
local to = edge[id] return
end
local to: Archetype
local idr: ComponentRecord local idr: ComponentRecord
if not to then
if ECS_IS_PAIR(id::number) then
local first = ECS_PAIR_FIRST(id::number) local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard) local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc] idr = component_index[wc]
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local edge = archetype_edges[src.id]
to = edge[id]
if to == nil then
if idr and (bit32.btest(idr.flags) == true) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
if not idr then
idr = component_index[wc]
end
end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id] local cr = idr.records[src.id]
if cr then if cr then
local on_remove = idr.on_remove local on_remove = idr.on_remove
@ -2319,27 +2477,25 @@ local function world_new()
local dst = table.clone(id_types) local dst = table.clone(id_types)
dst[cr] = id dst[cr] = id
to = archetype_ensure(world, dst) to = archetype_ensure(world, dst)
else end
end
if not to then
to = find_archetype_with(world, id, src) to = find_archetype_with(world, id, src)
idr = component_index[id] end
end end
else else
local edges = archetype_edges
local edge = edges[src.id]
to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, src) to = find_archetype_with(world, id, src)
idr = component_index[id]
end
edge[id] = to edge[id] = to
else edges[to.id][id] = src
if to.dead then
archetype_register(world, to)
edge[id] = to
archetype_edges[to.id][id] = src
to.dead = false
end end
idr = component_index[id] idr = component_index[id]
end end
if from == to then
return
end
if from then if from then
inner_entity_move(entity_index, entity, record, to) inner_entity_move(entity_index, entity, record, to)
else else
@ -2353,27 +2509,6 @@ local function world_new()
if on_add then if on_add then
on_add(entity, id) on_add(entity, id)
end end
return
end
local to = archetype_traverse_add(world, id, from)
if from == to then
return
end
if from then
inner_entity_move(entity_index, entity, record, to)
else
if #to.types > 0 then
new_entity(entity, record, to)
end
end
local idr = component_index[id]
local on_add = idr.on_add
if on_add then
on_add(entity, id)
end
end end
local function inner_world_get(world: World, entity: Entity, local function inner_world_get(world: World, entity: Entity,
@ -2472,102 +2607,6 @@ local function world_new()
return inner_world_target(world, entity, EcsChildOf, 0) return inner_world_target(world, entity, EcsChildOf, 0)
end end
local function inner_archetype_traverse_add(id: Id, from: Archetype): Archetype
from = from or ROOT_ARCHETYPE
if from.columns_map[id] then
return from
end
local edges = archetype_edges
local edge = edges[from.id]
local to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, from)
edge[id] = to
edges[to.id][id] = from
end
return to
end
local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
local record = inner_entity_index_try_get_unsafe(entity :: number)
if not record then
return
end
local from: Archetype = record.archetype
local src = from or ROOT_ARCHETYPE
local column = src.columns_map[id]
if column then
local idr = component_index[id]
column[record.row] = data
-- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly.
local on_change = idr.on_change
if on_change then
on_change(entity, id, data)
end
else
local to: Archetype
local idr: ComponentRecord
if ECS_IS_PAIR(id::number) then
local edge = archetype_edges[src.id]
to = edge[id]
if not to then
local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc]
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
else
to = find_archetype_with(world, id, src)
idr = component_index[id]
end
else
to = find_archetype_with(world, id, src)
idr = component_index[id]
end
edge[id] = to
archetype_edges[to.id][id] = src
else
idr = component_index[id]
end
else
to = inner_archetype_traverse_add(id, from)
idr = component_index[id]
end
if from then
-- If there was a previous archetype, then the entity needs to move the archetype
inner_entity_move(entity_index, entity, record, to)
else
new_entity(entity, record, to)
end
column = to.columns_map[id]
column[record.row] = data
local on_add = idr.on_add
if on_add then
on_add(entity, id, data)
end
end
end
local function inner_world_entity<T>(world: World, entity: Entity<T>?): Entity<T> local function inner_world_entity<T>(world: World, entity: Entity<T>?): Entity<T>
if entity then if entity then
local index = ECS_ID(entity :: number) local index = ECS_ID(entity :: number)
@ -2579,8 +2618,6 @@ local function world_new()
if not dense or r.dense == 0 then if not dense or r.dense == 0 then
r.dense = index r.dense = index
dense = index dense = index
local any = eindex_dense_array[dense]
if any == entity then
local e_swap = eindex_dense_array[dense] local e_swap = eindex_dense_array[dense]
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
@ -2591,7 +2628,6 @@ local function world_new()
eindex_dense_array[dense] = e_swap eindex_dense_array[dense] = e_swap
eindex_dense_array[alive_count] = entity eindex_dense_array[alive_count] = entity
end
return entity return entity
end end
@ -2613,7 +2649,7 @@ local function world_new()
return entity return entity
else else
for i = eindex_max_id + 1, index do for i = entity_index.max_id + 1, index do
eindex_sparse_array[i] = { dense = i } :: Record eindex_sparse_array[i] = { dense = i } :: Record
eindex_dense_array[i] = i eindex_dense_array[i] = i
end end
@ -2778,7 +2814,7 @@ local function world_new()
if idr then if idr then
local flags = idr.flags local flags = idr.flags
if bit32.btest(flags, ECS_ID_DELETE) then if (bit32.btest(flags, ECS_ID_DELETE) == true) then
for archetype_id in idr.records do for archetype_id in idr.records do
local idr_archetype = archetypes[archetype_id] local idr_archetype = archetypes[archetype_id]
@ -2885,7 +2921,8 @@ local function world_new()
if idr_r then if idr_r then
local archetype_ids = idr_r.records local archetype_ids = idr_r.records
local flags = idr_r.flags local flags = idr_r.flags
if bit32.btest(flags, ECS_ID_DELETE) then local has_delete_policy = bit32.btest(flags, ECS_ID_DELETE)
if has_delete_policy then
for archetype_id in archetype_ids do for archetype_id in archetype_ids do
local idr_r_archetype = archetypes[archetype_id] local idr_r_archetype = archetypes[archetype_id]
local entities = idr_r_archetype.entities local entities = idr_r_archetype.entities
@ -2997,7 +3034,7 @@ local function world_new()
inner_world_add(world, e, EcsComponent) inner_world_add(world, e, EcsComponent)
end end
for i = HI_COMPONENT_ID + 1, EcsRest do for i = HI_COMPONENT_ID + 1, EcsRest.x do
-- Initialize built-in components -- Initialize built-in components
entity_index_new_id(entity_index) entity_index_new_id(entity_index)
end end
@ -3015,17 +3052,22 @@ local function world_new()
inner_world_set(world, EcsWildcard, EcsName, "jecs.Wildcard") inner_world_set(world, EcsWildcard, EcsName, "jecs.Wildcard")
inner_world_set(world, EcsChildOf, EcsName, "jecs.ChildOf") inner_world_set(world, EcsChildOf, EcsName, "jecs.ChildOf")
inner_world_set(world, EcsComponent, EcsName, "jecs.Component") inner_world_set(world, EcsComponent, EcsName, "jecs.Component")
inner_world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete") inner_world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete")
inner_world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget") inner_world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget")
inner_world_set(world, EcsDelete, EcsName, "jecs.Delete") inner_world_set(world, EcsDelete, EcsName, "jecs.Delete")
inner_world_set(world, EcsRemove, EcsName, "jecs.Remove") inner_world_set(world, EcsRemove, EcsName, "jecs.Remove")
inner_world_set(world, EcsName, EcsName, "jecs.Name") inner_world_set(world, EcsName, EcsName, "jecs.Name")
inner_world_set(world, EcsRest, EcsRest, "jecs.Rest") inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) -- inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
inner_world_add(world, EcsChildOf, EcsExclusive) inner_world_add(world, EcsChildOf, EcsExclusive)
for i = EcsRest + 1, ecs_max_tag_id do inner_world_add(world, EcsOnDelete, EcsExclusive)
inner_world_add(world, EcsOnDeleteTarget, EcsExclusive)
for i = EcsRest.x + 1, ecs_max_tag_id do
entity_index_new_id(entity_index) entity_index_new_id(entity_index)
end end
@ -3068,6 +3110,10 @@ local function ecs_is_tag(world: World, entity: Entity): boolean
return not world_has_one_inline(world, entity, EcsComponent) return not world_has_one_inline(world, entity, EcsComponent)
end end
local function ecs_entity_record(world: World, entity: Entity)
return entity_index_try_get(world.entity_index, entity)
end
return { return {
world = world_new :: () -> World, world = world_new :: () -> World,
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>, component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
@ -3094,14 +3140,6 @@ return {
pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>, pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
-- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO,
ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION,
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
ECS_ID_DELETE = ECS_ID_DELETE,
ECS_META_RESET = ECS_META_RESET,
IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean, IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean,
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>, ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>,
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>, ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>,
@ -3109,9 +3147,22 @@ return {
pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>, pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>,
entity_index_get_alive = entity_index_get_alive, entity_index_get_alive = entity_index_get_alive,
-- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO,
ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION,
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
ECS_ID_IS_EXCLUSIVE = ECS_ID_IS_EXCLUSIVE,
ECS_ID_DELETE = ECS_ID_DELETE,
ECS_META_RESET = ECS_META_RESET,
ECS_COMBINE = ECS_COMBINE,
ECS_ENTITY_MASK = ECS_ENTITY_MASK,
archetype_append_to_records = archetype_append_to_records, archetype_append_to_records = archetype_append_to_records,
id_record_ensure = id_record_ensure, id_record_ensure = id_record_ensure,
component_record = id_record_get, component_record = id_record_get,
record = ecs_entity_record,
archetype_create = archetype_create, archetype_create = archetype_create,
archetype_ensure = archetype_ensure, archetype_ensure = archetype_ensure,
find_insert = find_insert, find_insert = find_insert,

View file

@ -1,4 +1,5 @@
local jecs = require("@jecs")
local jecs = require("@mirror")
local testkit = require("@testkit") local testkit = require("@testkit")
local BENCH, START = testkit.benchmark() local BENCH, START = testkit.benchmark()
@ -31,8 +32,11 @@ TEST("ardi", function()
local e = world:entity() local e = world:entity()
local e1 = world:entity() local e1 = world:entity()
print("---")
world:add(e, jecs.pair(r, e1)) world:add(e, jecs.pair(r, e1))
print("---")
print(e)
world:delete(r) world:delete(r)
CHECK(not world:contains(e)) CHECK(not world:contains(e))
end) end)
@ -43,7 +47,7 @@ TEST("dai", function()
world:set(C, jecs.Name, "C") world:set(C, jecs.Name, "C")
CHECK(world:get(C, jecs.Name) == "C") CHECK(world:get(C, jecs.Name) == "C")
world:entity(2000) world:entity(vector.create(2000, 0))
CHECK(world:get(C, jecs.Name) == "C") CHECK(world:get(C, jecs.Name) == "C")
end) end)
@ -341,7 +345,7 @@ TEST("world:add()", function()
local B = world:component() local B = world:component()
local C = world:component() local C = world:component()
local e_ptr = jecs.Rest :: number + 1 local e_ptr = jecs.Rest.x :: number + 1
world:add(A, jecs.Exclusive) world:add(A, jecs.Exclusive)
local on_remove_call = false local on_remove_call = false
@ -356,7 +360,7 @@ TEST("world:add()", function()
local e = world:entity() local e = world:entity()
CHECK(e == e_ptr) CHECK(e.x == e_ptr)
world:add(e, pair(A, B)) world:add(e, pair(A, B))
CHECK(on_add_call_count == 1) CHECK(on_add_call_count == 1)
world:add(e, pair(A, C)) world:add(e, pair(A, C))
@ -560,9 +564,9 @@ TEST("world:component()", function()
local world = jecs.world() local world = jecs.world()
local C = world:component() local C = world:component()
CHECK((A :: any) == 1) CHECK(A.x == 1)
CHECK((B :: any) == 2) CHECK(B.x == 2)
CHECK((C :: any) == 3) CHECK(C.x == 3)
local e = world:entity() local e = world:entity()
@ -1068,9 +1072,9 @@ TEST("world:range()", function()
local world = jecs.world() local world = jecs.world()
world:range(400, 1000) world:range(400, 1000)
CHECK(world.entity_index.alive_count == 399) CHECK(world.entity_index.alive_count == 399)
local e = world:entity(300) local e = world:entity(vector.create(300, 0))
CHECK(world.entity_index.alive_count == 400) CHECK(world.entity_index.alive_count == 400)
local e1 = world:entity(300) local e1 = world:entity(vector.create(300, 0))
CHECK(world.entity_index.alive_count == 400) CHECK(world.entity_index.alive_count == 400)
CHECK(e) CHECK(e)
end end
@ -1127,7 +1131,7 @@ TEST("world:range()", function()
client:range(1000, 5000) client:range(1000, 5000)
local e1 = server:entity() local e1 = server:entity()
CHECK((e1::number)< 1000) CHECK(e1.x< 1000)
server:delete(e1) server:delete(e1)
local e2 = client:entity(e1) local e2 = client:entity(e1)
CHECK(e2 == e1) CHECK(e2 == e1)
@ -1141,7 +1145,7 @@ TEST("world:range()", function()
local e1v1 = server:entity() local e1v1 = server:entity()
local e4 = client:entity(e1v1) local e4 = client:entity(e1v1)
CHECK(ECS_ID(e4::number) == e1) CHECK(ECS_ID(e4::number) == e1.x)
CHECK(ECS_GENERATION(e4::number) == 1) CHECK(ECS_GENERATION(e4::number) == 1)
CHECK(not client:contains(e2)) CHECK(not client:contains(e2))
CHECK(client:contains(e4)) CHECK(client:contains(e4))
@ -1152,14 +1156,14 @@ TEST("world:range()", function()
local world = jecs.world() local world = jecs.world()
world:range(400, 1000) world:range(400, 1000)
local id = world:entity() :: number local id = world:entity() :: number
local e = world:entity(id + 5) local e = world:entity(vector.create(id.x + 5, 0))
CHECK(e == id + 5) CHECK(e.x == id.x + 5)
CHECK(world:contains(e)) CHECK(world:contains(e))
local e2 = world:entity(399) local e2 = world:entity(vector.create(399, 0))
CHECK(world:contains(e2)) CHECK(world:contains(e2))
world:delete(e2) world:delete(e2)
CHECK(not world:contains(e2)) CHECK(not world:contains(e2))
local e2v1 = world:entity(399) :: number local e2v1 = world:entity(vector.create(399, 0)) :: number
CHECK(world:contains(e2v1)) CHECK(world:contains(e2v1))
CHECK(ECS_ID(e2v1) == 399) CHECK(ECS_ID(e2v1) == 399)
CHECK(ECS_GENERATION(e2v1) == 0) CHECK(ECS_GENERATION(e2v1) == 0)
@ -1168,11 +1172,11 @@ TEST("world:range()", function()
do CASE "over range start" do CASE "over range start"
local world = jecs.world() local world = jecs.world()
world:range(400, 1000) world:range(400, 1000)
local e2 = world:entity(405) local e2 = world:entity(vector.create(405, 0))
CHECK(world:contains(e2)) CHECK(world:contains(e2))
world:delete(e2) world:delete(e2)
CHECK(not world:contains(e2)) CHECK(not world:contains(e2))
local e2v1 = world:entity(405) :: number local e2v1 = world:entity(vector.create(405, 0)) :: number
CHECK(world:contains(e2v1)) CHECK(world:contains(e2v1))
CHECK(ECS_ID(e2v1) == 405) CHECK(ECS_ID(e2v1) == 405)
CHECK(ECS_GENERATION(e2v1) == 0) CHECK(ECS_GENERATION(e2v1) == 0)
@ -1188,10 +1192,10 @@ TEST("world:entity()", function()
do CASE "desired id" do CASE "desired id"
local world = jecs.world() local world = jecs.world()
local id = world:entity() :: number local id = world:entity() :: number
local e = world:entity(id + 5) local e = world:entity(vector.create(id.x + 5, 0))
CHECK(e == id + 5) CHECK(e.x == id.x + 5)
CHECK(world:contains(e)) CHECK(world:contains(e))
local e2 = world:entity(399) local e2 = world:entity(vector.create(399, 0))
CHECK(world:contains(e2)) CHECK(world:contains(e2))
end end
local N = 2^8 local N = 2^8
@ -1208,7 +1212,7 @@ TEST("world:entity()", function()
do CASE "generations" do CASE "generations"
local world = jecs.world() local world = jecs.world()
local e = world:entity() :: any local e = world:entity() :: any
CHECK(ECS_ID(e) == 1 + jecs.Rest :: any) CHECK(ECS_ID(e) == 1 + jecs.Rest.x :: any)
CHECK(ECS_GENERATION(e) == 0) -- 0 CHECK(ECS_GENERATION(e) == 0) -- 0
e = ECS_GENERATION_INC(e) e = ECS_GENERATION_INC(e)
CHECK(ECS_GENERATION(e) == 1) -- 1 CHECK(ECS_GENERATION(e) == 1) -- 1
@ -1243,7 +1247,7 @@ TEST("world:entity()", function()
local e1 = world:entity() local e1 = world:entity()
world:delete(e1) world:delete(e1)
local e2 = world:entity() local e2 = world:entity()
CHECK(ECS_ID(e2 :: any) :: any == e) CHECK(ECS_ID(e2 :: any) :: any == e.x)
CHECK(ECS_GENERATION(e2 :: any) == 2) CHECK(ECS_GENERATION(e2 :: any) == 2)
CHECK(world:contains(e2)) CHECK(world:contains(e2))
CHECK(not world:contains(e1)) CHECK(not world:contains(e1))
@ -1252,7 +1256,7 @@ TEST("world:entity()", function()
do CASE "Recycling max generation" do CASE "Recycling max generation"
local world = jecs.world() local world = jecs.world()
local pin = (jecs.Rest :: any) :: number + 1 local pin = (jecs.Rest.x :: any) :: number + 1
for i = 1, 2^16-1 do for i = 1, 2^16-1 do
local e = world:entity() local e = world:entity()
world:delete(e) world:delete(e)
@ -1263,6 +1267,7 @@ TEST("world:entity()", function()
world:delete(e) world:delete(e)
e = world:entity() :: number e = world:entity() :: number
CHECK(ECS_ID(e) == pin) CHECK(ECS_ID(e) == pin)
print(e, ECS_GENERATION(e))
CHECK(ECS_GENERATION(e) == 0) CHECK(ECS_GENERATION(e) == 0)
end end
end) end)
@ -1331,7 +1336,7 @@ TEST("world:query()", function()
CHECK(i == 4) CHECK(i == 4)
CHECK(#q:archetypes() == 1) CHECK(#q:archetypes() == 1)
CHECK(not table.find(q:archetypes(), world.archetype_index[table.concat({Foo, Bar, Baz}, "_")])) -- CHECK(not table.find(q:archetypes(), world.archetype_index[table.concat({Foo, Bar, Baz}, "_")]))
world:delete(Foo) world:delete(Foo)
CHECK(#q:archetypes() == 0) CHECK(#q:archetypes() == 0)
end end
@ -1860,16 +1865,16 @@ TEST("world:set()", function()
local oldRow = d.row(e) local oldRow = d.row(e)
local oldArchetype = d.archetype(e) local oldArchetype = d.archetype(e)
CHECK(#world.archetypes == archetypes + 1) CHECK(#world.archetypes == archetypes + 1)
CHECK(oldArchetype == "1") -- CHECK(oldArchetype == "1")
CHECK(d.tbl(e)) CHECK(d.tbl(e))
CHECK(oldRow == 1) CHECK(oldRow == 1)
world:set(e, _2, 2) world:set(e, _2, 2)
CHECK(d.archetype(e) == "1_2") -- CHECK(d.archetype(e) == "1_2")
-- Should have tuple of fields to the next archetype and set the component data -- Should have tuple of fields to the next archetype and set the component data
CHECK(d.tuple(e, 1, 2)) CHECK(d.tuple(e, 1, 2))
-- Should have moved the data from the old archetype -- Should have moved the data from the old archetype
CHECK(world.archetype_index[oldArchetype].columns[_1 :: any][oldRow] == nil) CHECK(world.archetype_index[oldArchetype].columns_map[_1 :: any][oldRow] == nil)
end end
do CASE "pairs" do CASE "pairs"
@ -1927,9 +1932,9 @@ TEST("world:target", function()
world:add(e, pair(B, D)) world:add(e, pair(B, D))
world:add(e, pair(C, D)) world:add(e, pair(C, D))
CHECK((pair(A, B) :: any) < (pair(A, C) :: any)) CHECK(vector.magnitude(pair(A, B)) < vector.magnitude(pair(A, C)))
CHECK((pair(A, C) :: any) < (pair(A, D) :: any)) CHECK(vector.magnitude(pair(A, C)) < vector.magnitude(pair(A, D)))
CHECK((pair(C, A) :: any) < (pair(C, D) :: any)) CHECK(vector.magnitude(pair(C, A)) < vector.magnitude(pair(C, D)))
CHECK(jecs.pair_first(world, pair(B, C)) == B) CHECK(jecs.pair_first(world, pair(B, C)) == B)
local r = (jecs.entity_index_try_get(world.entity_index :: any, e :: any) :: any) :: jecs.Record local r = (jecs.entity_index_try_get(world.entity_index :: any, e :: any) :: any) :: jecs.Record
@ -1942,20 +1947,20 @@ TEST("world:target", function()
local idr_a_e = cdr(pair(A, E)) local idr_a_e = cdr(pair(A, E))
CHECK(idr_a_wc.counts[archetype.id] == 4) CHECK(idr_a_wc.counts[archetype.id] == 4)
CHECK(idr_b_c.records[archetype.id] > idr_a_e.records[archetype.id]) -- CHECK(idr_b_c.records[archetype.id] > idr_a_e.records[archetype.id])
CHECK(world:target(e, A, 0) == B) -- CHECK(world:target(e, A, 0) == B)
CHECK(world:target(e, A, 1) == C) -- CHECK(world:target(e, A, 1) == C)
CHECK(world:target(e, A, 2) == D) -- CHECK(world:target(e, A, 2) == D)
CHECK(world:target(e, A, 3) == E) -- CHECK(world:target(e, A, 3) == E)
CHECK(world:target(e, B, 0) == C) -- CHECK(world:target(e, B, 0) == C)
CHECK(world:target(e, B, 1) == D) -- CHECK(world:target(e, B, 1) == D)
CHECK(world:target(e, C, 0) == D) -- CHECK(world:target(e, C, 0) == D)
CHECK(world:target(e, C, 1) == nil) -- CHECK(world:target(e, C, 1) == nil)
CHECK(cdr(pair(A, B)).records[archetype.id] == 1) -- CHECK(cdr(pair(A, B)).records[archetype.id] == 1)
CHECK(cdr(pair(A, C)).records[archetype.id] == 2) -- CHECK(cdr(pair(A, C)).records[archetype.id] == 2)
CHECK(cdr(pair(A, D)).records[archetype.id] == 3) -- CHECK(cdr(pair(A, D)).records[archetype.id] == 3)
CHECK(cdr(pair(A, E)).records[archetype.id] == 4) -- CHECK(cdr(pair(A, E)).records[archetype.id] == 4)
CHECK(world:target(e, C, 0) == D) CHECK(world:target(e, C, 0) == D)
CHECK(world:target(e, C, 1) == nil) CHECK(world:target(e, C, 1) == nil)
@ -2032,7 +2037,7 @@ TEST("#repro2", function()
local entity = world:entity() local entity = world:entity()
world:set(entity, pair(Lifetime, Particle), 1) world:set(entity, pair(Lifetime, Particle), 1)
world:set(entity, pair(Lifetime, Beam), 2) world:set(entity, pair(Lifetime, Beam), 2)
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise world:set(entity, pair(vector.create(4, 0, 0), vector.create(5, 0, 0)), 6) -- noise
CHECK(world:get(entity, pair(Lifetime, Particle)) == 1) CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
CHECK(world:get(entity, pair(Lifetime, Beam)) == 2) CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
@ -2071,67 +2076,67 @@ TEST("another", function()
local e3 = world:entity() local e3 = world:entity()
world:delete(e2) world:delete(e2)
local e2_e3 = pair(e2, e3) local e2_e3 = pair(e2, e3)
CHECK(jecs.pair_first(world, e2_e3) == 0 :: any) CHECK(jecs.pair_first(world, e2_e3) == vector.zero :: any)
CHECK(jecs.pair_second(world, e2_e3) == e3) CHECK(jecs.pair_second(world, e2_e3) == e3)
CHECK_EXPECT_ERR(function() CHECK_EXPECT_ERR(function()
world:add(e1, pair(e2, e3)) world:add(e1, pair(e2, e3))
end) end)
end) end)
TEST("#repro", function() -- TEST("#repro", function()
local world = jecs.world() -- local world = jecs.world()
local function getTargets(relation) -- local function getTargets(relation)
local tgts = {} -- local tgts = {}
local pairwildcard = pair(relation, jecs.Wildcard) -- local pairwildcard = pair(relation, jecs.Wildcard)
local idr = assert(jecs.component_record(world, pairwildcard)) -- local idr = assert(jecs.component_record(world, pairwildcard))
local counts = idr.counts -- local counts = idr.counts
local records = idr.records -- local records = idr.records
for _, archetype in world:query(pairwildcard):archetypes() do -- for _, archetype in world:query(pairwildcard):archetypes() do
local archetype_id = archetype.id -- local archetype_id = archetype.id
local count = counts[archetype_id] -- local count = counts[archetype_id]
local tr = records[archetype_id] -- local tr = records[archetype_id]
local types = archetype.types -- local types = archetype.types
for _, entity in archetype.entities do -- for _, entity in archetype.entities do
for i = 0, count - 1 do -- for i = 0, count - 1 do
local tgt = jecs.pair_second(world, types[i + tr] :: any) -- local tgt = jecs.pair_second(world, types[i + tr] :: any)
table.insert(tgts, tgt) -- table.insert(tgts, tgt)
end -- end
end -- end
end -- end
return tgts -- return tgts
end -- end
local Attacks = world:component() -- local Attacks = world:component()
local Eats = world:component() -- local Eats = world:component()
local function setAttacksAndEats(entity1, entity2) -- local function setAttacksAndEats(entity1, entity2)
world:add(entity1, pair(Attacks, entity2)) -- world:add(entity1, pair(Attacks, entity2))
world:add(entity1, pair(Eats, entity2)) -- world:add(entity1, pair(Eats, entity2))
end -- end
local e1 = world:entity() -- local e1 = world:entity()
local e2 = world:entity() -- local e2 = world:entity()
local e3 = world:entity() -- local e3 = world:entity()
setAttacksAndEats(e3, e1) -- setAttacksAndEats(e3, e1)
setAttacksAndEats(e3, e2) -- setAttacksAndEats(e3, e2)
setAttacksAndEats(e1, e2) -- setAttacksAndEats(e1, e2)
local d = dwi(world) -- local d = dwi(world)
world:delete(e2) -- world:delete(e2)
local types1 = { pair(Attacks, e1), pair(Eats, e1) } -- local types1 = { pair(Attacks, e1), pair(Eats, e1) }
table.sort(types1) -- table.sort(types1)
CHECK(d.tbl(e1).type == "") -- CHECK(d.tbl(e1).type == "")
CHECK(d.tbl(e3).type == table.concat(types1, "_")) -- CHECK(d.tbl(e3).type == table.concat(types1, "_"))
for _, entity in getTargets(Attacks) do -- for _, entity in getTargets(Attacks) do
CHECK(entity == e1) -- CHECK(entity == e1)
end -- end
for _, entity in getTargets(Eats) do -- for _, entity in getTargets(Eats) do
CHECK(entity == e1) -- CHECK(entity == e1)
end -- end
end) -- end)
TEST("Hooks", function() TEST("Hooks", function()
do CASE "OnAdd" do CASE "OnAdd"