diff --git a/develop/untests.lua b/develop/untests.lua index ca01cf7..4504dc7 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -6655,9 +6655,9 @@ end do local f1, f2 = evo.id(2) - local c1 = evo.chunk(f1) - local c2 = evo.chunk(f2) - local c12 = evo.chunk(f1, f2) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + local c12 = assert(evo.chunk(f1, f2)) local e1a = evo.entity():set(f1, 1):build() local e1b = evo.entity():set(f1, 2):build() @@ -6694,3 +6694,416 @@ do assert(c12_es[1] == e1a and c12_es[2] == e1b) end end + +do + local f1, f2 = evo.id(2) + local c1 = assert(evo.chunk(f1)) + assert(evo.set(f2, f1)) + assert(evo.destroy(f1)) + do + assert(not evo.is_alive(f1)) + assert(evo.is_alive(f2)) + assert(evo.is_empty(f2)) + + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + end +end + +do + local f1 = evo.id() + local c1 = assert(evo.chunk(f1)) + assert(evo.set(f1, f1)) + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + end +end + +do + local f1, f2 = evo.id(2) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + local c12 = assert(evo.chunk(f1, f2)) + assert(evo.set(f1, evo.ON_DESTROY, evo.REMOVE_FRAGMENT_POLICY)) + assert(evo.set(f1, f1)) + assert(evo.set(f2, f1)) + assert(evo.set(f2, f2)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 0 and c2_ec == 0) + + local c12_es, c12_ec = evo.entities(c12) + assert(c12_es and #c12_es == 1 and c12_ec == 1) + assert(c12_es[1] == f2) + end + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f2) + + local c12_es, c12_ec = evo.entities(c12) + assert(c12_es and #c12_es == 0 and c12_ec == 0) + end +end + +do + local f1, f2 = evo.id(2) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + local c12 = assert(evo.chunk(f1, f2)) + assert(evo.set(f1, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY)) + assert(evo.set(f1, f1)) + assert(evo.set(f2, f1)) + assert(evo.set(f2, f2)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 0 and c2_ec == 0) + + local c12_es, c12_ec = evo.entities(c12) + assert(c12_es and #c12_es == 1 and c12_ec == 1) + assert(c12_es[1] == f2) + end + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 0 and c2_ec == 0) + + local c12_es, c12_ec = evo.entities(c12) + assert(c12_es and #c12_es == 0 and c12_ec == 0) + end +end + +do + local f1, f2, f3 = evo.id(3) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + assert(evo.set(f2, f1)) + assert(evo.set(f3, f2)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 1 and c1_ec == 1) + assert(c1_es[1] == f2) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f3) + end + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f3) + end +end + +do + local f1, f2, f3 = evo.id(3) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + assert(evo.set(f1, evo.ON_DESTROY, evo.REMOVE_FRAGMENT_POLICY)) + assert(evo.set(f2, f1)) + assert(evo.set(f3, f2)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 1 and c1_ec == 1) + assert(c1_es[1] == f2) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f3) + end + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f3) + end +end + +do + local f1, f2, f3 = evo.id(3) + local c1 = assert(evo.chunk(f1)) + local c2 = assert(evo.chunk(f2)) + assert(evo.set(f1, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY)) + assert(evo.set(f2, f1)) + assert(evo.set(f3, f2)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 1 and c1_ec == 1) + assert(c1_es[1] == f2) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 1 and c2_ec == 1) + assert(c2_es[1] == f3) + end + assert(evo.destroy(f1)) + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + + local c2_es, c2_ec = evo.entities(c2) + assert(c2_es and #c2_es == 0 and c2_ec == 0) + end +end + +do + local f1, f2, f3, f4, ft = evo.id(5) + assert(evo.set(f1, ft)) + assert(evo.set(f2, ft)) + assert(evo.set(f3, ft)) + assert(evo.set(f3, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY)) + local qt = evo.query():include(ft):build() + + local c4 = assert(evo.chunk(f4)) + local c14 = assert(evo.chunk(f1, f4)) + local c24 = assert(evo.chunk(f2, f4)) + local c234 = assert(evo.chunk(f2, f3, f4)) + local c124 = assert(evo.chunk(f1, f2, f4)) + + local e14 = evo.entity():set(f1, 1):set(f4, 2):build() + local e24 = evo.entity():set(f2, 3):set(f4, 4):build() + local e234 = evo.entity():set(f2, 5):set(f3, 6):set(f4, 7):build() + local e124 = evo.entity():set(f1, 8):set(f2, 6):set(f4, 9):build() + + assert(evo.batch_destroy(qt)) + + do + local c4_es, c4_ec = evo.entities(c4) + assert(c4_es and #c4_es == 3 and c4_ec == 3) + assert(c4_es[1] == e14 and c4_es[2] == e24 and c4_es[3] == e124) + end + + assert(#evo.entities(c14) == 0) + assert(#evo.entities(c24) == 0) + assert(#evo.entities(c124) == 0) + assert(#evo.entities(c234) == 0) + + assert(evo.is_alive(e14) and not evo.is_empty(e14)) + assert(evo.is_alive(e24) and not evo.is_empty(e24)) + assert(not evo.is_alive(e234) and evo.is_empty(e234)) + assert(evo.is_alive(e124) and not evo.is_empty(e124)) +end + +do + local f1 = evo.id() + assert(evo.set(f1, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY)) + assert(evo.set(f1, f1, f1)) + + local remove_count = 0 + assert(evo.set(f1, evo.ON_REMOVE, function(e, f, c) + assert(e == f1) + assert(f == f1) + assert(c == f1) + remove_count = remove_count + 1 + end)) + + local c1 = assert(evo.chunk(f1)) + + assert(evo.destroy(f1)) + + do + assert(not evo.is_alive(f1)) + assert(remove_count == 1) + + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + end +end + +do + local f1 = evo.id() + assert(evo.set(f1, evo.ON_DESTROY, evo.REMOVE_FRAGMENT_POLICY)) + assert(evo.set(f1, f1, f1)) + + local remove_count = 0 + assert(evo.set(f1, evo.ON_REMOVE, function(e, f, c) + assert(e == f1) + assert(f == f1) + assert(c == f1) + remove_count = remove_count + 1 + end)) + + local c1 = assert(evo.chunk(f1)) + + assert(evo.destroy(f1)) + + do + assert(not evo.is_alive(f1)) + assert(remove_count == 1) + + local c1_es, c1_ec = evo.entities(c1) + assert(c1_es and #c1_es == 0 and c1_ec == 0) + end +end + +do + local f1, f2, f3 = evo.id(3) + + assert(evo.set(f1, evo.NAME, 'f1')) + assert(evo.set(f2, evo.NAME, 'f2')) + assert(evo.set(f3, evo.NAME, 'f3')) + + local c1 = evo.chunk(f1) + local c12 = evo.chunk(f1, f2) + local c13 = evo.chunk(f1, f3) + local c123 = evo.chunk(f1, f2, f3) + + local e1a = evo.entity():set(f1, 1):build() + local e1b = evo.entity():set(f1, 2):build() + + local e12a = evo.entity():set(f1, 3):set(f2, 4):build() + local e12b = evo.entity():set(f1, 5):set(f2, 6):build() + + local e123a = evo.entity():set(f1, 7):set(f2, 8):set(f3, 9):build() + local e123b = evo.entity():set(f1, 10):set(f2, 11):set(f3, 12):build() + + assert(evo.destroy(f2)) + + do + assert(c1 and c12 and c13 and c123) + end + + do + local c1_es, c1_ec = evo.entities(c1) + assert(c1 and c1_es and c1_ec) + assert(c1_ec == 4 and #c1_es == 4) + assert(c1_es[1] == e1a and c1_es[2] == e1b and c1_es[3] == e12a and c1_es[4] == e12b) + end + + do + local c12_es, c12_ec = evo.entities(c12) + assert(c12 and c12_es and c12_ec) + assert(c12_ec == 0 and #c12_es == 0) + end + + do + local c13_es, c13_ec = evo.entities(c13) + assert(c13 and c13_es and c13_ec) + assert(c13_ec == 2 and #c13_es == 2) + assert(c13_es[1] == e123a and c13_es[2] == e123b) + end + + do + local c123_es, c123_ec = evo.entities(c123) + assert(c123 and c123_es and c123_ec) + assert(c123_ec == 0 and #c123_es == 0) + end +end + +do + do + local f1, f2 = evo.id(2) + evo.set(f1, f1) + evo.set(f2, f1) + evo.set(f1, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY) + assert(evo.destroy(f1)) + assert(not evo.is_alive(f1)) + assert(not evo.is_alive(f2)) + end + + do + local f1, f2 = evo.id(2) + evo.set(f1, f1) + evo.set(f2, f1) + evo.set(f1, evo.ON_DESTROY, evo.REMOVE_FRAGMENT_POLICY) + assert(evo.destroy(f1)) + assert(not evo.is_alive(f1)) + assert(evo.is_alive(f2) and evo.is_empty(f2)) + end +end + +do + local f1, f2 = evo.id(2) + + evo.set(f1, evo.ON_DESTROY, evo.DESTROY_ENTITY_POLICY) + + local e12a = evo.entity():set(f1, 1):set(f2, 2):build() + local e12b = evo.entity():set(f1, 3):set(f2, 4):build() + local e_e12a_e12b = evo.entity():set(e12a, 11):set(e12b, 22):build() + + local e2a = evo.entity():set(f2, 5):build() + local e2b = evo.entity():set(f2, 6):build() + local e_e2a_e2b = evo.entity():set(e2a, 55):set(e2b, 66):build() + + assert(evo.destroy(f1)) + + do + assert(not evo.is_alive(e12a) and not evo.is_alive(e12b)) + assert(evo.is_alive(e_e12a_e12b) and evo.is_empty(e_e12a_e12b)) + + assert(evo.is_alive(e2a) and evo.is_alive(e2b)) + assert(evo.is_alive(e_e2a_e2b) and not evo.is_empty(e_e2a_e2b)) + end + + do + local c2, c2_es, c2_ec = evo.chunk(f2) + assert(c2 and c2_es and c2_ec) + assert(#c2_es == 2 and c2_ec == 2) + assert(c2_es[1] == e2a and c2_es[2] == e2b) + end +end + +do + local f1, f2 = evo.id(2) + + evo.set(f1, evo.NAME, "f1") + evo.set(f2, evo.NAME, "f2") + + do + local c12 = evo.chunk(f1, f2) + + local f3 = evo.id() + evo.set(f3, evo.TAG) + local c123 = evo.chunk(f2, f1, f3) + evo.spawn_at(c123) + + assert(c12, c123) + end + + evo.set(f1, evo.ON_DESTROY, evo.REMOVE_FRAGMENT_POLICY) + + local e12a = evo.entity():set(f1, 1):set(f2, 2):build() + local e12b = evo.entity():set(f1, 3):set(f2, 4):build() + local e_e12a_e12b = evo.entity():set(e12a, 11):set(e12b, 22):build() + + local e2a = evo.entity():set(f2, 5):build() + local e2b = evo.entity():set(f2, 6):build() + local e_e2a_e2b = evo.entity():set(e2a, 55):set(e2b, 66):build() + + assert(evo.destroy(f1)) + + do + assert(evo.is_alive(e12a) and evo.is_alive(e12b)) + assert(evo.is_alive(e_e12a_e12b) and not evo.is_empty(e_e12a_e12b)) + assert(evo.is_alive(e2a) and evo.is_alive(e2b)) + assert(evo.is_alive(e_e2a_e2b) and not evo.is_empty(e_e2a_e2b)) + end + + do + local c2, c2_es, c2_ec = evo.chunk(f2) + assert(c2 and c2_es and c2_ec) + assert(#c2_es == 4 and c2_ec == 4) + assert(c2_es[1] == e2a and c2_es[2] == e2b and c2_es[3] == e12a and c2_es[4] == e12b) + end +end diff --git a/evolved.lua b/evolved.lua index a61e6b4..394ee6e 100644 --- a/evolved.lua +++ b/evolved.lua @@ -128,6 +128,7 @@ local __lua_ipairs = ipairs local __lua_next = next local __lua_pairs = pairs local __lua_pcall = pcall +local __lua_print = print local __lua_select = select local __lua_setmetatable = setmetatable local __lua_string_format = string.format @@ -1636,6 +1637,73 @@ local __chunk_multi_remove --- --- +---@param fragment evolved.fragment +---@param policy evolved.id +---@return integer purged_count +local function __purge_fragment(fragment, policy) + if __defer_depth <= 0 then + __lua_error('purge operations should be deferred') + end + + local minor_chunks = __minor_chunks[fragment] + + if not minor_chunks then + return 0 + end + + local purged_count = 0 + + if policy == __DESTROY_ENTITY_POLICY then + for minor_chunk_index = #minor_chunks, 1, -1 do + local minor_chunk = minor_chunks[minor_chunk_index] + purged_count = purged_count + __chunk_destroy(minor_chunk) + end + elseif policy == __REMOVE_FRAGMENT_POLICY then + for minor_chunk_index = #minor_chunks, 1, -1 do + local minor_chunk = minor_chunks[minor_chunk_index] + purged_count = purged_count + __chunk_remove(minor_chunk, fragment) + end + else + __lua_print(__lua_string_format('| evolved.lua | unknown ON_DESTROY policy (%s) on (%s)', + __id_name(policy), __id_name(fragment))) + end + + return purged_count +end + +---@param fragments evolved.fragment[] +---@param policies evolved.id[] +---@param count integer +---@return integer purged_count +local function __purge_fragments(fragments, policies, count) + if __defer_depth <= 0 then + __lua_error('purge operations should be deferred') + end + + local purged_count = 0 + + for index = 1, count do + local fragment, policy = fragments[index], policies[index] + + if policy == __DESTROY_ENTITY_POLICY then + purged_count = purged_count + __purge_fragment(fragment, __DESTROY_ENTITY_POLICY) + elseif policy == __REMOVE_FRAGMENT_POLICY then + purged_count = purged_count + __purge_fragment(fragment, __REMOVE_FRAGMENT_POLICY) + else + __lua_print(__lua_string_format('| evolved.lua | unknown ON_DESTROY policy (%s) on (%s)', + __id_name(policy), __id_name(fragment))) + end + end + + return purged_count +end + +--- +--- +--- +--- +--- + ---@param chunk evolved.chunk ---@param fragment evolved.fragment ---@param ... any component arguments @@ -2180,18 +2248,38 @@ __chunk_destroy = function(chunk) end do + local purging_fragments = __acquire_table(__table_pool_tag.fragment_list) + local purging_policies = __acquire_table(__table_pool_tag.fragment_list) + local purging_count = 0 + local entity_chunks = __entity_chunks local entity_places = __entity_places for place = 1, chunk_entity_count do local entity = chunk_entities[place] local entity_index = entity % 0x100000 + + if __minor_chunks[entity] then + purging_count = purging_count + 1 + purging_policies[purging_count] = __chunk_get_components(chunk, place, __ON_DESTROY) + or __REMOVE_FRAGMENT_POLICY + purging_fragments[purging_count] = entity + end + entity_chunks[entity_index] = nil entity_places[entity_index] = nil + __release_id(entity) end __detach_all_entities(chunk) + + if purging_count > 0 then + __purge_fragments(purging_fragments, purging_policies, purging_count) + end + + __release_table(__table_pool_tag.fragment_list, purging_fragments) + __release_table(__table_pool_tag.fragment_list, purging_policies) end __structural_changes = __structural_changes + chunk_entity_count @@ -4965,14 +5053,10 @@ __evolved_clear = function(entity) local chunk = entity_chunks[entity_index] local place = entity_places[entity_index] - if not chunk then - return true, false - end - __defer() do - if chunk.__has_remove_hooks then + if chunk and chunk.__has_remove_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count local chunk_component_indices = chunk.__component_indices @@ -4998,12 +5082,14 @@ __evolved_clear = function(entity) end end - __detach_entity(chunk, place) + if chunk then + __detach_entity(chunk, place) - entity_chunks[entity_index] = nil - entity_places[entity_index] = nil + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil - __structural_changes = __structural_changes + 1 + __structural_changes = __structural_changes + 1 + end end __commit() @@ -5031,15 +5117,10 @@ __evolved_destroy = function(entity) local chunk = entity_chunks[entity_index] local place = entity_places[entity_index] - if not chunk then - __release_id(entity) - return true, false - end - __defer() do - if chunk.__has_remove_hooks then + if chunk and chunk.__has_remove_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count local chunk_component_indices = chunk.__component_indices @@ -5065,13 +5146,31 @@ __evolved_destroy = function(entity) end end - __release_id(entity) - __detach_entity(chunk, place) + do + local purging_fragment ---@type evolved.fragment? + local purging_policy ---@type evolved.id? - entity_chunks[entity_index] = nil - entity_places[entity_index] = nil + if __minor_chunks[entity] then + purging_fragment = entity + purging_policy = chunk and __chunk_get_components(chunk, place, __ON_DESTROY) + or __REMOVE_FRAGMENT_POLICY + end - __structural_changes = __structural_changes + 1 + if chunk then + __detach_entity(chunk, place) + + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil + + __structural_changes = __structural_changes + 1 + end + + __release_id(entity) + + if purging_fragment then + __purge_fragment(purging_fragment, purging_policy) + end + end end __commit()