mirror of
https://github.com/Ukendio/jecs.git
synced 2026-03-18 00:44:32 +00:00
Prune on cascaded deletion
This commit is contained in:
parent
aeedea2fcb
commit
4236bd02fd
2 changed files with 121 additions and 35 deletions
|
|
@ -533,7 +533,11 @@ end
|
||||||
local function entity_index_get_alive(entity_index: entityindex, entity: i53): i53?
|
local function entity_index_get_alive(entity_index: entityindex, entity: i53): i53?
|
||||||
local r = entity_index_try_get_any(entity_index, entity :: number)
|
local r = entity_index_try_get_any(entity_index, entity :: number)
|
||||||
if r then
|
if r then
|
||||||
return entity_index.dense_array[r.dense]
|
local dense = r.dense
|
||||||
|
if dense > entity_index.alive_count then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return entity_index.dense_array[dense]
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
@ -3510,12 +3514,17 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr_t then
|
if idr_t then
|
||||||
local archetype_ids = idr_t.records
|
local archetype_ids = idr_t.records
|
||||||
local to_remove = {}:: { [i53]: componentrecord}
|
local to_remove = {}:: { [i53]: componentrecord}
|
||||||
|
local did_cascade_delete = false
|
||||||
|
|
||||||
for archetype_id in archetype_ids do
|
for archetype_id in archetype_ids do
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
if not idr_t_archetype then
|
||||||
|
continue
|
||||||
|
end
|
||||||
local idr_t_types = idr_t_archetype.types
|
local idr_t_types = idr_t_archetype.types
|
||||||
local entities = idr_t_archetype.entities
|
local entities = idr_t_archetype.entities
|
||||||
local deleted_any = false
|
local deleted_any = false
|
||||||
|
|
@ -3547,6 +3556,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
if deleted_any then
|
if deleted_any then
|
||||||
|
did_cascade_delete = true
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3583,9 +3593,6 @@ local function world_new(DEBUG: boolean?)
|
||||||
for id, component_record in to_remove do
|
for id, component_record in to_remove do
|
||||||
local on_remove = component_record.on_remove
|
local on_remove = component_record.on_remove
|
||||||
if on_remove then
|
if on_remove then
|
||||||
-- NOTE(marcus): We could be smarter with this and
|
|
||||||
-- assume hooks are deterministic and that they will
|
|
||||||
-- move to the same archetype. However users often are not reasonable people.
|
|
||||||
on_remove(child, id)
|
on_remove(child, id)
|
||||||
local src = r.archetype
|
local src = r.archetype
|
||||||
if src ~= idr_t_archetype then
|
if src ~= idr_t_archetype then
|
||||||
|
|
@ -3601,6 +3608,20 @@ local function world_new(DEBUG: boolean?)
|
||||||
table.clear(to_remove)
|
table.clear(to_remove)
|
||||||
archetype_destroy(world, idr_t_archetype)
|
archetype_destroy(world, idr_t_archetype)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if did_cascade_delete then
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
if not idr_t_archetype then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
world_delete(world, entities[i])
|
||||||
|
end
|
||||||
|
archetype_destroy(world, idr_t_archetype)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr_r then
|
if idr_r then
|
||||||
|
|
|
||||||
|
|
@ -653,6 +653,71 @@ TEST("world:children()", function()
|
||||||
jecs.ECS_META_RESET()
|
jecs.ECS_META_RESET()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Arauser repro: many parents, only some have routes+checkpoints; delete all parents.
|
||||||
|
-- With the bug: 1 checkpoint can remain and/or world:target returns non-alive parent.
|
||||||
|
-- Scale and structure match arauser so the test FAILS when the bug is present.
|
||||||
|
TEST("ChildOf cascade: world_target must not return non-alive parent", function()
|
||||||
|
local w = jecs.world(true)
|
||||||
|
local ParentTag = w:component()
|
||||||
|
local Anything = w:component()
|
||||||
|
local RouteTag = w:entity()
|
||||||
|
local CheckpointTag = w:entity()
|
||||||
|
local RouteOf = w:entity()
|
||||||
|
|
||||||
|
-- Like arauser: many parents (tiles) with two components, only a subset get routes + checkpoints
|
||||||
|
local nParents = 2000
|
||||||
|
local nWithChildren = 10
|
||||||
|
local parents = {}
|
||||||
|
for i = 1, nParents do
|
||||||
|
local p = w:entity()
|
||||||
|
w:set(p, ParentTag, {
|
||||||
|
row = math.floor((i - 1) / 50) + 1,
|
||||||
|
col = ((i - 1) % 50) + 1,
|
||||||
|
})
|
||||||
|
w:add(p, Anything)
|
||||||
|
parents[i] = p
|
||||||
|
end
|
||||||
|
local used = {}
|
||||||
|
local picked = 0
|
||||||
|
while picked < nWithChildren do
|
||||||
|
local idx = math.random(1, nParents)
|
||||||
|
if not used[idx] then
|
||||||
|
used[idx] = true
|
||||||
|
picked += 1
|
||||||
|
local p = parents[idx]
|
||||||
|
local route = w:entity()
|
||||||
|
w:add(route, RouteTag)
|
||||||
|
w:add(route, pair(ChildOf, p))
|
||||||
|
local checkpoint = w:entity()
|
||||||
|
w:add(checkpoint, CheckpointTag)
|
||||||
|
w:add(checkpoint, pair(ChildOf, p))
|
||||||
|
w:add(checkpoint, pair(RouteOf, route))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Delete all parents (collect first to avoid iterator invalidation)
|
||||||
|
local toDelete = {}
|
||||||
|
for e in w:query(ParentTag):iter() do
|
||||||
|
toDelete[#toDelete + 1] = e
|
||||||
|
end
|
||||||
|
for _, e in ipairs(toDelete) do
|
||||||
|
w:delete(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- These must hold; with the bug one of them fails (checkpoint remains or parent not alive)
|
||||||
|
local count = 0
|
||||||
|
for checkpoint in w:query(CheckpointTag):iter() do
|
||||||
|
count += 1
|
||||||
|
CHECK(w:contains(checkpoint))
|
||||||
|
local parent = w:target(checkpoint, ChildOf)
|
||||||
|
if parent ~= nil then
|
||||||
|
CHECK(w:contains(parent))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
CHECK(count == 0)
|
||||||
|
jecs.ECS_META_RESET()
|
||||||
|
end)
|
||||||
|
|
||||||
-- TEST("world:purge()", function()
|
-- TEST("world:purge()", function()
|
||||||
-- do CASE "should remove all instances of specified component"
|
-- do CASE "should remove all instances of specified component"
|
||||||
-- local world = jecs.world()
|
-- local world = jecs.world()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue