From 189269fdfc0593c38046da54480cb850d88c2cf6 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 22 Apr 2025 05:47:16 +0700 Subject: [PATCH] aos variant of spawn/clone functions WIP --- README.md | 3 + develop/unbench.lua | 165 +++++++++++++++ develop/untests.lua | 279 +++++++++++++++++++++++++ evolved.lua | 480 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 920 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9eaaac5..64d693a 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_sta process :: system... -> () +spawn :: ? -> entity +clone :: entity -> ? -> entity + spawn_at :: chunk?, fragment[]?, component[]? -> entity spawn_as :: entity?, fragment[]?, component[]? -> entity spawn_with :: fragment[]?, component[]? -> entity diff --git a/develop/unbench.lua b/develop/unbench.lua index f9ac387..fb0f7e7 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -528,6 +528,171 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' +basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [F1] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [F1] = true, [F2] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [F1] = true, [F2] = true, [F3] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +print '----------------------------------------' + + +basics.describe_bench(string.format('create and destroy %d entities with 1 components / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [F1] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [F1] = true, [F2] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [F1] = true, [F2] = true, [F3] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [F1] = true, [F2] = true, [F3] = true, [F4] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +print '----------------------------------------' + basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn_at', N), ---@param entities evolved.id[] function(entities) diff --git a/develop/untests.lua b/develop/untests.lua index 2fb1158..e151f97 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -6688,3 +6688,282 @@ do assert(not evo.has(e3, f1) and evo.get(e3, f1) == nil) end end + +do + local f1, f2 = evo.id(2) + + do + local e = evo.spawn() + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn({}) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn({ [f1] = 1 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 1) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + end + + do + local e = evo.spawn({ [f1] = 1, [f2] = 2 }) + assert(evo.is_alive(e) and not evo.is_empty(e)) + assert(evo.has(e, f1) and evo.get(e, f1) == 1) + assert(evo.has(e, f2) and evo.get(e, f2) == 2) + end +end + +do + local f1, f2, f3 = evo.id(3) + + do + local p = evo.spawn() + + local e1 = evo.clone(p) + assert(evo.is_alive(e1) and evo.is_empty(e1)) + + local e2 = evo.clone(p, {}) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + + local e3 = evo.clone(p, { [f1] = 11 }) + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 11) + assert(not evo.has(e3, f2) and evo.get(e3, f2) == nil) + + local e4 = evo.clone(p, { [f1] = 11, [f2] = 22 }) + assert(evo.is_alive(e4) and not evo.is_empty(e4)) + assert(evo.has(e4, f1) and evo.get(e4, f1) == 11) + assert(evo.has(e4, f2) and evo.get(e4, f2) == 22) + + local e5 = evo.clone(p, { [f3] = 33 }) + assert(evo.is_alive(e5) and not evo.is_empty(e5)) + assert(not evo.has(e5, f1) and evo.get(e5, f1) == nil) + assert(not evo.has(e5, f2) and evo.get(e5, f2) == nil) + assert(evo.has(e5, f3) and evo.get(e5, f3) == 33) + + local e6 = evo.clone(p, { [f1] = 11, [f2] = 22, [f3] = 33 }) + assert(evo.is_alive(e6) and not evo.is_empty(e6)) + assert(evo.has(e6, f1) and evo.get(e6, f1) == 11) + assert(evo.has(e6, f2) and evo.get(e6, f2) == 22) + assert(evo.has(e6, f3) and evo.get(e6, f3) == 33) + end + + do + local p = evo.spawn({ [f1] = 1 }) + + local e1 = evo.clone(p) + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 1) + assert(not evo.has(e1, f2) and evo.get(e1, f2) == nil) + + local e2 = evo.clone(p, {}) + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 1) + assert(not evo.has(e2, f2) and evo.get(e2, f2) == nil) + + local e3 = evo.clone(p, { [f1] = 11 }) + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 11) + assert(not evo.has(e3, f2) and evo.get(e3, f2) == nil) + + local e4 = evo.clone(p, { [f2] = 22 }) + assert(evo.is_alive(e4) and not evo.is_empty(e4)) + assert(evo.has(e4, f1) and evo.get(e4, f1) == 1) + assert(evo.has(e4, f2) and evo.get(e4, f2) == 22) + + local e5 = evo.clone(p, { [f1] = 11, [f2] = 22 }) + assert(evo.is_alive(e5) and not evo.is_empty(e5)) + assert(evo.has(e5, f1) and evo.get(e5, f1) == 11) + assert(evo.has(e5, f2) and evo.get(e5, f2) == 22) + + local e6 = evo.clone(p, { [f3] = 33 }) + assert(evo.is_alive(e6) and not evo.is_empty(e6)) + assert(evo.has(e6, f1) and evo.get(e6, f1) == 1) + assert(not evo.has(e6, f2) and evo.get(e6, f2) == nil) + assert(evo.has(e6, f3) and evo.get(e6, f3) == 33) + + local e7 = evo.clone(p, { [f1] = 11, [f2] = 22, [f3] = 33 }) + assert(evo.is_alive(e7) and not evo.is_empty(e7)) + assert(evo.has(e7, f1) and evo.get(e7, f1) == 11) + assert(evo.has(e7, f2) and evo.get(e7, f2) == 22) + assert(evo.has(e7, f3) and evo.get(e7, f3) == 33) + end + + do + local p = evo.spawn({ [f1] = 1, [f2] = 2 }) + + local e1 = evo.clone(p) + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 1) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 2) + + local e2 = evo.clone(p, {}) + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 1) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 2) + + local e3 = evo.clone(p, { [f1] = 11 }) + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 11) + assert(evo.has(e3, f2) and evo.get(e3, f2) == 2) + + local e4 = evo.clone(p, { [f2] = 22 }) + assert(evo.is_alive(e4) and not evo.is_empty(e4)) + assert(evo.has(e4, f1) and evo.get(e4, f1) == 1) + assert(evo.has(e4, f2) and evo.get(e4, f2) == 22) + + local e5 = evo.clone(p, { [f1] = 11, [f2] = 22 }) + assert(evo.is_alive(e5) and not evo.is_empty(e5)) + assert(evo.has(e5, f1) and evo.get(e5, f1) == 11) + assert(evo.has(e5, f2) and evo.get(e5, f2) == 22) + + local e6 = evo.clone(p, { [f3] = 33 }) + assert(evo.is_alive(e6) and not evo.is_empty(e6)) + assert(evo.has(e6, f1) and evo.get(e6, f1) == 1) + assert(evo.has(e6, f2) and evo.get(e6, f2) == 2) + assert(evo.has(e6, f3) and evo.get(e6, f3) == 33) + + local e7 = evo.clone(p, { [f1] = 11, [f2] = 22, [f3] = 33 }) + assert(evo.is_alive(e7) and not evo.is_empty(e7)) + assert(evo.has(e7, f1) and evo.get(e7, f1) == 11) + assert(evo.has(e7, f2) and evo.get(e7, f2) == 22) + assert(evo.has(e7, f3) and evo.get(e7, f3) == 33) + end +end + +do + local f1, f2, f3 = evo.id(3) + + do + assert(evo.defer()) + + local e1 = evo.spawn() + local e2 = evo.spawn({}) + local e3 = evo.spawn({ [f1] = 11 }) + local e4 = evo.spawn({ [f1] = 11, [f2] = 22 }) + + 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.is_alive(e4) and evo.is_empty(e4)) + + assert(evo.commit()) + + 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 not evo.is_empty(e3)) + assert(evo.is_alive(e4) and not evo.is_empty(e4)) + + assert(evo.has(e3, f1) and evo.get(e3, f1) == 11) + assert(not evo.has(e3, f2) and evo.get(e3, f2) == nil) + + assert(evo.has(e4, f1) and evo.get(e4, f1) == 11) + assert(evo.has(e4, f2) and evo.get(e4, f2) == 22) + end + + do + local p1 = evo.spawn() + local p2 = evo.spawn({ [f1] = 1 }) + local p3 = evo.spawn({ [f1] = 1, [f2] = 2 }) + + assert(evo.defer()) + + local e1a = evo.clone(p1) + local e1b = evo.clone(p1, {}) + local e1c = evo.clone(p1, { [f1] = 11 }) + local e1d = evo.clone(p1, { [f2] = 22 }) + local e1e = evo.clone(p1, { [f1] = 11, [f2] = 22 }) + local e1f = evo.clone(p1, { [f3] = 33 }) + local e1g = evo.clone(p1, { [f1] = 11, [f2] = 22, [f3] = 33 }) + + local e2a = evo.clone(p2) + local e2b = evo.clone(p2, {}) + local e2c = evo.clone(p2, { [f1] = 11 }) + local e2d = evo.clone(p2, { [f2] = 22 }) + local e2e = evo.clone(p2, { [f1] = 11, [f2] = 22 }) + local e2f = evo.clone(p2, { [f3] = 33 }) + local e2g = evo.clone(p2, { [f1] = 11, [f2] = 22, [f3] = 33 }) + + local e3a = evo.clone(p3) + local e3b = evo.clone(p3, {}) + local e3c = evo.clone(p3, { [f1] = 11 }) + local e3d = evo.clone(p3, { [f2] = 22 }) + local e3e = evo.clone(p3, { [f1] = 11, [f2] = 22 }) + local e3f = evo.clone(p3, { [f3] = 33 }) + local e3g = evo.clone(p3, { [f1] = 11, [f2] = 22, [f3] = 33 }) + + assert(evo.is_alive_all(e1a, e1b, e1c, e1d, e1e, e1f, e1g)) + assert(evo.is_alive_all(e2a, e2b, e2c, e2d, e2e, e2f, e2g)) + assert(evo.is_alive_all(e3a, e3b, e3c, e3d, e3e, e3f, e3g)) + + assert(evo.is_empty_all(e1a, e1b, e1c, e1d, e1e, e1f, e1g)) + assert(evo.is_empty_all(e2a, e2b, e2c, e2d, e2e, e2f, e2g)) + assert(evo.is_empty_all(e3a, e3b, e3c, e3d, e3e, e3f, e3g)) + + assert(evo.commit()) + + assert(not evo.has(e1a, f1) and not evo.has(e1a, f2)) + assert(not evo.has(e1b, f1) and not evo.has(e1b, f2)) + + assert(evo.has(e1c, f1) and evo.get(e1c, f1) == 11 and not evo.has(e1c, f2)) + assert(not evo.has(e1d, f1) and evo.has(e1d, f2) and evo.get(e1d, f2) == 22) + + assert(not evo.has(e1f, f1) and evo.get(e1f, f1) == nil) + assert(not evo.has(e1f, f2) and evo.get(e1f, f2) == nil) + assert(evo.has(e1f, f3) and evo.get(e1f, f3) == 33) + + assert(evo.has(e1g, f1) and evo.get(e1g, f1) == 11) + assert(evo.has(e1g, f2) and evo.get(e1g, f2) == 22) + assert(evo.has(e1g, f3) and evo.get(e1g, f3) == 33) + + assert(evo.has(e2a, f1) and evo.get(e2a, f1) == 1) + assert(not evo.has(e2a, f2) and evo.get(e2a, f2) == nil) + + assert(evo.has(e2b, f1) and evo.get(e2b, f1) == 1) + assert(not evo.has(e2b, f2) and evo.get(e2b, f2) == nil) + + assert(evo.has(e2c, f1) and evo.get(e2c, f1) == 11) + assert(not evo.has(e2c, f2) and evo.get(e2c, f2) == nil) + + assert(evo.has(e2d, f1) and evo.get(e2d, f1) == 1) + assert(evo.has(e2d, f2) and evo.get(e2d, f2) == 22) + + assert(evo.has(e2e, f1) and evo.get(e2e, f1) == 11) + assert(evo.has(e2e, f2) and evo.get(e2e, f2) == 22) + + assert(evo.has(e2f, f1) and evo.get(e2f, f1) == 1) + assert(not evo.has(e2f, f2) and evo.get(e2f, f2) == nil) + assert(evo.has(e2f, f3) and evo.get(e2f, f3) == 33) + + assert(evo.has(e2g, f1) and evo.get(e2g, f1) == 11) + assert(evo.has(e2g, f2) and evo.get(e2g, f2) == 22) + assert(evo.has(e2g, f3) and evo.get(e2g, f3) == 33) + + assert(evo.has(e3a, f1) and evo.get(e3a, f1) == 1) + assert(evo.has(e3a, f2) and evo.get(e3a, f2) == 2) + + assert(evo.has(e3b, f1) and evo.get(e3b, f1) == 1) + assert(evo.has(e3b, f2) and evo.get(e3b, f2) == 2) + + assert(evo.has(e3c, f1) and evo.get(e3c, f1) == 11) + assert(evo.has(e3c, f2) and evo.get(e3c, f2) == 2) + + assert(evo.has(e3d, f1) and evo.get(e3d, f1) == 1) + assert(evo.has(e3d, f2) and evo.get(e3d, f2) == 22) + + assert(evo.has(e3e, f1) and evo.get(e3e, f1) == 11) + assert(evo.has(e3e, f2) and evo.get(e3e, f2) == 22) + + assert(evo.has(e3f, f1) and evo.get(e3f, f1) == 1) + assert(evo.has(e3f, f2) and evo.get(e3f, f2) == 2) + assert(evo.has(e3f, f3) and evo.get(e3f, f3) == 33) + + assert(evo.has(e3g, f1) and evo.get(e3g, f1) == 11) + assert(evo.has(e3g, f2) and evo.get(e3g, f2) == 22) + assert(evo.has(e3g, f3) and evo.get(e3g, f3) == 33) + end +end diff --git a/evolved.lua b/evolved.lua index 27e3652..79a104a 100644 --- a/evolved.lua +++ b/evolved.lua @@ -339,8 +339,9 @@ local __table_pool_tag = { entity_list = 7, fragment_set = 8, fragment_list = 9, - component_list = 10, - __count = 10, + component_map = 10, + component_list = 11, + __count = 11, } ---@class (exact) evolved.table_pool @@ -662,6 +663,11 @@ local __safe_tbls = { __newindex = function() __error_fmt('attempt to modify empty fragment list') end }), + ---@type table + __EMPTY_COMPONENT_MAP = __lua_setmetatable({}, { + __newindex = function() __error_fmt('attempt to modify empty component map') end + }), + ---@type evolved.component[] __EMPTY_COMPONENT_LIST = __lua_setmetatable({}, { __newindex = function() __error_fmt('attempt to modify empty component list') end @@ -716,6 +722,9 @@ local __evolved_execute local __evolved_process +local __evolved_spawn +local __evolved_clone + local __evolved_spawn_at local __evolved_spawn_as local __evolved_spawn_with @@ -1073,6 +1082,13 @@ function __debug_fns.validate_fragment_list(fragment_list, fragment_count) end end +---@param components table +function __debug_fns.validate_component_map(components) + for fragment in __lua_next, components do + __debug_fns.validate_fragment(fragment) + end +end + ---@param query evolved.query function __debug_fns.validate_query(query) local query_index = query % 0x100000 @@ -1301,6 +1317,18 @@ local function __chunk_with_fragment_list(chunk, fragment_list, fragment_count) return chunk end +---@param chunk? evolved.chunk +---@param components table +---@return evolved.chunk? +---@nodiscard +local function __chunk_with_component_map(chunk, components) + for fragment in __lua_next, components do + chunk = __chunk_with_fragment(chunk, fragment) + end + + return chunk +end + ---@param chunk? evolved.chunk ---@param fragment evolved.fragment ---@return evolved.chunk? @@ -1433,6 +1461,27 @@ local function __chunk_fragment_list(fragment_list, fragment_count) return chunk end +---@param components table +---@return evolved.chunk? +---@nodiscard +local function __chunk_component_map(components) + local root_fragment = __lua_next(components) + + if not root_fragment then + return + end + + local chunk = __root_chunks[root_fragment] + or __chunk_with_fragment(nil, root_fragment) + + for child_fragment in __lua_next, components, root_fragment do + chunk = chunk.__with_fragment_edges[child_fragment] + or __chunk_with_fragment(chunk, child_fragment) + end + + return chunk +end + --- --- --- @@ -1640,6 +1689,9 @@ local __defer_batch_remove local __defer_batch_clear local __defer_batch_destroy +local __defer_spawn_entity +local __defer_clone_entity + local __defer_spawn_entity_at local __defer_spawn_entity_as local __defer_spawn_entity_with @@ -1703,6 +1755,281 @@ local function __detach_all_entities(chunk) chunk.__entity_count = 0 end +---@param entity evolved.entity +---@param components table +local function __spawn_entity(entity, components) + if __defer_depth <= 0 then + __error_fmt('spawn entity operations should be deferred') + end + + local chunk = __chunk_component_map(components) + + 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 chunk.__has_setup_hooks then + for fragment, component in __lua_next, components do + local component_index = chunk_component_indices[fragment] + + if component_index then + ---@type evolved.duplicate? + local fragment_duplicate = + __evolved_get(fragment, __DUPLICATE) + + local new_component = 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 component_storage = chunk_component_storages[component_index] + + component_storage[place] = new_component + end + end + else + for fragment, component in __lua_next, components do + local component_index = chunk_component_indices[fragment] + + if component_index then + local new_component = component + + local component_storage = chunk_component_storages[component_index] + + 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 + + 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 components table +local function __clone_entity(entity, prefab, components) + if __defer_depth <= 0 then + __error_fmt('clone entity operations should be deferred') + end + + local prefab_index = prefab % 0x100000 + local prefab_chunk = __entity_chunks[prefab_index] + local prefab_place = __entity_places[prefab_index] + + local chunk = __chunk_with_component_map(prefab_chunk, components) + + 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 component_index = chunk_component_indices[fragment] + local component_storage = chunk_component_storages[component_index] + + 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 component_index = chunk_component_indices[fragment] + local component_storage = chunk_component_storages[component_index] + + component_storage[place] = new_component + end + end + end + + if chunk.__has_setup_hooks then + for fragment, component in __lua_next, components do + local component_index = chunk_component_indices[fragment] + + if component_index then + ---@type evolved.duplicate? + local fragment_duplicate = + __evolved_get(fragment, __DUPLICATE) + + local new_component = 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 component_storage = chunk_component_storages[component_index] + + component_storage[place] = new_component + end + end + else + for fragment, component in __lua_next, components do + local component_index = chunk_component_indices[fragment] + + if component_index then + local new_component = component + + local component_storage = chunk_component_storages[component_index] + + 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 + + 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 chunk? evolved.chunk ---@param fragment_list evolved.fragment[] @@ -2999,13 +3326,16 @@ local __defer_op = { batch_clear = 7, batch_destroy = 8, - spawn_entity_at = 9, - spawn_entity_as = 10, - spawn_entity_with = 11, + spawn_entity = 9, + clone_entity = 10, - call_hook = 12, + spawn_entity_at = 11, + spawn_entity_as = 12, + spawn_entity_with = 13, - __count = 12, + call_hook = 14, + + __count = 14, } ---@type table @@ -3492,6 +3822,85 @@ __defer_ops[__defer_op.batch_destroy] = function(bytes, index) return 1 + argument_count end +---@param entity evolved.entity +---@param components table +function __defer_spawn_entity(entity, components) + ---@type table + local component_map = __acquire_table(__table_pool_tag.component_map) + + for fragment, component in __lua_next, components do + component_map[fragment] = component + end + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.spawn_entity + bytecode[length + 2] = entity + bytecode[length + 3] = component_map + + __defer_length = length + 3 +end + +__defer_ops[__defer_op.spawn_entity] = function(bytes, index) + local entity = bytes[index + 0] + local component_map = bytes[index + 1] + + if __debug_mode then + __debug_fns.validate_component_map(component_map) + end + + __evolved_defer() + do + __spawn_entity(entity, component_map) + __release_table(__table_pool_tag.component_map, component_map) + end + __evolved_commit() + + return 2 +end + +---@param entity evolved.entity +---@param prefab evolved.entity +---@param components table +function __defer_clone_entity(entity, prefab, components) + ---@type table + local component_map = __acquire_table(__table_pool_tag.component_map) + + for fragment, component in __lua_next, components do + component_map[fragment] = component + end + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.clone_entity + bytecode[length + 2] = entity + bytecode[length + 3] = prefab + bytecode[length + 4] = component_map + + __defer_length = length + 4 +end + +__defer_ops[__defer_op.clone_entity] = function(bytes, index) + local entity = bytes[index + 0] + local prefab = bytes[index + 1] + local component_map = bytes[index + 2] + + if __debug_mode then + __debug_fns.validate_component_map(component_map) + end + + __evolved_defer() + do + __clone_entity(entity, prefab, component_map) + __release_table(__table_pool_tag.component_map, component_map) + end + __evolved_commit() + + return 3 +end + ---@param entity evolved.entity ---@param chunk? evolved.chunk ---@param fragments evolved.fragment[] @@ -4755,6 +5164,60 @@ end --- --- +---@param components? table +---@return evolved.entity +function __evolved_spawn(components) + if not components then + components = __safe_tbls.__EMPTY_COMPONENT_MAP + end + + if __debug_mode then + __debug_fns.validate_component_map(components) + end + + local entity = __acquire_id() + + if __defer_depth > 0 then + __defer_spawn_entity(entity, components) + else + __evolved_defer() + do + __spawn_entity(entity, components) + end + __evolved_commit() + end + + return entity +end + +---@param prefab evolved.entity +---@param components? table +---@return evolved.entity +function __evolved_clone(prefab, components) + if not components then + components = __safe_tbls.__EMPTY_COMPONENT_MAP + end + + if __debug_mode then + __debug_fns.validate_prefab(prefab) + __debug_fns.validate_component_map(components) + end + + local entity = __acquire_id() + + if __defer_depth > 0 then + __defer_clone_entity(entity, prefab, components) + else + __evolved_defer() + do + __clone_entity(entity, prefab, components) + end + __evolved_commit() + end + + return entity +end + ---@param chunk? evolved.chunk ---@param fragments? evolved.fragment[] ---@param components? evolved.component[] @@ -5862,6 +6325,9 @@ evolved.execute = __evolved_execute evolved.process = __evolved_process +evolved.spawn = __evolved_spawn +evolved.clone = __evolved_clone + evolved.spawn_at = __evolved_spawn_at evolved.spawn_as = __evolved_spawn_as evolved.spawn_with = __evolved_spawn_with