mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
check cascaded deletion for structural changes within onremove hooks
This commit is contained in:
parent
5208aa7749
commit
e4b12f4a28
3 changed files with 62 additions and 32 deletions
|
|
@ -83,6 +83,9 @@ local function observers_new(
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
local wc = tgt == jecs.w
|
||||||
local onremoved = world:removed(rel, function(entity, id, delete: boolean?)
|
local onremoved = world:removed(rel, function(entity, id, delete: boolean?)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
if not wc and id ~= term then
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -98,7 +101,10 @@ local function observers_new(
|
||||||
|
|
||||||
table.insert(cleanup, onremoved)
|
table.insert(cleanup, onremoved)
|
||||||
else
|
else
|
||||||
local onremoved = world:removed(term, function(entity, id)
|
local onremoved = world:removed(term, function(entity, id, delete: boolean?)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if archetype then
|
||||||
|
|
|
||||||
|
|
@ -1693,7 +1693,7 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
local row = i_u
|
local row = i_u
|
||||||
i_u -= 1
|
i_u -= 1
|
||||||
|
|
||||||
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], captured_g[row]
|
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], col6[row]
|
||||||
end
|
end
|
||||||
elseif not id8 then
|
elseif not id8 then
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
|
|
@ -2288,7 +2288,7 @@ local function query_cached(query: QueryInner)
|
||||||
local row = i_u
|
local row = i_u
|
||||||
i_u -= 1
|
i_u -= 1
|
||||||
|
|
||||||
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], captured_g[row]
|
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], col6[row]
|
||||||
end
|
end
|
||||||
elseif not id8 then
|
elseif not id8 then
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
|
|
@ -3393,6 +3393,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_delete(world: world, entity: i53)
|
local function world_delete(world: world, entity: i53)
|
||||||
|
DEBUG_DELETING_ENTITY = entity
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
|
|
@ -3525,6 +3526,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
else
|
else
|
||||||
local on_remove = id_record.on_remove
|
local on_remove = id_record.on_remove
|
||||||
|
|
||||||
|
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local child = entities[i]
|
local child = entities[i]
|
||||||
if on_remove then
|
if on_remove then
|
||||||
|
|
@ -3532,18 +3534,12 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = entity_index_try_get_unsafe(child) :: record
|
local r = entity_index_try_get_unsafe(child) :: record
|
||||||
local to = archetype_traverse_remove(world, id, r.archetype)
|
|
||||||
inner_entity_move(child, r, to)
|
inner_entity_move(child, r, to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
archetype_destroy(world, idr_t_archetype)
|
||||||
for archetype_id in archetype_ids do
|
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
|
||||||
if idr_t_archetype then
|
|
||||||
archetype_destroy(world, idr_t_archetype)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3707,16 +3703,33 @@ local function world_new(DEBUG: boolean?)
|
||||||
-- NOTE(marcus): Make it easy to grep the debug functions and
|
-- NOTE(marcus): Make it easy to grep the debug functions and
|
||||||
-- being able to read the specification, without having to look
|
-- being able to read the specification, without having to look
|
||||||
-- at the implementation to understand invariants.
|
-- at the implementation to understand invariants.
|
||||||
local function world_remove_checked(world: world, entity: i53, id: i53)
|
|
||||||
|
local DEBUG_DELETING_ENTITY
|
||||||
|
local function DEBUG_IS_DELETING_ENTITY(entity: i53)
|
||||||
|
if DEBUG_DELETING_ENTITY == entity then
|
||||||
|
error([[
|
||||||
|
Tried to make structural changes while the entity is in process
|
||||||
|
of being deleted. You called this function inside of the
|
||||||
|
OnRemove hook, but the entity is going to remove all of its
|
||||||
|
components making this operation moot.
|
||||||
|
]], 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function DEBUG_IS_INVALID_ENTITY(entity: i53)
|
||||||
local entity_id = ECS_ID(entity)
|
local entity_id = ECS_ID(entity)
|
||||||
local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id
|
local r = eindex_sparse_array[entity_id]
|
||||||
if wrong_entity then
|
local canonical_entity = eindex_dense_array[r.dense]
|
||||||
|
|
||||||
|
if canonical_entity ~= entity then
|
||||||
error([[
|
error([[
|
||||||
This Entity handle has an outdated generation. You are
|
This Entity handle has an outdated generation. You are
|
||||||
probably holding onto an entity that you got from outside the ECS
|
probably holding onto an entity that you got from outside the ECS
|
||||||
]], 2)
|
]], 2)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function DEBUG_ID_IS_INVALID_PAIR(id: i53)
|
||||||
if ECS_ID_IS_WILDCARD(id) then
|
if ECS_ID_IS_WILDCARD(id) then
|
||||||
error([[
|
error([[
|
||||||
You tried to pass in a wildcard pair. This is strictly
|
You tried to pass in a wildcard pair. This is strictly
|
||||||
|
|
@ -3725,34 +3738,43 @@ local function world_new(DEBUG: boolean?)
|
||||||
targets to remove and use jecs.bulk_remove.
|
targets to remove and use jecs.bulk_remove.
|
||||||
]], 2)
|
]], 2)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE(marcus): I have to save the old function and overriding the
|
||||||
|
-- upvalue in order to actually allow cascaded deletions to also be
|
||||||
|
-- checked by our program because we use the world_delete ptr internally.
|
||||||
|
local canonical_world_delete = world_delete
|
||||||
|
local function world_delete_checked(world: world, entity: i53)
|
||||||
|
DEBUG_DELETING_ENTITY = entity
|
||||||
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
|
canonical_world_delete(world, entity, id)
|
||||||
|
end
|
||||||
|
world_delete = world_delete_checked
|
||||||
|
|
||||||
|
local function world_remove_checked(world: world, entity: i53, id: i53)
|
||||||
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
|
DEBUG_ID_IS_INVALID_PAIR(id)
|
||||||
|
|
||||||
world_remove(world, entity, id)
|
world_remove(world, entity, id)
|
||||||
end
|
end
|
||||||
local function world_add_checked(world: world, entity: i53, id: i53)
|
local function world_add_checked(world: world, entity: i53, id: i53)
|
||||||
local entity_id = ECS_ID(entity)
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
if wrong_entity then
|
DEBUG_ID_IS_INVALID_PAIR(id)
|
||||||
error([[
|
|
||||||
This Entity handle has an outdated generation. You are
|
|
||||||
probably holding onto an entity that you got from outside the ECS
|
|
||||||
]], 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
world_add(world, entity, id)
|
world_add(world, entity, id)
|
||||||
end
|
end
|
||||||
local function world_set_checked(world: world, entity: i53, id: i53, value: any)
|
local function world_set_checked(world: world, entity: i53, id: i53, value: any)
|
||||||
local entity_id = ECS_ID(entity)
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
if wrong_entity then
|
DEBUG_ID_IS_INVALID_PAIR(id)
|
||||||
error([[
|
|
||||||
This Entity handle has an outdated generation. You are
|
|
||||||
probably holding onto an entity that you got from outside the ECS
|
|
||||||
]], 2)
|
|
||||||
end
|
|
||||||
world_set(world, entity, id, value)
|
world_set(world, entity, id, value)
|
||||||
end
|
end
|
||||||
world.remove = world_remove_unchecked
|
world.remove = world_remove_checked
|
||||||
world.add = world_add_checked
|
world.add = world_add_checked
|
||||||
|
world.set = world_set_checked
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, EcsRest do
|
for i = 1, EcsRest do
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ local entity_visualiser = require("@modules/entity_visualiser")
|
||||||
local dwi = entity_visualiser.stringify
|
local dwi = entity_visualiser.stringify
|
||||||
|
|
||||||
TEST("reproduce idr_t nil archetype bug", function()
|
TEST("reproduce idr_t nil archetype bug", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world(true)
|
||||||
|
|
||||||
local cts = {
|
local cts = {
|
||||||
Humanoid = world:component(),
|
Humanoid = world:component(),
|
||||||
|
|
@ -46,7 +46,9 @@ TEST("reproduce idr_t nil archetype bug", function()
|
||||||
local src = r.archetype
|
local src = r.archetype
|
||||||
|
|
||||||
--REMOVING THIS jecs.archetype_traverse_remove CALL STOPS IT FROM HAPPENING
|
--REMOVING THIS jecs.archetype_traverse_remove CALL STOPS IT FROM HAPPENING
|
||||||
-- local dst = src and jecs.archetype_traverse_remove(world, id, src)
|
CHECK_EXPECT_ERR(function()
|
||||||
|
world:remove(entity, cts.Humanoid)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local batchSize = 200
|
local batchSize = 200
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue