From 1775d57c82b2130950ac490aee2af49f2a09af41 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 6 Apr 2025 21:25:31 +0700 Subject: [PATCH] new evolved.spawn_as function --- README.md | 1 + ROADMAP.md | 1 - develop/untests.lua | 266 +++++++++++++++++++++++++++++++++++ evolved.lua | 333 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 575 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index de708a1..671159e 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_sta process :: phase... -> () spawn_at :: chunk?, fragment[]?, component[]? -> entity +spawn_as :: entity?, fragment[]?, component[]? -> entity spawn_with :: fragment[]?, component[]? -> entity debug_mode :: boolean -> () diff --git a/ROADMAP.md b/ROADMAP.md index b483eb1..53db098 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,6 @@ - can we pass systems and groups to the process function? - builders should be rewritten :/ -- add evolved.spawn_as function - is/has_all/any for lists? ## After first release diff --git a/develop/untests.lua b/develop/untests.lua index f1f2698..558b6e6 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -8226,3 +8226,269 @@ do assert(evo.has(e, f2) and evo.get(e, f2) == 43) end end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f1, evo.NAME, 'f1') + evo.set(f2, evo.NAME, 'f2') + + evo.set(f1, evo.DEFAULT, 42) + evo.set(f3, evo.TAG) + + do + local p = evo.id() + + do + local e = evo.spawn_as(p) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn_as(p, {}) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn_as(p, {}, {}) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn_as(p, {}, { 43 }) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + end + + do + local p = evo.id() + + do + local e = evo.spawn_as(p, { f1 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, { f1 }, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, { f1 }, { 43 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + end + + do + local e = evo.spawn_as(p, { f1 }, { 43, 44 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + end + + do + local e = evo.spawn_as(p, { f1, f2 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + + do + local e = evo.spawn_as(p, { f2 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + + do + local e = evo.spawn_as(p, { f2 }, { 44 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(evo.has(e, f2) and evo.get(e, f2) == 44) + end + + do + local e = evo.spawn_as(p, { f2, f1 }, { 44, 43 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == 44) + end + + do + local e = evo.spawn_as(p, { f1, f2, f3 }, { 43, 44 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == 44) + assert(evo.has(e, f3) and evo.get(e, f3) == nil) + end + + do + local e = evo.spawn_as(p, { f1, f2, f3 }, { 43, 44, 45 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == 44) + assert(evo.has(e, f3) and evo.get(e, f3) == nil) + end + end + + do + local p = evo.id() + evo.set(p, f1, 42) + + do + local e = evo.spawn_as(p) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, {}, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, {}, { 43 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + end + + do + local p = evo.id() + evo.set(p, f1, 43) + + do + local e = evo.spawn_as(p, { f1 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, { f1 }, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + end + + do + local e = evo.spawn_as(p, { f1 }, { 44 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 44) + end + + do + local e = evo.spawn_as(p, { f1 }, { 44, 45 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 44) + end + + do + local e = evo.spawn_as(p, { f2 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + + do + local e = evo.spawn_as(p, { f2 }, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + + do + local e = evo.spawn_as(p, { f2 }, { 45 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 43) + assert(evo.has(e, f2) and evo.get(e, f2) == 45) + end + + do + local e = evo.spawn_as(p, { f1, f2 }, {}) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + + do + local e = evo.spawn_as(p, { f1, f2 }, { 44, 45 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 44) + assert(evo.has(e, f2) and evo.get(e, f2) == 45) + end + end +end + +do + local f1, f2 = evo.id(2) + + local p = evo.id() + + assert(evo.defer()) + evo.set(p, f1, 42) + local e1 = evo.spawn_as(p) + local e2 = evo.spawn_as(p, { f2 }, { 44 }) + local e3 = evo.spawn_as(p, { f1, f2 }, { 43, 44 }) + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(evo.is_alive(e3) and evo.is_empty(e3)) + assert(evo.commit()) + + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 42) + + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 44) + + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 43) + assert(evo.has(e3, f2) and evo.get(e3, f2) == 44) +end + +do + local f1, f2, f3 = evo.id(3) + + do + local p1 = evo.spawn_as(nil, { f2, f3 }, { 42, 43 }) + local e1 = evo.spawn_as(p1, { f1 }, { 41 }) + + assert(evo.is_alive_all(p1, e1) and not evo.is_empty_any(p1, e1)) + + assert(not evo.has(p1, f1) and evo.get(p1, f1) == nil) + assert(evo.has(p1, f2) and evo.get(p1, f2) == 42) + assert(evo.has(p1, f3) and evo.get(p1, f3) == 43) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 41) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) + assert(evo.has(e1, f3) and evo.get(e1, f3) == 43) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f2, evo.DUPLICATE, function(v) return v * 2 end) + + do + local p1 = evo.spawn_as(nil, { f2, f3 }, { 42, 43 }) + local e1 = evo.spawn_as(p1, { f1 }, { 41 }) + + assert(evo.is_alive_all(p1, e1) and not evo.is_empty_any(p1, e1)) + + assert(not evo.has(p1, f1) and evo.get(p1, f1) == nil) + assert(evo.has(p1, f2) and evo.get(p1, f2) == 84) + assert(evo.has(p1, f3) and evo.get(p1, f3) == 43) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 41) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 168) + assert(evo.has(e1, f3) and evo.get(e1, f3) == 43) + end +end diff --git a/evolved.lua b/evolved.lua index 74e2a77..3d2db58 100644 --- a/evolved.lua +++ b/evolved.lua @@ -761,6 +761,7 @@ local __evolved_execute local __evolved_process local __evolved_spawn_at +local __evolved_spawn_as local __evolved_spawn_with local __evolved_debug_mode @@ -998,6 +999,16 @@ function __debug_fns.validate_entity(entity) end end +---@param prefab evolved.entity +function __debug_fns.validate_prefab(prefab) + local prefab_index = prefab % 0x100000 + + if __freelist_ids[prefab_index] ~= prefab then + __error_fmt('the prefab (%s) is not alive and cannot be used', + __id_name(prefab)) + end +end + ---@param ... evolved.entity entities function __debug_fns.validate_entities(...) for i = 1, __lua_select('#', ...) do @@ -1622,6 +1633,7 @@ local __defer_batch_multi_set local __defer_batch_multi_remove local __defer_spawn_entity_at +local __defer_spawn_entity_as local __defer_spawn_entity_with local __defer_call_hook @@ -1705,9 +1717,6 @@ local function __spawn_entity_at(entity, chunk, fragment_list, fragment_count, c local chunk_component_storages = chunk.__component_storages local chunk_component_fragments = chunk.__component_fragments - local chunk_has_setup_hooks = chunk.__has_setup_hooks - local chunk_has_insert_hooks = chunk.__has_insert_hooks - local place = chunk_entity_count + 1 chunk.__entity_count = place @@ -1722,7 +1731,7 @@ local function __spawn_entity_at(entity, chunk, fragment_list, fragment_count, c __structural_changes = __structural_changes + 1 end - if chunk_has_setup_hooks then + if chunk.__has_setup_hooks then for component_index = 1, chunk_component_count do local fragment = chunk_component_fragments[component_index] @@ -1754,27 +1763,33 @@ local function __spawn_entity_at(entity, chunk, fragment_list, fragment_count, c end end - if chunk_has_setup_hooks then + if chunk.__has_setup_hooks then for i = 1, fragment_count do local fragment = fragment_list[i] local component_index = chunk_component_indices[fragment] if component_index then - ---@type evolved.duplicate? - local fragment_duplicate = - __evolved_get(fragment, __DUPLICATE) + ---@type evolved.default?, evolved.duplicate? + local fragment_default, fragment_duplicate = + __evolved_get(fragment, __DEFAULT, __DUPLICATE) local new_component = component_list[i] + if new_component == nil then + new_component = fragment_default + end + if new_component ~= nil and fragment_duplicate then new_component = fragment_duplicate(new_component) end - if new_component ~= nil then - local component_storage = chunk_component_storages[component_index] - - component_storage[place] = new_component + if new_component == nil then + new_component = true end + + local component_storage = chunk_component_storages[component_index] + + component_storage[place] = new_component end end else @@ -1785,16 +1800,196 @@ local function __spawn_entity_at(entity, chunk, fragment_list, fragment_count, c if component_index then local new_component = component_list[i] - if new_component ~= nil then - local component_storage = chunk_component_storages[component_index] - - component_storage[place] = new_component + if new_component == nil then + new_component = true end + + local component_storage = chunk_component_storages[component_index] + + component_storage[place] = new_component end end end - if chunk_has_insert_hooks then + if chunk.__has_insert_hooks then + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + + for chunk_fragment_index = 1, chunk_fragment_count do + local fragment = chunk_fragment_list[chunk_fragment_index] + + ---@type evolved.set_hook?, evolved.insert_hook? + local fragment_on_set, fragment_on_insert = + __evolved_get(fragment, __ON_SET, __ON_INSERT) + + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + local new_component = component_storage[place] + + if fragment_on_set then + __defer_call_hook(fragment_on_set, entity, fragment, new_component) + end + + if fragment_on_insert then + __defer_call_hook(fragment_on_insert, entity, fragment, new_component) + end + else + if fragment_on_set then + __defer_call_hook(fragment_on_set, entity, fragment) + end + + if fragment_on_insert then + __defer_call_hook(fragment_on_insert, entity, fragment) + end + end + end + end +end + +---@param entity evolved.entity +---@param prefab? evolved.entity +---@param fragment_list evolved.fragment[] +---@param fragment_count integer +---@param component_list evolved.component[] +local function __spawn_entity_as(entity, prefab, fragment_list, fragment_count, component_list) + if __defer_depth <= 0 then + __error_fmt('spawn entity operations should be deferred') + end + + local prefab_index = prefab and prefab % 0x100000 + local prefab_chunk = prefab and __entity_chunks[prefab_index] + local prefab_place = prefab and __entity_places[prefab_index] + + local chunk = __chunk_with_fragment_list(prefab_chunk, fragment_list, fragment_count) + + if not chunk then + return + end + + local chunk_entity_list = chunk.__entity_list + local chunk_entity_count = chunk.__entity_count + + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + + local place = chunk_entity_count + 1 + chunk.__entity_count = place + + chunk_entity_list[place] = entity + + do + local entity_index = entity % 0x100000 + + __entity_chunks[entity_index] = chunk + __entity_places[entity_index] = place + + __structural_changes = __structural_changes + 1 + end + + if prefab_chunk then + local prefab_component_count = prefab_chunk.__component_count + local prefab_component_storages = prefab_chunk.__component_storages + local prefab_component_fragments = prefab_chunk.__component_fragments + + if prefab_chunk.__has_setup_hooks then + for prefab_component_index = 1, prefab_component_count do + local fragment = prefab_component_fragments[prefab_component_index] + + ---@type evolved.duplicate? + local fragment_duplicate = + __evolved_get(fragment, __DUPLICATE) + + local prefab_component_storage = prefab_component_storages[prefab_component_index] + local prefab_component = prefab_component_storage[prefab_place] + + local new_component = prefab_component + + if new_component ~= nil and fragment_duplicate then + new_component = fragment_duplicate(new_component) + end + + if new_component == nil then + new_component = true + end + + local entity_component_index = chunk_component_indices[fragment] + local entity_component_storage = chunk_component_storages[entity_component_index] + + entity_component_storage[place] = new_component + end + else + for prefab_component_index = 1, prefab_component_count do + local fragment = prefab_component_fragments[prefab_component_index] + + local prefab_component_storage = prefab_component_storages[prefab_component_index] + local prefab_component = prefab_component_storage[prefab_place] + + local new_component = prefab_component + + if new_component == nil then + new_component = true + end + + local entity_component_index = chunk_component_indices[fragment] + local entity_component_storage = chunk_component_storages[entity_component_index] + + entity_component_storage[place] = new_component + end + end + end + + if chunk.__has_setup_hooks then + for i = 1, fragment_count do + local fragment = fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + ---@type evolved.default?, evolved.duplicate? + local fragment_default, fragment_duplicate = + __evolved_get(fragment, __DEFAULT, __DUPLICATE) + + local new_component = component_list[i] + + if new_component == nil then + new_component = fragment_default + end + + if new_component ~= nil and fragment_duplicate then + new_component = fragment_duplicate(new_component) + end + + if new_component == nil then + new_component = true + end + + local entity_component_storage = chunk_component_storages[component_index] + + entity_component_storage[place] = new_component + end + end + else + for i = 1, fragment_count do + local fragment = fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local new_component = component_list[i] + + if new_component == nil then + new_component = true + end + + local entity_component_storage = chunk_component_storages[component_index] + + entity_component_storage[place] = new_component + end + end + end + + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -1852,9 +2047,6 @@ local function __spawn_entity_with(entity, chunk, fragment_list, fragment_count, local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages - local chunk_has_setup_hooks = chunk.__has_setup_hooks - local chunk_has_insert_hooks = chunk.__has_insert_hooks - local place = chunk_entity_count + 1 chunk.__entity_count = place @@ -1869,7 +2061,7 @@ local function __spawn_entity_with(entity, chunk, fragment_list, fragment_count, __structural_changes = __structural_changes + 1 end - if chunk_has_setup_hooks then + if chunk.__has_setup_hooks then for i = 1, fragment_count do local fragment = fragment_list[i] local component_index = chunk_component_indices[fragment] @@ -1917,7 +2109,7 @@ local function __spawn_entity_with(entity, chunk, fragment_list, fragment_count, end end - if chunk_has_insert_hooks then + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -3447,11 +3639,12 @@ local __defer_op = { batch_multi_remove = 12, spawn_entity_at = 13, - spawn_entity_with = 14, + spawn_entity_as = 14, + spawn_entity_with = 15, - call_hook = 15, + call_hook = 16, - __count = 15, + __count = 16, } ---@type table @@ -4119,6 +4312,57 @@ __defer_ops[__defer_op.spawn_entity_at] = function(bytes, index) return 5 end +---@param entity evolved.entity +---@param prefab? evolved.entity +---@param fragments evolved.fragment[] +---@param fragment_count integer +---@param components evolved.component[] +---@param component_count integer +__defer_spawn_entity_as = function(entity, prefab, fragments, fragment_count, components, component_count) + ---@type evolved.fragment[] + local fragment_list = __acquire_table(__table_pool_tag.fragment_list) + __lua_table_move(fragments, 1, fragment_count, 1, fragment_list) + + ---@type evolved.component[] + local component_list = __acquire_table(__table_pool_tag.component_list) + __lua_table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.spawn_entity_as + bytecode[length + 2] = entity + bytecode[length + 3] = prefab + bytecode[length + 4] = fragment_list + bytecode[length + 5] = fragment_count + bytecode[length + 6] = component_list + + __defer_length = length + 6 +end + +__defer_ops[__defer_op.spawn_entity_as] = function(bytes, index) + local entity = bytes[index + 0] + local prefab = bytes[index + 1] + local fragment_list = bytes[index + 2] + local fragment_count = bytes[index + 3] + local component_list = bytes[index + 4] + + if __debug_mode then + if prefab then __debug_fns.validate_prefab(prefab) end + __debug_fns.validate_fragment_list(fragment_list, fragment_count) + end + + __evolved_defer() + do + __spawn_entity_as(entity, prefab, fragment_list, fragment_count, component_list) + __release_table(__table_pool_tag.fragment_list, fragment_list) + __release_table(__table_pool_tag.component_list, component_list) + end + __evolved_commit() + + return 5 +end + ---@param entity evolved.entity ---@param chunk? evolved.chunk ---@param fragments evolved.fragment[] @@ -5860,6 +6104,44 @@ __evolved_spawn_at = function(chunk, fragments, components) return entity end +---@param prefab? evolved.entity +---@param fragments? evolved.fragment[] +---@param components? evolved.component[] +__evolved_spawn_as = function(prefab, fragments, components) + if not fragments then + fragments = __safe_tbls.__EMPTY_FRAGMENT_LIST + end + + if not components then + components = __safe_tbls.__EMPTY_COMPONENT_LIST + end + + local fragment_count = #fragments + local component_count = #components + + if __debug_mode then + if prefab then __debug_fns.validate_prefab(prefab) end + __debug_fns.validate_fragment_list(fragments, fragment_count) + end + + local entity = __acquire_id() + + if __defer_depth > 0 then + __defer_spawn_entity_as(entity, prefab, + fragments, fragment_count, + components, component_count) + return entity + end + + __evolved_defer() + do + __spawn_entity_as(entity, prefab, fragments, fragment_count, components) + end + __evolved_commit() + + return entity +end + ---@param fragments? evolved.fragment[] ---@param components? evolved.component[] ---@return evolved.entity entity @@ -7291,6 +7573,7 @@ evolved.execute = __evolved_execute evolved.process = __evolved_process evolved.spawn_at = __evolved_spawn_at +evolved.spawn_as = __evolved_spawn_as evolved.spawn_with = __evolved_spawn_with evolved.debug_mode = __evolved_debug_mode