From 4eb15bbb5510acd5dbd4daae10a2e32db55ac97f Mon Sep 17 00:00:00 2001 From: Ukendio Date: Thu, 27 Mar 2025 21:49:44 +0100 Subject: [PATCH] Update removal functions to handle more variants --- jecs.luau | 154 ++++++++++++++++++++++++++++++++++++++++-------- test/tests.luau | 60 ------------------- 2 files changed, 131 insertions(+), 83 deletions(-) diff --git a/jecs.luau b/jecs.luau index 69a4ad7..b320c84 100644 --- a/jecs.luau +++ b/jecs.luau @@ -1064,21 +1064,19 @@ local function archetype_delete(world: ecs_world_t, archetype: ecs_archetype_t, end local function world_clear(world: ecs_world_t, entity: i53) - --TODO: use sparse_get (stashed) - local record = entity_index_try_get(world.entity_index, entity) - if not record then - return - end + local component_index = world.component_index + local archetypes = world.archetypes + local tgt = ECS_PAIR(EcsWildcard, entity) + local idr_t = component_index[tgt] + local idr = component_index[entity] + local rel = ECS_PAIR(entity, EcsWildcard) + local idr_r = component_index[rel] - local archetype = record.archetype - local row = record.row - - local idr = world.component_index[entity] if idr then local count = 0 local queue = {} for archetype_id in idr.cache do - local idr_archetype = world.archetypes[archetype_id] + local idr_archetype = archetypes[archetype_id] local entities = idr_archetype.entities local n = #entities count += n @@ -1089,14 +1087,79 @@ local function world_clear(world: ecs_world_t, entity: i53) end end - if archetype then - -- In the future should have a destruct mode for - -- deleting archetypes themselves. Maybe requires recycling - archetype_delete(world, archetype, row) + if idr_t then + local children + local ids + + local count = 0 + local archetype_ids = idr_t.cache + for archetype_id in archetype_ids do + local idr_t_archetype = archetypes[archetype_id] + local idr_t_types = idr_t_archetype.types + local entities = idr_t_archetype.entities + local removal_queued = false + + 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 + if not ids then + ids = {} + end + ids[id] = true + removal_queued = true + end + + if not removal_queued then + continue + end + + if not queue then + queue = {} + end + + local n = #entities + table.move(entities, 1, n, count + 1, queue) + count += n + end + + for id in ids do + for _, child in queue do + world_remove(world, child, id) + end + end end - record.archetype = nil :: any - record.row = nil :: any + if idr_r then + local count = 0 + local archetype_ids = idr_r.cache + local ids = {} + local queue = {} + for archetype_id in archetype_ids do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local tr = idr_r_archetype.records[rel] + local tr_count = idr_r_archetype.counts[rel] + local types = idr_r_archetype.types + for i = tr, tr + tr_count - 1 do + ids[types[i]] = true + end + local n = #entities + table.move(entities, 1, n, count + 1, queue) + count += n + end + + for _, e in queue do + for id in ids do + world_remove(world, e, id) + end + end + end end local function archetype_disconnect_edge(edge: ecs_graph_edge_t) @@ -1225,8 +1288,11 @@ local function world_delete(world: ecs_world_t, entity: i53) local component_index = world.component_index local archetypes = world.archetypes local tgt = ECS_PAIR(EcsWildcard, delete) + local rel = ECS_PAIR(delete, EcsWildcard) + local idr_t = component_index[tgt] local idr = component_index[delete] + local idr_r = component_index[rel] if idr then local flags = idr.flags @@ -1256,11 +1322,10 @@ local function world_delete(world: ecs_world_t, entity: i53) end end - local dense_array = entity_index.dense_array - if idr_t then local children local ids + local count = 0 local archetype_ids = idr_t.cache for archetype_id in archetype_ids do @@ -1288,7 +1353,7 @@ local function world_delete(world: ecs_world_t, entity: i53) end break else - if not ids then + if not ids then ids = {} end ids[id] = true @@ -1308,8 +1373,8 @@ local function world_delete(world: ecs_world_t, entity: i53) end if ids then - for id in ids do - for _, child in children do + for _, child in children do + for id in ids do world_remove(world, child, id) end end @@ -1320,6 +1385,51 @@ local function world_delete(world: ecs_world_t, entity: i53) end end + if idr_r then + local count = 0 + local archetype_ids = idr_r.cache + local flags = idr_r.flags + if bit32.band(flags, ECS_ID_DELETE) ~= 0 then + for archetype_id in archetype_ids do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local n = #entities + for i = n, 1, -1 do + world_delete(world, entities[i]) + end + archetype_destroy(world, idr_r_archetype) + end + else + local children = {} + local count = 0 + local ids = {} + for archetype_id in archetype_ids do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local tr = idr_r_achetype.records[rel] + local tr_count = idr_r_archetype.counts[rel] + local types = idr_r_achetype.types + for i = tr, tr_count - 1 do + ids[types[tr]] = true + end + local n = #entities + table.move(entities, 1, n, count + 1, children) + count += n + end + + for _, child in children do + for id in ids do + world_remove(world, child, id) + end + end + + for archetype_id in archetype_ids do + archetype_destroy(world, archetypes[archetype_id]) + end + end + end + + local dense_array = entity_index.dense_array local index_of_deleted_entity = record.dense local index_of_last_alive_entity = entity_index.alive_count entity_index.alive_count = index_of_last_alive_entity - 1 @@ -1350,8 +1460,6 @@ export type QueryInner = { world: World, } - - local function query_iter_init(query: ecs_query_data_t): () -> (number, ...any) local world_query_iter_next diff --git a/test/tests.luau b/test/tests.luau index 2d8eb0f..6e0ab22 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -1109,66 +1109,6 @@ TEST("world:clear()", function() CHECK(world:get(e1, A) == nil) CHECK(world:get(e1, B)) end - do CASE("should remove its components") - local world = jecs.World.new() :: World - local A = world:component() - local B = world:component() - - local e = world:entity() - - world:set(e, A, true) - world:set(e, B, true) - - CHECK(world:get(e, A)) - CHECK(world:get(e, B)) - - world:clear(e) - CHECK(world:get(e, A) == nil) - CHECK(world:get(e, B) == nil) - end - - do CASE("should move last record") - local world = world_new() - local A = world:component() - - local e = world:entity() - local e1 = world:entity() - - world:add(e, A) - world:add(e1, A) - - local archetype = world.archetype_index["1"] - local archetype_entities = archetype.entities - - local _e = e :: number - local _e1 = e1 :: number - - CHECK(archetype_entities[1] == _e) - CHECK(archetype_entities[2] == _e1) - - local e_record: jecs.Record = entity_index_try_get_any( - world.entity_index, e) :: any - local e1_record: jecs.Record = entity_index_try_get_any( - world.entity_index, e1) :: any - CHECK(e_record.archetype == archetype) - CHECK(e1_record.archetype == archetype) - CHECK(e1_record.row == 2) - - world:clear(e) - - CHECK((e_record.archetype :: jecs.Archetype?) == nil) - CHECK((e_record.row :: number?) == nil) - CHECK(e1_record.archetype == archetype) - CHECK(e1_record.row == 1) - - CHECK(archetype_entities[1] == _e1) - CHECK(archetype_entities[2] == nil) - - CHECK(world:contains(e) == true) - CHECK(world:has(e, A) == false) - CHECK(world:contains(e1) == true) - CHECK(world:has(e1, A) == true) - end end) TEST("world:has()", function()