mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-14 04:29:18 +00:00
Improve relationship performance
Some checks failed
Some checks failed
This commit is contained in:
parent
0b6bfea5c8
commit
8f95309871
5 changed files with 298 additions and 113 deletions
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added signals that allow listening to relation part of pairs in signals.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `OnRemove` hooks so that they are allowed to move entity's archetype even during deletion.
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `jecs.Exclusive` trait for making exclusive relationships.
|
- `jecs.Exclusive` trait for making exclusive relationships.
|
||||||
|
|
||||||
|
|
270
jecs.luau
270
jecs.luau
|
@ -54,8 +54,6 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
patch: (self: Query<T...>, fn: (T...) -> (T...)) -> (),
|
|
||||||
view: (self: Query<T...>) -> View<T...>,
|
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
|
@ -70,6 +68,14 @@ export type Observer = {
|
||||||
query: QueryInner,
|
query: QueryInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type query = {
|
||||||
|
compatible_archetypes: { archetype },
|
||||||
|
ids: { i53 },
|
||||||
|
filter_with: { i53 },
|
||||||
|
filter_without: { i53 },
|
||||||
|
next: () -> (i53, ...any),
|
||||||
|
world: World,
|
||||||
|
}
|
||||||
|
|
||||||
export type observer = {
|
export type observer = {
|
||||||
callback: (archetype: archetype) -> (),
|
callback: (archetype: archetype) -> (),
|
||||||
|
@ -86,7 +92,7 @@ type archetype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type componentrecord = {
|
type componentrecord = {
|
||||||
records: { [i53]: number },
|
records: { [number]: number },
|
||||||
counts: { [i53]: number },
|
counts: { [i53]: number },
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
@ -94,6 +100,8 @@ type componentrecord = {
|
||||||
on_add: ((entity: i53, id: i53, value: any?) -> ())?,
|
on_add: ((entity: i53, id: i53, value: any?) -> ())?,
|
||||||
on_change: ((entity: i53, id: i53, value: any) -> ())?,
|
on_change: ((entity: i53, id: i53, value: any) -> ())?,
|
||||||
on_remove: ((entity: i53, id: i53) -> ())?,
|
on_remove: ((entity: i53, id: i53) -> ())?,
|
||||||
|
|
||||||
|
wildcard_pairs: { [number]: componentrecord },
|
||||||
}
|
}
|
||||||
type record = {
|
type record = {
|
||||||
archetype: archetype,
|
archetype: archetype,
|
||||||
|
@ -889,17 +897,32 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
||||||
if ECS_IS_PAIR(component_id) then
|
if ECS_IS_PAIR(component_id) then
|
||||||
local relation = ECS_PAIR_FIRST(component_id)
|
local relation = ECS_PAIR_FIRST(component_id)
|
||||||
local object = ECS_PAIR_SECOND(component_id)
|
local object = ECS_PAIR_SECOND(component_id)
|
||||||
|
|
||||||
local r = ECS_PAIR(relation, EcsWildcard)
|
local r = ECS_PAIR(relation, EcsWildcard)
|
||||||
local idr_r = id_record_ensure(world, r)
|
local idr_r = id_record_ensure(world, r)
|
||||||
idr_r.size += 1
|
|
||||||
|
|
||||||
|
idr_r.size += 1
|
||||||
archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
|
archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
|
||||||
|
local idr_r_wc_pairs = idr_r.wildcard_pairs
|
||||||
|
if not idr_r_wc_pairs then
|
||||||
|
idr_r_wc_pairs = {} :: {[i53]: componentrecord }
|
||||||
|
idr_r.wildcard_pairs = idr_r_wc_pairs
|
||||||
|
end
|
||||||
|
idr_r_wc_pairs[component_id] = idr
|
||||||
|
|
||||||
local t = ECS_PAIR(EcsWildcard, object)
|
local t = ECS_PAIR(EcsWildcard, object)
|
||||||
local idr_t = id_record_ensure(world, t)
|
local idr_t = id_record_ensure(world, t)
|
||||||
idr_t.size += 1
|
|
||||||
|
|
||||||
|
idr_t.size += 1
|
||||||
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
||||||
|
|
||||||
|
-- Hypothetically this should only capture leaf component records
|
||||||
|
local idr_t_wc_pairs = idr_t.wildcard_pairs
|
||||||
|
if not idr_t_wc_pairs then
|
||||||
|
idr_t_wc_pairs = {} :: {[i53]: componentrecord }
|
||||||
|
idr_t.wildcard_pairs = idr_t_wc_pairs
|
||||||
|
end
|
||||||
|
idr_t_wc_pairs[component_id] = idr
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1086,15 +1109,12 @@ end
|
||||||
|
|
||||||
local function archetype_delete(world: world, archetype: archetype, row: number)
|
local function archetype_delete(world: world, archetype: archetype, row: number)
|
||||||
local entity_index = world.entity_index
|
local entity_index = world.entity_index
|
||||||
local component_index = world.component_index
|
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
local id_types = archetype.types
|
|
||||||
local entities = archetype.entities
|
local entities = archetype.entities
|
||||||
local column_count = #entities
|
local column_count = #entities
|
||||||
local last = #entities
|
local last = #entities
|
||||||
local move = entities[last]
|
local move = entities[last]
|
||||||
-- We assume first that the entity is the last in the archetype
|
-- We assume first that the entity is the last in the archetype
|
||||||
local delete = move
|
|
||||||
|
|
||||||
if row ~= last then
|
if row ~= last then
|
||||||
local record_to_move = entity_index_try_get_any(entity_index, move)
|
local record_to_move = entity_index_try_get_any(entity_index, move)
|
||||||
|
@ -1102,18 +1122,9 @@ local function archetype_delete(world: world, archetype: archetype, row: number)
|
||||||
record_to_move.row = row
|
record_to_move.row = row
|
||||||
end
|
end
|
||||||
|
|
||||||
delete = entities[row]
|
|
||||||
entities[row] = move
|
entities[row] = move
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, id in id_types do
|
|
||||||
local idr = component_index[id]
|
|
||||||
local on_remove = idr.on_remove
|
|
||||||
if on_remove then
|
|
||||||
on_remove(delete, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
entities[last] = nil :: any
|
entities[last] = nil :: any
|
||||||
|
|
||||||
if row == last then
|
if row == last then
|
||||||
|
@ -2365,6 +2376,22 @@ local function world_new()
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function exclusive_traverse_add(
|
||||||
|
archetype: archetype,
|
||||||
|
cr: number,
|
||||||
|
id: i53
|
||||||
|
)
|
||||||
|
local edge = archetype_edges[archetype.id]
|
||||||
|
local to = edge[id]
|
||||||
|
if not to then
|
||||||
|
local dst = table.clone(archetype.types)
|
||||||
|
dst[cr] = id
|
||||||
|
to = archetype_ensure(world, dst)
|
||||||
|
edge[id] = to
|
||||||
|
end
|
||||||
|
return to
|
||||||
|
end
|
||||||
|
|
||||||
local function inner_world_set(world: world, entity: i53, id: i53, data): ()
|
local function inner_world_set(world: world, entity: i53, id: i53, data): ()
|
||||||
local record = inner_entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
|
@ -2395,22 +2422,19 @@ local function world_new()
|
||||||
local edge = archetype_edges[src.id]
|
local edge = archetype_edges[src.id]
|
||||||
to = edge[id]
|
to = edge[id]
|
||||||
if to == nil then
|
if to == nil then
|
||||||
if idr and (bit32.btest(idr.flags) == true) then
|
if idr and (bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) == true) 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
|
||||||
local id_types = src.types
|
local id_types = src.types
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
|
|
||||||
src = record.archetype
|
src = record.archetype
|
||||||
id_types = src.types
|
id_types = src.types
|
||||||
cr = idr.records[src.id]
|
cr = idr.records[src.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
local dst = table.clone(id_types)
|
to = exclusive_traverse_add(src, cr, id)
|
||||||
dst[cr] = id
|
|
||||||
to = archetype_ensure(world, dst)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2425,30 +2449,27 @@ local function world_new()
|
||||||
archetype_edges[(to :: Archetype).id][id] = src
|
archetype_edges[(to :: Archetype).id][id] = src
|
||||||
else
|
else
|
||||||
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
||||||
local cr = idr.records[src.id]
|
local on_remove = idr.on_remove
|
||||||
if cr then
|
if on_remove then
|
||||||
local on_remove = idr.on_remove
|
local cr = idr.records[src.id]
|
||||||
local id_types = src.types
|
if cr then
|
||||||
if on_remove then
|
local id_types = src.types
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
src = record.archetype
|
local arche = record.archetype
|
||||||
id_types = src.types
|
if src ~= arche then
|
||||||
cr = idr.records[src.id]
|
id_types = arche.types
|
||||||
|
cr = idr.records[arche.id]
|
||||||
|
to = exclusive_traverse_add(arche, cr, id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local dst = table.clone(id_types)
|
|
||||||
dst[cr] = id
|
|
||||||
to = archetype_ensure(world, dst)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not to then
|
|
||||||
to = find_archetype_with(world, id, src)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local edges = archetype_edges
|
local edges = archetype_edges
|
||||||
local edge = edges[src.id]
|
local edge = edges[src.id]
|
||||||
|
|
||||||
to = edge[id] :: archetype
|
to = edge[id]
|
||||||
if not to then
|
if not to then
|
||||||
to = find_archetype_with(world, id, src)
|
to = find_archetype_with(world, id, src)
|
||||||
edge[id] = to
|
edge[id] = to
|
||||||
|
@ -2501,7 +2522,7 @@ local function world_new()
|
||||||
local edge = archetype_edges[src.id]
|
local edge = archetype_edges[src.id]
|
||||||
to = edge[id]
|
to = edge[id]
|
||||||
if to == nil then
|
if to == nil then
|
||||||
if idr and (bit32.btest(idr.flags) == true) then
|
if idr and (bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) == true) 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
|
||||||
|
@ -2514,9 +2535,7 @@ local function world_new()
|
||||||
cr = idr.records[src.id]
|
cr = idr.records[src.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
local dst = table.clone(id_types)
|
to = exclusive_traverse_add(src, cr, id)
|
||||||
dst[cr] = id
|
|
||||||
to = archetype_ensure(world, dst)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2531,24 +2550,21 @@ local function world_new()
|
||||||
archetype_edges[(to :: Archetype).id][id] = src
|
archetype_edges[(to :: Archetype).id][id] = src
|
||||||
else
|
else
|
||||||
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
||||||
local cr = idr.records[src.id]
|
local on_remove = idr.on_remove
|
||||||
if cr then
|
if on_remove then
|
||||||
local on_remove = idr.on_remove
|
local cr = idr.records[src.id]
|
||||||
local id_types = src.types
|
if cr then
|
||||||
if on_remove then
|
local id_types = src.types
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
src = record.archetype
|
local arche = record.archetype
|
||||||
id_types = src.types
|
if src ~= arche then
|
||||||
cr = idr.records[src.id]
|
id_types = arche.types
|
||||||
|
cr = idr.records[arche.id]
|
||||||
|
to = exclusive_traverse_add(arche, cr, id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local dst = table.clone(id_types)
|
|
||||||
dst[cr] = id
|
|
||||||
to = archetype_ensure(world, dst)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not to then
|
|
||||||
to = find_archetype_with(world, id, src)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local edges = archetype_edges
|
local edges = archetype_edges
|
||||||
|
@ -2626,12 +2642,20 @@ local function world_new()
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
|
||||||
if idr then
|
|
||||||
idr.on_add = on_add
|
if idr_pair then
|
||||||
|
for id, cr in idr_pair.wildcard_pairs do
|
||||||
|
cr.on_add = on_add
|
||||||
|
end
|
||||||
|
idr_pair.on_add = on_add
|
||||||
else
|
else
|
||||||
inner_world_set(world, component, EcsOnAdd, on_add)
|
local idr = component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_add = on_add
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
inner_world_set(world, component, EcsOnAdd, on_add)
|
||||||
end
|
end
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
|
@ -2661,12 +2685,22 @@ local function world_new()
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
|
||||||
if idr then
|
|
||||||
idr.on_change = on_change
|
if idr_pair then
|
||||||
|
for _, cr in idr_pair.wildcard_pairs do
|
||||||
|
cr.on_change = on_change
|
||||||
|
end
|
||||||
|
|
||||||
|
idr_pair.on_change = on_change
|
||||||
else
|
else
|
||||||
inner_world_set(world, component, EcsOnChange, on_change)
|
local idr = component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_change = on_change
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
inner_world_set(world, component, EcsOnChange, on_change)
|
||||||
end
|
end
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
|
@ -2687,17 +2721,28 @@ local function world_new()
|
||||||
listener(entity, id)
|
listener(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
|
local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
|
||||||
if existing_hook then
|
if existing_hook then
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
|
||||||
if idr then
|
|
||||||
idr.on_remove = on_remove
|
if idr_pair then
|
||||||
|
for _, cr in idr_pair.wildcard_pairs do
|
||||||
|
cr.on_remove = on_remove
|
||||||
|
end
|
||||||
|
|
||||||
|
idr_pair.on_remove = on_remove
|
||||||
else
|
else
|
||||||
inner_world_set(world, component, EcsOnRemove, on_remove)
|
local idr = component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_remove = on_remove
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
inner_world_set(world, component, EcsOnRemove, on_remove)
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
|
@ -2860,6 +2905,7 @@ local function world_new()
|
||||||
if from.columns_map[id] then
|
if from.columns_map[id] then
|
||||||
local idr = world.component_index[id]
|
local idr = world.component_index[id]
|
||||||
local on_remove = idr.on_remove
|
local on_remove = idr.on_remove
|
||||||
|
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id)
|
on_remove(entity, id)
|
||||||
end
|
end
|
||||||
|
@ -2964,12 +3010,16 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
local row = record.row
|
|
||||||
|
|
||||||
if archetype then
|
if archetype then
|
||||||
-- In the future should have a destruct mode for
|
for _, id in archetype.types do
|
||||||
-- deleting archetypes themselves. Maybe requires recycling
|
local idr = component_index[id]
|
||||||
archetype_delete(world, archetype, row)
|
local on_remove = idr.on_remove
|
||||||
|
if on_remove then
|
||||||
|
on_remove(entity, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
archetype_delete(world, record.archetype, record.row)
|
||||||
end
|
end
|
||||||
|
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
|
@ -3035,56 +3085,55 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if idr_t then
|
if idr_t then
|
||||||
local archetype_ids = idr_t.records
|
for id, cr in idr_t.wildcard_pairs do
|
||||||
for archetype_id in archetype_ids do
|
local flags = cr.flags
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
||||||
local node = idr_t_archetype
|
local on_remove = cr.on_remove
|
||||||
local idr_t_types = idr_t_archetype.types
|
if flags_delete_mask then
|
||||||
local entities = idr_t_archetype.entities
|
for archetype_id in cr.records do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
local deleted = false
|
local entities = idr_t_archetype.entities
|
||||||
for _, id in idr_t_types do
|
|
||||||
if not ECS_IS_PAIR(id) then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
local object = entity_index_get_alive(
|
|
||||||
entity_index, ECS_PAIR_SECOND(id))
|
|
||||||
if object ~= entity then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
local id_record = component_index[id]
|
|
||||||
local flags = id_record.flags
|
|
||||||
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
|
||||||
if flags_delete_mask then
|
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local child = entities[i]
|
local child = entities[i]
|
||||||
inner_world_delete(world, child)
|
inner_world_delete(world, child)
|
||||||
end
|
end
|
||||||
deleted = true
|
|
||||||
break
|
break
|
||||||
else
|
end
|
||||||
node = archetype_traverse_remove(world, id, node)
|
else
|
||||||
local on_remove = component_index[id].on_remove
|
for archetype_id in cr.records do
|
||||||
if on_remove then
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
for _, entity in entities do
|
local entities = idr_t_archetype.entities
|
||||||
on_remove(entity, id)
|
-- archetype_traverse_remove is not idempotent meaning
|
||||||
|
-- this access is actually unsafe because it can
|
||||||
|
-- incorrectly cache an edge despite a node of the
|
||||||
|
-- component id on the archetype does not exist. This
|
||||||
|
-- requires careful testing to ensure correct values are
|
||||||
|
-- being passed to the arguments.
|
||||||
|
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
||||||
|
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
local e = entities[i]
|
||||||
|
local r = eindex_sparse_array[ECS_ID(e :: number)]
|
||||||
|
if on_remove then
|
||||||
|
on_remove(e, id)
|
||||||
|
|
||||||
|
local from = r.archetype
|
||||||
|
if from ~= idr_t_archetype then
|
||||||
|
-- unfortunately the on_remove hook allows a window where `e` can have changed archetype
|
||||||
|
-- this is hypothetically not that expensive of an operation anyways
|
||||||
|
to = archetype_traverse_remove(world, id, from)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
inner_entity_move(entity_index, e, r, to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not deleted then
|
for archetype_id in cr.records do
|
||||||
for i = #entities, 1, -1 do
|
archetype_destroy(world, archetypes[archetype_id])
|
||||||
local e = entities[i]
|
|
||||||
local r = inner_entity_index_try_get_unsafe(e) :: record
|
|
||||||
inner_entity_move(entity_index, e, r, node)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for archetype_id in archetype_ids do
|
|
||||||
archetype_destroy(world, archetypes[archetype_id])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr_r then
|
if idr_r then
|
||||||
|
@ -3286,6 +3335,9 @@ end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
world = world_new :: () -> World,
|
world = world_new :: () -> World,
|
||||||
|
World = {
|
||||||
|
new = world_new
|
||||||
|
},
|
||||||
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
|
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
|
||||||
tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
|
tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
|
||||||
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
|
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.9.0-rc.8",
|
"version": "0.9.0-rc.9",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
129
test/tests.luau
129
test/tests.luau
|
@ -333,6 +333,15 @@ TEST("world:add()", function()
|
||||||
|
|
||||||
CHECK(world:has(e, pair(A, B)) == false)
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
CHECK(world:has(e, pair(A, C)) == true)
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
|
||||||
|
-- We have to test the path that checks the uncached method
|
||||||
|
local e1 = world:entity()
|
||||||
|
|
||||||
|
world:add(e1, pair(A, B))
|
||||||
|
world:add(e1, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e1, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e1, pair(A, C)) == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "exclusive relations invoke hooks"
|
do CASE "exclusive relations invoke hooks"
|
||||||
|
@ -379,6 +388,44 @@ TEST("world:add()", function()
|
||||||
CHECK(world:has(e, pair(A, C)) == true)
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "exclusive relations invoke on_remove hooks that should allow side effects"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
local call_count = 0
|
||||||
|
world:set(A, jecs.OnRemove, function(e, id)
|
||||||
|
call_count += 1
|
||||||
|
if call_count == 1 then
|
||||||
|
world:add(e, C)
|
||||||
|
else
|
||||||
|
world:add(e, D)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, C))
|
||||||
|
|
||||||
|
|
||||||
|
-- We have to ensure that it actually invokes hooks everytime it
|
||||||
|
-- traverses the archetype
|
||||||
|
e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, D))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "idempotent"
|
do CASE "idempotent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local d = dwi(world)
|
local d = dwi(world)
|
||||||
|
@ -621,6 +668,57 @@ TEST("world:contains()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:delete()", function()
|
TEST("world:delete()", function()
|
||||||
|
do CASE "idr_t//delete_mask@3102..3108"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
world:add(A, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local B = world:component()
|
||||||
|
local B_OnAdd_called = false
|
||||||
|
local B_OnRemove_called = false
|
||||||
|
world:set(B, jecs.OnAdd, function()
|
||||||
|
B_OnAdd_called = true
|
||||||
|
end)
|
||||||
|
world:set(B, jecs.OnRemove, function()
|
||||||
|
B_OnRemove_called = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:set(A, jecs.OnRemove, function(entity, id)
|
||||||
|
world:set(entity, B, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:set(e2, pair(A, e1), true)
|
||||||
|
|
||||||
|
world:delete(e1)
|
||||||
|
|
||||||
|
CHECK(not world:has(e2, pair(A, e1)))
|
||||||
|
CHECK(not world:has(e2, B))
|
||||||
|
CHECK(not world:contains(e1))
|
||||||
|
CHECK(not world:contains(e2))
|
||||||
|
CHECK(B_OnAdd_called)
|
||||||
|
-- CHECK(B_OnRemove_called)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "idr_t//remove//on_remove//changed_archetype@3123..3126"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
world:set(A, jecs.OnRemove, function(entity, id)
|
||||||
|
world:set(entity, B, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:add(e2, pair(A, e2))
|
||||||
|
world:set(e2, pair(A, e1), true)
|
||||||
|
|
||||||
|
world:delete(e1)
|
||||||
|
|
||||||
|
CHECK(not world:has(e2, pair(A, e1)))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "pair(OnDelete, Delete)"
|
do CASE "pair(OnDelete, Delete)"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local ct = world:component()
|
local ct = world:component()
|
||||||
|
@ -1076,19 +1174,45 @@ TEST("world:added", function()
|
||||||
CHECK(ran)
|
CHECK(ran)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE ""
|
||||||
|
local world = jecs.world()
|
||||||
|
local IsNearby = world:component()
|
||||||
|
world:set(IsNearby, jecs.Name, "IsNearby")
|
||||||
|
local person1, person2 = world:entity(), world:entity()
|
||||||
|
|
||||||
|
world:add(person2, jecs.pair(IsNearby, person1))
|
||||||
|
local IsNearby_added_called = false
|
||||||
|
world:added(IsNearby, function(...) -- This prints fine
|
||||||
|
IsNearby_added_called = true
|
||||||
|
end)
|
||||||
|
local IsNearby_removed_called = false
|
||||||
|
world:removed(IsNearby, function(...)
|
||||||
|
IsNearby_removed_called = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:remove(person2, pair(IsNearby, person1))
|
||||||
|
world:add(person2, pair(IsNearby, person1))
|
||||||
|
world:remove(person2, pair(IsNearby, person1))
|
||||||
|
|
||||||
|
CHECK(IsNearby_added_called)
|
||||||
|
CHECK(IsNearby_removed_called)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
do CASE "Should work even if set after the pair has been used"
|
do CASE "Should work even if set after the pair has been used"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
|
||||||
world:set(world:entity(), A, 2)
|
|
||||||
world:set(world:entity(), pair(A, B), 2)
|
world:set(world:entity(), pair(A, B), 2)
|
||||||
|
|
||||||
|
local ran = false
|
||||||
world:added(A, function()
|
world:added(A, function()
|
||||||
ran = true
|
ran = true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
|
print(pair(A, B))
|
||||||
world:set(entity, pair(A, B), 3)
|
world:set(entity, pair(A, B), 3)
|
||||||
CHECK(ran)
|
CHECK(ran)
|
||||||
end
|
end
|
||||||
|
@ -1099,6 +1223,7 @@ TEST("world:added", function()
|
||||||
|
|
||||||
world:add(world:entity(), A)
|
world:add(world:entity(), A)
|
||||||
|
|
||||||
|
local ran = false
|
||||||
world:added(A, function()
|
world:added(A, function()
|
||||||
ran = true
|
ran = true
|
||||||
end)
|
end)
|
||||||
|
@ -1113,6 +1238,7 @@ TEST("world:added", function()
|
||||||
|
|
||||||
world:add(world:entity(), pair(A, B))
|
world:add(world:entity(), pair(A, B))
|
||||||
|
|
||||||
|
local ran = false
|
||||||
world:added(A, function()
|
world:added(A, function()
|
||||||
ran = true
|
ran = true
|
||||||
end)
|
end)
|
||||||
|
@ -2199,7 +2325,6 @@ TEST("#repro", function()
|
||||||
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, "_"))
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.9.0-rc.8"
|
version = "0.9.0-rc.9"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
Loading…
Reference in a new issue