From 3a3abbd2fd2e72bf1fdca17b87ab25d6e4da507a Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 12 Jan 2026 15:11:59 +0700 Subject: [PATCH 01/13] dummy realloc fragments --- evolved.lua | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/evolved.lua b/evolved.lua index 2e90401..e47f21c 100644 --- a/evolved.lua +++ b/evolved.lua @@ -40,6 +40,9 @@ local evolved = { ---@alias evolved.default evolved.component ---@alias evolved.duplicate fun(component: evolved.component): evolved.component +---@alias evolved.realloc fun(storage?: evolved.storage, old_size: integer, new_size: integer): evolved.storage +---@alias evolved.compmove fun(destination: evolved.storage, source: evolved.storage, count: integer) + ---@alias evolved.execute fun( --- chunk: evolved.chunk, --- entity_list: evolved.entity[], @@ -168,6 +171,8 @@ local __structural_changes = 0 ---@type integer ---@field package __component_fragments evolved.fragment[] ---@field package __component_defaults evolved.default[] ---@field package __component_duplicates evolved.duplicate[] +---@field package __component_reallocs evolved.realloc[] +---@field package __component_compmoves evolved.compmove[] ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table ---@field package __with_required_fragments? evolved.chunk @@ -971,6 +976,9 @@ local __INTERNAL = __acquire_id() local __DEFAULT = __acquire_id() local __DUPLICATE = __acquire_id() +local __REALLOC = __acquire_id() +local __COMPMOVE = __acquire_id() + local __PREFAB = __acquire_id() local __DISABLED = __acquire_id() @@ -1231,6 +1239,8 @@ function __new_chunk(chunk_parent, chunk_fragment) __component_fragments = {}, __component_defaults = {}, __component_duplicates = {}, + __component_reallocs = {}, + __component_compmoves = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, __with_required_fragments = nil, @@ -1425,14 +1435,16 @@ function __update_chunk_storages(chunk) local component_fragments = chunk.__component_fragments local component_defaults = chunk.__component_defaults local component_duplicates = chunk.__component_duplicates + local component_reallocs = chunk.__component_reallocs + local component_compmoves = chunk.__component_compmoves for fragment_index = 1, fragment_count do local fragment = fragment_list[fragment_index] local component_index = component_indices[fragment] - ---@type evolved.default?, evolved.duplicate? - local fragment_default, fragment_duplicate = - __evolved_get(fragment, __DEFAULT, __DUPLICATE) + ---@type evolved.default?, evolved.duplicate?, evolved.realloc?, evolved.compmove? + local fragment_default, fragment_duplicate, fragment_realloc, fragment_compmove = + __evolved_get(fragment, __DEFAULT, __DUPLICATE, __REALLOC, __COMPMOVE) local is_fragment_tag = __evolved_has(fragment, __TAG) @@ -1442,12 +1454,16 @@ function __update_chunk_storages(chunk) local last_component_fragment = component_fragments[component_count] local last_component_default = component_defaults[component_count] local last_component_duplicate = component_duplicates[component_count] + local last_component_realloc = component_reallocs[component_count] + local last_component_compmove = component_compmoves[component_count] component_indices[last_component_fragment] = component_index component_storages[component_index] = last_component_storage component_fragments[component_index] = last_component_fragment component_defaults[component_index] = last_component_default component_duplicates[component_index] = last_component_duplicate + component_reallocs[component_index] = last_component_realloc + component_compmoves[component_index] = last_component_compmove end component_indices[fragment] = nil @@ -1455,6 +1471,8 @@ function __update_chunk_storages(chunk) component_fragments[component_count] = nil component_defaults[component_count] = nil component_duplicates[component_count] = nil + component_reallocs[component_count] = nil + component_compmoves[component_count] = nil component_count = component_count - 1 chunk.__component_count = component_count @@ -1470,6 +1488,8 @@ function __update_chunk_storages(chunk) component_fragments[component_storage_index] = fragment component_defaults[component_storage_index] = fragment_default component_duplicates[component_storage_index] = fragment_duplicate + component_reallocs[component_storage_index] = fragment_realloc + component_compmoves[component_storage_index] = fragment_compmove if fragment_duplicate then for place = 1, entity_count do @@ -1488,6 +1508,8 @@ function __update_chunk_storages(chunk) elseif component_index then component_defaults[component_index] = fragment_default component_duplicates[component_index] = fragment_duplicate + component_reallocs[component_index] = fragment_realloc + component_compmoves[component_index] = fragment_compmove end end end @@ -2239,6 +2261,8 @@ function __spawn_entity(chunk, entity, components) local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates + local chunk_component_reallocs = chunk.__component_reallocs + local chunk_component_compmoves = chunk.__component_compmoves local place = chunk_entity_count + 1 @@ -2358,6 +2382,8 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates + local chunk_component_reallocs = chunk.__component_reallocs + local chunk_component_compmoves = chunk.__component_compmoves local b_place = chunk_entity_count + 1 local e_place = chunk_entity_count + entity_count @@ -2509,6 +2535,8 @@ function __clone_entity(prefab, entity, components) local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates + local chunk_component_reallocs = chunk.__component_reallocs + local chunk_component_compmoves = chunk.__component_compmoves local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages @@ -2655,6 +2683,8 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates + local chunk_component_reallocs = chunk.__component_reallocs + local chunk_component_compmoves = chunk.__component_compmoves local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages @@ -6051,6 +6081,18 @@ function __builder_mt:duplicate(duplicate) return self:set(__DUPLICATE, duplicate) end +---@param realloc evolved.realloc +---@return evolved.builder builder +function __builder_mt:realloc(realloc) + return self:set(__REALLOC, realloc) +end + +---@param compmove evolved.compmove +---@return evolved.builder builder +function __builder_mt:compmove(compmove) + return self:set(__COMPMOVE, compmove) +end + ---@return evolved.builder builder function __builder_mt:prefab() return self:set(__PREFAB) @@ -6263,6 +6305,12 @@ __evolved_set(__DEFAULT, __ON_REMOVE, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_INSERT, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_REMOVE, __update_major_chunks) +__evolved_set(__REALLOC, __ON_INSERT, __update_major_chunks) +__evolved_set(__REALLOC, __ON_REMOVE, __update_major_chunks) + +__evolved_set(__COMPMOVE, __ON_INSERT, __update_major_chunks) +__evolved_set(__COMPMOVE, __ON_REMOVE, __update_major_chunks) + --- --- --- @@ -6279,6 +6327,9 @@ __evolved_set(__INTERNAL, __NAME, 'INTERNAL') __evolved_set(__DEFAULT, __NAME, 'DEFAULT') __evolved_set(__DUPLICATE, __NAME, 'DUPLICATE') +__evolved_set(__REALLOC, __NAME, 'REALLOC') +__evolved_set(__COMPMOVE, __NAME, 'COMPMOVE') + __evolved_set(__PREFAB, __NAME, 'PREFAB') __evolved_set(__DISABLED, __NAME, 'DISABLED') @@ -6320,6 +6371,9 @@ __evolved_set(__INTERNAL, __INTERNAL) __evolved_set(__DEFAULT, __INTERNAL) __evolved_set(__DUPLICATE, __INTERNAL) +__evolved_set(__REALLOC, __INTERNAL) +__evolved_set(__COMPMOVE, __INTERNAL) + __evolved_set(__PREFAB, __INTERNAL) __evolved_set(__DISABLED, __INTERNAL) @@ -6683,6 +6737,9 @@ evolved.INTERNAL = __INTERNAL evolved.DEFAULT = __DEFAULT evolved.DUPLICATE = __DUPLICATE +evolved.REALLOC = __REALLOC +evolved.COMPMOVE = __COMPMOVE + evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED From c9bfb26748d7fec051972d60759adcb09f40b0bf Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 12 Jan 2026 16:29:44 +0700 Subject: [PATCH 02/13] dummy chunk entity capacity --- evolved.lua | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/evolved.lua b/evolved.lua index e47f21c..c7cdb30 100644 --- a/evolved.lua +++ b/evolved.lua @@ -161,6 +161,7 @@ local __structural_changes = 0 ---@type integer ---@field package __child_count integer ---@field package __entity_list evolved.entity[] ---@field package __entity_count integer +---@field package __entity_capacity integer ---@field package __fragment evolved.fragment ---@field package __fragment_set table ---@field package __fragment_list evolved.fragment[] @@ -1152,6 +1153,8 @@ local __clone_entity local __multi_clone_entity local __purge_chunk +local __expand_chunk +local __shrink_chunk local __clear_chunk_list local __destroy_entity_list local __destroy_fragment_list @@ -1229,6 +1232,7 @@ function __new_chunk(chunk_parent, chunk_fragment) __child_count = 0, __entity_list = {}, __entity_count = 0, + __entity_capacity = 0, __fragment = chunk_fragment, __fragment_set = chunk_fragment_set, __fragment_list = chunk_fragment_list, @@ -1425,6 +1429,7 @@ end ---@param chunk evolved.chunk function __update_chunk_storages(chunk) local entity_count = chunk.__entity_count + local entity_capacity = chunk.__entity_capacity local fragment_list = chunk.__fragment_list local fragment_count = chunk.__fragment_count @@ -2254,6 +2259,7 @@ function __spawn_entity(chunk, entity, components) local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count + local chunk_entity_capacity = chunk.__entity_capacity local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices @@ -2266,6 +2272,10 @@ function __spawn_entity(chunk, entity, components) local place = chunk_entity_count + 1 + if place > chunk_entity_capacity then + __expand_chunk(chunk, place) + end + do chunk.__entity_count = place __structural_changes = __structural_changes + 1 @@ -2375,6 +2385,7 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count + local chunk_entity_capacity = chunk.__entity_capacity local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices @@ -2388,6 +2399,10 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local b_place = chunk_entity_count + 1 local e_place = chunk_entity_count + entity_count + if e_place > chunk_entity_capacity then + __expand_chunk(chunk, e_place) + end + do chunk.__entity_count = e_place __structural_changes = __structural_changes + 1 @@ -2528,6 +2543,7 @@ function __clone_entity(prefab, entity, components) local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count + local chunk_entity_capacity = chunk.__entity_capacity local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices @@ -2543,6 +2559,10 @@ function __clone_entity(prefab, entity, components) local place = chunk_entity_count + 1 + if place > chunk_entity_capacity then + __expand_chunk(chunk, place) + end + do chunk.__entity_count = place __structural_changes = __structural_changes + 1 @@ -2676,6 +2696,7 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count + local chunk_entity_capacity = chunk.__entity_capacity local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices @@ -2692,6 +2713,10 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local b_place = chunk_entity_count + 1 local e_place = chunk_entity_count + entity_count + if e_place > chunk_entity_capacity then + __expand_chunk(chunk, e_place) + end + do chunk.__entity_count = e_place __structural_changes = __structural_changes + 1 @@ -2863,6 +2888,15 @@ function __purge_chunk(chunk) chunk.__unreachable_or_collected = true end +---@param chunk evolved.chunk +---@param min_capacity integer +function __expand_chunk(chunk, min_capacity) +end + +---@param chunk evolved.chunk +function __shrink_chunk(chunk) +end + ---@param chunk_list evolved.chunk[] ---@param chunk_count integer function __clear_chunk_list(chunk_list, chunk_count) @@ -3184,6 +3218,7 @@ function __chunk_set(old_chunk, fragment, component) local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count + local new_entity_capacity = new_chunk.__entity_capacity local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages @@ -3201,6 +3236,10 @@ function __chunk_set(old_chunk, fragment, component) local sum_entity_count = old_entity_count + new_entity_count + if sum_entity_count > new_entity_capacity then + __expand_chunk(new_chunk, sum_entity_count) + end + if new_entity_count == 0 then old_chunk.__entity_list, new_chunk.__entity_list = new_entity_list, old_entity_list @@ -3507,6 +3546,7 @@ function __chunk_remove(old_chunk, ...) if new_chunk then local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count + local new_entity_capacity = new_chunk.__entity_capacity local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages @@ -3514,6 +3554,10 @@ function __chunk_remove(old_chunk, ...) local sum_entity_count = old_entity_count + new_entity_count + if sum_entity_count > new_entity_capacity then + __expand_chunk(new_chunk, sum_entity_count) + end + if new_entity_count == 0 then old_chunk.__entity_list, new_chunk.__entity_list = new_entity_list, old_entity_list @@ -4681,6 +4725,7 @@ function __evolved_set(entity, fragment, component) local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count + local new_entity_capacity = new_chunk.__entity_capacity local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages @@ -4697,9 +4742,13 @@ function __evolved_set(entity, fragment, component) end local new_place = new_entity_count + 1 - new_chunk.__entity_count = new_place + + if new_place > new_entity_capacity then + __expand_chunk(new_chunk, new_place) + end new_entity_list[new_place] = entity + new_chunk.__entity_count = new_place if old_chunk then local old_component_count = old_chunk.__component_count @@ -4887,15 +4936,20 @@ function __evolved_remove(entity, ...) if new_chunk then local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count + local new_entity_capacity = new_chunk.__entity_capacity local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments local new_place = new_entity_count + 1 - new_chunk.__entity_count = new_place + + if new_place > new_entity_capacity then + __expand_chunk(new_chunk, new_place) + end new_entity_list[new_place] = entity + new_chunk.__entity_count = new_place for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] @@ -5499,6 +5553,8 @@ function __evolved_collect_garbage() if should_be_purged then __purge_chunk(postorder_chunk) + else + __shrink_chunk(postorder_chunk) end end From e75ddef3962db05ed873a58bf5eb9665bbd821a6 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 13 Jan 2026 01:14:50 +0700 Subject: [PATCH 03/13] first realloc/compmove imp --- develop/ROADMAP.md | 1 + develop/all.lua | 3 + develop/testing/realloc_tests.lua | 301 +++++++++++++++++++++++++++++ evolved.lua | 307 ++++++++++++++++++++---------- 4 files changed, 516 insertions(+), 96 deletions(-) create mode 100644 develop/testing/realloc_tests.lua diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index 75f88c5..b46e2fe 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -9,6 +9,7 @@ ## Thoughts +- We can create component storages on-demand rather than in advance - We should have a way to not copy components on deferred spawn/clone ## Known Issues diff --git a/develop/all.lua b/develop/all.lua index ca2350e..9615cde 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -8,6 +8,7 @@ require 'develop.testing.main_tests' require 'develop.testing.multi_spawn_tests' require 'develop.testing.name_tests' require 'develop.testing.process_with_tests' +require 'develop.testing.realloc_tests' require 'develop.testing.requires_fragment_tests' require 'develop.testing.spawn_tests' require 'develop.testing.system_as_query_tests' @@ -35,3 +36,5 @@ print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.requires_fuzz' print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.unique_fuzz' + +print 'All tests passed.' diff --git a/develop/testing/realloc_tests.lua b/develop/testing/realloc_tests.lua new file mode 100644 index 0000000..3863316 --- /dev/null +++ b/develop/testing/realloc_tests.lua @@ -0,0 +1,301 @@ +local evo = require 'evolved' + +---@type ffilib? +local ffi = (function() + local ffi_loader = package and package.preload and package.preload['ffi'] + local ffi = ffi_loader and ffi_loader() + return ffi +end)() + +if not ffi then + return +end + +local FLOAT_TYPEOF = ffi.typeof('float') +local FLOAT_SIZEOF = ffi.sizeof(FLOAT_TYPEOF) +local FLOAT_STORAGE_TYPEOF = ffi.typeof('$[?]', FLOAT_TYPEOF) + +local DOUBLE_TYPEOF = ffi.typeof('double') +local DOUBLE_SIZEOF = ffi.sizeof(DOUBLE_TYPEOF) +local DOUBLE_STORAGE_TYPEOF = ffi.typeof('$[?]', DOUBLE_TYPEOF) + +---@type evolved.realloc +local function float_realloc(old_storage, old_size, new_size) + local new_storage = ffi.new(FLOAT_STORAGE_TYPEOF, new_size + 1) + + if old_storage then + ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * FLOAT_SIZEOF) + end + + return new_storage +end + +---@type evolved.realloc +local function double_realloc(old_storage, old_size, new_size) + local new_storage = ffi.new(DOUBLE_STORAGE_TYPEOF, new_size + 1) + + if old_storage then + ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * DOUBLE_SIZEOF) + end + + return new_storage +end + +---@type evolved.compmove +local function double_compmove(src, f, e, t, dst) + ffi.copy(dst + t, src + f, (e - f + 1) * DOUBLE_SIZEOF) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + + local e1 = evo.builder():set(f1, 21):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + + local e2 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + + local e3 = evo.builder():set(f1, 84):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 84) + + evo.destroy(e1) + assert(not evo.has(e1, f1)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 84) + + evo.destroy(e3) + assert(not evo.has(e1, f1)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(not evo.has(e3, f1)) + + evo.destroy(e2) + assert(not evo.has(e1, f1)) + assert(not evo.has(e2, f1)) + assert(not evo.has(e3, f1)) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + local q1 = evo.builder():include(f1):build() + + do + local es, ec = {}, 10 + for i = 1, ec do es[i] = evo.spawn({ [f1] = i }) end + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == i) end + end + + do + local p = evo.builder():set(f1, 42):build() + local es, ec = {}, 10 + for i = 1, ec do es[i] = evo.clone(p) end + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) end + end + + do + local es1, ec1 = evo.multi_spawn(10, { [f1] = 42 }) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 42) end + + local es2, ec2 = evo.multi_spawn(20, { [f1] = 84 }) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 42) end + for i = 1, ec2 do assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 84) end + end + + do + local p = evo.builder():set(f1, 21):build() + + local es1, ec1 = evo.multi_clone(10, p) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 21) end + + local es2, ec2 = evo.multi_clone(20, p) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 21) end + for i = 1, ec2 do assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 21) end + end + + evo.batch_destroy(q1) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + local f2 = evo.builder():realloc(double_realloc):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local e = evo.builder():set(f1, 21):set(f2, 42):build() + assert(evo.has(e, f1) and evo.get(e, f1) == 21) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + + evo.remove(e, f1) + + assert(not evo.has(e, f1)) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + end + + do + local e = evo.builder():set(f1, 21):set(f2, 42):build() + assert(evo.has(e, f1) and evo.get(e, f1) == 21) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + + evo.clear(e) + + assert(not evo.has(e, f1)) + assert(not evo.has(e, f2)) + end + + do + local es, ec = evo.multi_spawn(10, { [f1] = 21, [f2] = 42 }) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_remove(q1, f1) + + local e12 = evo.builder():set(f1, 1):set(f2, 2):build() + assert(evo.has(e12, f1) and evo.get(e12, f1) == 1) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_set(q2, f1, 84) + + assert(evo.has(e12, f1) and evo.get(e12, f1) == 84) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 84) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_set(q2, f1, 21) + + assert(evo.has(e12, f1) and evo.get(e12, f1) == 21) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + end +end + +do + local f1 = evo.builder():realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es1, ec1 = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 1) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + local es2, ec2 = evo.multi_spawn(20, { [f1] = 3, [f2] = 4 }) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 1) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 3) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + + local e2 = evo.builder():set(f2, 42):build() + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + + evo.batch_remove(q1, f1) + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + + for i = 1, ec1 do + assert(not evo.has(es1[i], f1)) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(not evo.has(es2[i], f1)) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + + local e12 = evo.builder():set(f1, 21):set(f2, 42):build() + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + assert(evo.has(e12, f1) and evo.get(e12, f1) == 21) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 42) + + evo.batch_set(q2, f1, 84) + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + assert(evo.has(e12, f1) and evo.get(e12, f1) == 84) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 42) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 84) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 84) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + end +end + +do + local f1 = evo.builder():default(42):build() + + local es, ec = evo.multi_spawn(10, { [f1] = 21 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) end + + evo.set(f1, evo.TAG) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + + evo.remove(f1, evo.TAG) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) end +end + +do + local f1 = evo.builder():realloc(float_realloc):build() + + local e1 = evo.builder():set(f1, 3):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.set(f1, evo.REALLOC, double_realloc) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.remove(f1, evo.REALLOC) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.set(f1, evo.REALLOC, double_realloc) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + + local es, ec = evo.multi_spawn(20, { [f1] = 42 }) + + for i = 1, ec / 2 do + evo.destroy(es[ec - i + 1]) + end + + evo.collect_garbage() + + for i = 1, ec / 2 do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) + end +end diff --git a/evolved.lua b/evolved.lua index c7cdb30..ca5f11a 100644 --- a/evolved.lua +++ b/evolved.lua @@ -40,8 +40,8 @@ local evolved = { ---@alias evolved.default evolved.component ---@alias evolved.duplicate fun(component: evolved.component): evolved.component ----@alias evolved.realloc fun(storage?: evolved.storage, old_size: integer, new_size: integer): evolved.storage ----@alias evolved.compmove fun(destination: evolved.storage, source: evolved.storage, count: integer) +---@alias evolved.realloc fun(src?: evolved.storage, old_size: integer, new_size: integer): evolved.storage +---@alias evolved.compmove fun(src: evolved.storage, f: integer, e: integer, t: integer, dst: evolved.storage) ---@alias evolved.execute fun( --- chunk: evolved.chunk, @@ -192,6 +192,8 @@ local __structural_changes = 0 ---@type integer ---@field package __has_internal_major boolean ---@field package __has_internal_minors boolean ---@field package __has_internal_fragments boolean +---@field package __has_storage_reallocs boolean +---@field package __has_storage_compmoves boolean ---@field package __has_required_fragments boolean local __chunk_mt = {} __chunk_mt.__index = __chunk_mt @@ -1263,6 +1265,8 @@ function __new_chunk(chunk_parent, chunk_fragment) __has_internal_major = false, __has_internal_minors = false, __has_internal_fragments = false, + __has_storage_reallocs = false, + __has_storage_compmoves = false, __has_required_fragments = false, }, __chunk_mt) @@ -1351,6 +1355,12 @@ function __update_chunk_caches(chunk) local has_internal_minors = chunk_parent ~= nil and chunk_parent.__has_internal_fragments local has_internal_fragments = has_internal_major or has_internal_minors + local has_storage_reallocs = chunk_parent ~= nil and chunk_parent.__has_storage_reallocs + or __evolved_has(chunk_fragment, __REALLOC) + + local has_storage_compmoves = chunk_parent ~= nil and chunk_parent.__has_storage_compmoves + or __evolved_has(chunk_fragment, __COMPMOVE) + local has_required_fragments = false for chunk_fragment_index = 1, chunk_fragment_count do @@ -1391,6 +1401,9 @@ function __update_chunk_caches(chunk) chunk.__has_internal_minors = has_internal_minors chunk.__has_internal_fragments = has_internal_fragments + chunk.__has_storage_reallocs = has_storage_reallocs + chunk.__has_storage_compmoves = has_storage_compmoves + chunk.__has_required_fragments = has_required_fragments if has_required_fragments then @@ -1445,7 +1458,9 @@ function __update_chunk_storages(chunk) for fragment_index = 1, fragment_count do local fragment = fragment_list[fragment_index] + local component_index = component_indices[fragment] + local component_realloc = component_index and component_reallocs[component_index] ---@type evolved.default?, evolved.duplicate?, evolved.realloc?, evolved.compmove? local fragment_default, fragment_duplicate, fragment_realloc, fragment_compmove = @@ -1485,7 +1500,9 @@ function __update_chunk_storages(chunk) component_count = component_count + 1 chunk.__component_count = component_count - local component_storage = __lua_table_new(entity_count) + local component_storage = fragment_realloc + and fragment_realloc(nil, 0, entity_capacity) + or __lua_table_new(entity_capacity) local component_storage_index = component_count component_indices[fragment] = component_storage_index @@ -1511,6 +1528,33 @@ function __update_chunk_storages(chunk) end end elseif component_index then + if component_realloc ~= fragment_realloc then + local old_component_storage = component_storages[component_index] + + local new_component_storage = fragment_realloc + and fragment_realloc(nil, 0, entity_capacity) + or __lua_table_new(entity_capacity) + + if fragment_duplicate then + for place = 1, entity_count do + local new_component = old_component_storage[place] + if new_component == nil then new_component = fragment_default end + if new_component ~= nil then new_component = fragment_duplicate(new_component) end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + else + for place = 1, entity_count do + local new_component = old_component_storage[place] + if new_component == nil then new_component = fragment_default end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + end + + component_storages[component_index] = new_component_storage + end + component_defaults[component_index] = fragment_default component_duplicates[component_index] = fragment_duplicate component_reallocs[component_index] = fragment_realloc @@ -2186,27 +2230,16 @@ function __detach_entity(chunk, place) local component_count = chunk.__component_count local component_storages = chunk.__component_storages - if place == entity_count then - entity_list[entity_count] = nil - - for component_index = 1, component_count do - local component_storage = component_storages[component_index] - component_storage[entity_count] = nil - end - else + if place ~= entity_count then local last_entity = entity_list[entity_count] - local last_entity_primary = last_entity % 2 ^ 20 + entity_list[place] = last_entity + local last_entity_primary = last_entity % 2 ^ 20 __entity_places[last_entity_primary] = place - entity_list[place] = last_entity - entity_list[entity_count] = nil - for component_index = 1, component_count do local component_storage = component_storages[component_index] - local last_entity_component = component_storage[entity_count] - component_storage[place] = last_entity_component - component_storage[entity_count] = nil + component_storage[place] = component_storage[entity_count] end end @@ -2215,18 +2248,6 @@ end ---@param chunk evolved.chunk function __detach_all_entities(chunk) - local entity_list = chunk.__entity_list - - local component_count = chunk.__component_count - local component_storages = chunk.__component_storages - - __lua_table_clear(entity_list) - - for component_index = 1, component_count do - local component_storage = component_storages[component_index] - __lua_table_clear(component_storage) - end - chunk.__entity_count = 0 end @@ -2257,25 +2278,21 @@ function __spawn_entity(chunk, entity, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_entity_capacity = chunk.__entity_capacity - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local chunk_component_reallocs = chunk.__component_reallocs - local chunk_component_compmoves = chunk.__component_compmoves - local place = chunk_entity_count + 1 + local place = chunk.__entity_count + 1 - if place > chunk_entity_capacity then + if place > chunk.__entity_capacity then __expand_chunk(chunk, place) end + local chunk_entity_list = chunk.__entity_list + do chunk.__entity_count = place __structural_changes = __structural_changes + 1 @@ -2383,26 +2400,22 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_entity_capacity = chunk.__entity_capacity - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local chunk_component_reallocs = chunk.__component_reallocs - local chunk_component_compmoves = chunk.__component_compmoves - local b_place = chunk_entity_count + 1 - local e_place = chunk_entity_count + entity_count + local b_place = chunk.__entity_count + 1 + local e_place = b_place + entity_count - 1 - if e_place > chunk_entity_capacity then + if e_place > chunk.__entity_capacity then __expand_chunk(chunk, e_place) end + local chunk_entity_list = chunk.__entity_list + do chunk.__entity_count = e_place __structural_changes = __structural_changes + 1 @@ -2541,28 +2554,24 @@ function __clone_entity(prefab, entity, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_entity_capacity = chunk.__entity_capacity - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local chunk_component_reallocs = chunk.__component_reallocs - local chunk_component_compmoves = chunk.__component_compmoves local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages - local place = chunk_entity_count + 1 + local place = chunk.__entity_count + 1 - if place > chunk_entity_capacity then + if place > chunk.__entity_capacity then __expand_chunk(chunk, place) end + local chunk_entity_list = chunk.__entity_list + do chunk.__entity_count = place __structural_changes = __structural_changes + 1 @@ -2694,29 +2703,25 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_entity_capacity = chunk.__entity_capacity - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages local chunk_component_fragments = chunk.__component_fragments local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local chunk_component_reallocs = chunk.__component_reallocs - local chunk_component_compmoves = chunk.__component_compmoves local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages - local b_place = chunk_entity_count + 1 - local e_place = chunk_entity_count + entity_count + local b_place = chunk.__entity_count + 1 + local e_place = b_place + entity_count - 1 - if e_place > chunk_entity_capacity then + if e_place > chunk.__entity_capacity then __expand_chunk(chunk, e_place) end + local chunk_entity_list = chunk.__entity_list + do chunk.__entity_count = e_place __structural_changes = __structural_changes + 1 @@ -2891,10 +2896,104 @@ end ---@param chunk evolved.chunk ---@param min_capacity integer function __expand_chunk(chunk, min_capacity) + if __defer_depth <= 0 then + __error_fmt('this operation should be deferred') + end + + if min_capacity < 4 then + min_capacity = 4 + end + + local entity_count = chunk.__entity_count + + if min_capacity < entity_count then + min_capacity = entity_count + end + + local old_capacity = chunk.__entity_capacity + if old_capacity >= min_capacity then + -- no need to expand, the chunk is already large enough + return + end + + local new_capacity = old_capacity * 2 + + if new_capacity < min_capacity then + new_capacity = min_capacity + end + + if chunk.__has_storage_reallocs then + local component_count = chunk.__component_count + local component_storages = chunk.__component_storages + local component_reallocs = chunk.__component_reallocs + + for component_index = 1, component_count do + local component_realloc = component_reallocs[component_index] + + if component_realloc then + local old_component_storage = component_storages[component_index] + + local new_component_storage = component_realloc( + old_component_storage, old_capacity, new_capacity) + + component_storages[component_index] = new_component_storage + end + end + end + + chunk.__entity_capacity = new_capacity end ---@param chunk evolved.chunk function __shrink_chunk(chunk) + if __defer_depth <= 0 then + __error_fmt('this operation should be deferred') + end + + local min_capacity = 4 + + local entity_count = chunk.__entity_count + + if min_capacity < entity_count then + min_capacity = entity_count + end + + local old_capacity = chunk.__entity_capacity + if old_capacity <= min_capacity then + -- no need to shrink, the chunk is already small enough + return + end + + do + local entity_list = __lua_table_new(min_capacity) + __lua_table_move(chunk.__entity_list, 1, entity_count, 1, entity_list) + chunk.__entity_list = entity_list + end + + do + local component_count = chunk.__component_count + local component_storages = chunk.__component_storages + local component_reallocs = chunk.__component_reallocs + + for component_index = 1, component_count do + local component_realloc = component_reallocs[component_index] + + local old_component_storage = component_storages[component_index] + local new_component_storage ---@type evolved.storage? + + if component_realloc then + new_component_storage = component_realloc( + old_component_storage, old_capacity, min_capacity) + else + new_component_storage = __lua_table_new(min_capacity) + __lua_table_move(old_component_storage, 1, entity_count, 1, new_component_storage) + end + + component_storages[component_index] = new_component_storage + end + end + + chunk.__entity_capacity = min_capacity end ---@param chunk_list evolved.chunk[] @@ -3216,12 +3315,10 @@ function __chunk_set(old_chunk, fragment, component) end end - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_entity_capacity = new_chunk.__entity_capacity - local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages + local new_component_reallocs = new_chunk.__component_reallocs + local new_component_compmoves = new_chunk.__component_compmoves local new_chunk_has_setup_hooks = new_chunk.__has_setup_hooks local new_chunk_has_insert_hooks = new_chunk.__has_insert_hooks @@ -3234,12 +3331,15 @@ function __chunk_set(old_chunk, fragment, component) __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end - local sum_entity_count = old_entity_count + new_entity_count + local sum_entity_count = old_entity_count + new_chunk.__entity_count - if sum_entity_count > new_entity_capacity then + if sum_entity_count > new_chunk.__entity_capacity then __expand_chunk(new_chunk, sum_entity_count) end + local new_entity_list = new_chunk.__entity_list + local new_entity_count = new_chunk.__entity_count + if new_entity_count == 0 then old_chunk.__entity_list, new_chunk.__entity_list = new_entity_list, old_entity_list @@ -3263,10 +3363,19 @@ function __chunk_set(old_chunk, fragment, component) local new_ci = new_component_indices[old_f] local new_cs = new_component_storages[new_ci] + local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] - __lua_table_move( - old_cs, 1, old_entity_count, - new_entity_count + 1, new_cs) + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + else + __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + end end __lua_table_move( @@ -3544,20 +3653,21 @@ function __chunk_remove(old_chunk, ...) end if new_chunk then - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_entity_capacity = new_chunk.__entity_capacity - local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments + local new_component_reallocs = new_chunk.__component_reallocs + local new_component_compmoves = new_chunk.__component_compmoves - local sum_entity_count = old_entity_count + new_entity_count + local sum_entity_count = old_entity_count + new_chunk.__entity_count - if sum_entity_count > new_entity_capacity then + if sum_entity_count > new_chunk.__entity_capacity then __expand_chunk(new_chunk, sum_entity_count) end + local new_entity_list = new_chunk.__entity_list + local new_entity_count = new_chunk.__entity_count + if new_entity_count == 0 then old_chunk.__entity_list, new_chunk.__entity_list = new_entity_list, old_entity_list @@ -3578,13 +3688,22 @@ function __chunk_remove(old_chunk, ...) for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] local new_cs = new_component_storages[new_ci] + local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] local old_ci = old_component_indices[new_f] local old_cs = old_component_storages[old_ci] - __lua_table_move( - old_cs, 1, old_entity_count, - new_entity_count + 1, new_cs) + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + else + __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + end end __lua_table_move( @@ -4723,10 +4842,6 @@ function __evolved_set(entity, fragment, component) end end - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_entity_capacity = new_chunk.__entity_capacity - local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages @@ -4741,12 +4856,14 @@ function __evolved_set(entity, fragment, component) __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end - local new_place = new_entity_count + 1 + local new_place = new_chunk.__entity_count + 1 - if new_place > new_entity_capacity then + if new_place > new_chunk.__entity_capacity then __expand_chunk(new_chunk, new_place) end + local new_entity_list = new_chunk.__entity_list + new_entity_list[new_place] = entity new_chunk.__entity_count = new_place @@ -4934,20 +5051,18 @@ function __evolved_remove(entity, ...) end if new_chunk then - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_entity_capacity = new_chunk.__entity_capacity - local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments - local new_place = new_entity_count + 1 + local new_place = new_chunk.__entity_count + 1 - if new_place > new_entity_capacity then + if new_place > new_chunk.__entity_capacity then __expand_chunk(new_chunk, new_place) end + local new_entity_list = new_chunk.__entity_list + new_entity_list[new_place] = entity new_chunk.__entity_count = new_place @@ -6361,10 +6476,10 @@ __evolved_set(__DEFAULT, __ON_REMOVE, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_INSERT, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_REMOVE, __update_major_chunks) -__evolved_set(__REALLOC, __ON_INSERT, __update_major_chunks) +__evolved_set(__REALLOC, __ON_SET, __update_major_chunks) __evolved_set(__REALLOC, __ON_REMOVE, __update_major_chunks) -__evolved_set(__COMPMOVE, __ON_INSERT, __update_major_chunks) +__evolved_set(__COMPMOVE, __ON_SET, __update_major_chunks) __evolved_set(__COMPMOVE, __ON_REMOVE, __update_major_chunks) --- From c52f708184909edcc87ba9aedaa0ebc326690dda Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 13 Jan 2026 04:47:16 +0700 Subject: [PATCH 04/13] fix batch optimizations with reallocs --- develop/testing/realloc_tests.lua | 100 ++++++++++++++++++++++++++++++ evolved.lua | 93 +++++++++++++++++++-------- 2 files changed, 167 insertions(+), 26 deletions(-) diff --git a/develop/testing/realloc_tests.lua b/develop/testing/realloc_tests.lua index 3863316..c7a755d 100644 --- a/develop/testing/realloc_tests.lua +++ b/develop/testing/realloc_tests.lua @@ -19,10 +19,18 @@ local DOUBLE_TYPEOF = ffi.typeof('double') local DOUBLE_SIZEOF = ffi.sizeof(DOUBLE_TYPEOF) local DOUBLE_STORAGE_TYPEOF = ffi.typeof('$[?]', DOUBLE_TYPEOF) +local STORAGE_SIZES = {} + ---@type evolved.realloc local function float_realloc(old_storage, old_size, new_size) + if old_storage then + assert(STORAGE_SIZES[old_storage] == old_size) + end + local new_storage = ffi.new(FLOAT_STORAGE_TYPEOF, new_size + 1) + STORAGE_SIZES[new_storage] = new_size + if old_storage then ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * FLOAT_SIZEOF) end @@ -32,8 +40,14 @@ end ---@type evolved.realloc local function double_realloc(old_storage, old_size, new_size) + if old_storage then + assert(STORAGE_SIZES[old_storage] == old_size) + end + local new_storage = ffi.new(DOUBLE_STORAGE_TYPEOF, new_size + 1) + STORAGE_SIZES[new_storage] = new_size + if old_storage then ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * DOUBLE_SIZEOF) end @@ -299,3 +313,89 @@ do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) end end + +do + evo.collect_garbage() + + local f1 = evo.builder():name('f1'):realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():name('f2'):realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es, ec = evo.multi_spawn(40, { [f2] = 2 }) + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + evo.batch_destroy(q2) + end + + do + local es, ec = evo.multi_spawn(50, { [f1] = 1, [f2] = 2 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_remove(q1, f1) + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_destroy(q1, q2) + end + + do + evo.spawn({ [f1] = 1 }) + evo.spawn({ [f2] = 2 }) + evo.spawn({ [f1] = 1, [f2] = 2 }) + end + + evo.collect_garbage() +end + +do + evo.collect_garbage() + + local f1 = evo.builder():name('f1'):realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():name('f2'):realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es, ec = evo.multi_spawn(40, { [f1] = 1, [f2] = 2 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + evo.batch_destroy(q2) + end + + do + local es, ec = evo.multi_spawn(50, { [f1] = 1 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(not evo.has(es[i], f2)) + end + + evo.batch_set(q1, f2, 2) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_destroy(q1, q2) + end + + do + evo.spawn({ [f1] = 1 }) + evo.spawn({ [f2] = 2 }) + evo.spawn({ [f1] = 1, [f2] = 2 }) + end + + evo.collect_garbage() +end diff --git a/evolved.lua b/evolved.lua index ca5f11a..9e2b434 100644 --- a/evolved.lua +++ b/evolved.lua @@ -2900,10 +2900,6 @@ function __expand_chunk(chunk, min_capacity) __error_fmt('this operation should be deferred') end - if min_capacity < 4 then - min_capacity = 4 - end - local entity_count = chunk.__entity_count if min_capacity < entity_count then @@ -2922,6 +2918,10 @@ function __expand_chunk(chunk, min_capacity) new_capacity = min_capacity end + if new_capacity < 4 then + new_capacity = 4 + end + if chunk.__has_storage_reallocs then local component_count = chunk.__component_count local component_storages = chunk.__component_storages @@ -2945,13 +2945,12 @@ function __expand_chunk(chunk, min_capacity) end ---@param chunk evolved.chunk -function __shrink_chunk(chunk) +---@param min_capacity integer +function __shrink_chunk(chunk, min_capacity) if __defer_depth <= 0 then __error_fmt('this operation should be deferred') end - local min_capacity = 4 - local entity_count = chunk.__entity_count if min_capacity < entity_count then @@ -3349,10 +3348,28 @@ function __chunk_set(old_chunk, fragment, component) for old_ci = 1, old_component_count do local old_f = old_component_fragments[old_ci] + local new_ci = new_component_indices[old_f] + local new_cr = new_component_reallocs[new_ci] + + if new_cr then + local old_cs = old_component_storages[old_ci] - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] + local new_cs = new_component_storages[new_ci] + local new_cm = new_component_compmoves[new_ci] + + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + end + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end end new_chunk.__entity_count = sum_entity_count @@ -3364,14 +3381,17 @@ function __chunk_set(old_chunk, fragment, component) local new_ci = new_component_indices[old_f] local new_cs = new_component_storages[new_ci] local new_cr = new_component_reallocs[new_ci] - local new_cm = new_component_compmoves[new_ci] - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - elseif new_cr then - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] + if new_cr then + local new_cm = new_component_compmoves[new_ci] + + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end end else __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) @@ -3677,10 +3697,28 @@ function __chunk_remove(old_chunk, ...) for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] + local new_cr = new_component_reallocs[new_ci] + local old_ci = old_component_indices[new_f] - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] + if new_cr then + local new_cs = new_component_storages[new_ci] + local new_cm = new_component_compmoves[new_ci] + + local old_cs = old_component_storages[old_ci] + + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + end + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end end new_chunk.__entity_count = sum_entity_count @@ -3689,17 +3727,20 @@ function __chunk_remove(old_chunk, ...) local new_f = new_component_fragments[new_ci] local new_cs = new_component_storages[new_ci] local new_cr = new_component_reallocs[new_ci] - local new_cm = new_component_compmoves[new_ci] local old_ci = old_component_indices[new_f] local old_cs = old_component_storages[old_ci] - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - elseif new_cr then - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] + if new_cr then + local new_cm = new_component_compmoves[new_ci] + + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end end else __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) @@ -5669,7 +5710,7 @@ function __evolved_collect_garbage() if should_be_purged then __purge_chunk(postorder_chunk) else - __shrink_chunk(postorder_chunk) + __shrink_chunk(postorder_chunk, 0) end end From a7e5652ad462ab924ae009a0a9f732cd620edfc7 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 13 Jan 2026 05:11:04 +0700 Subject: [PATCH 05/13] update readme and teal defs (REALLOC/COMPMOVE fragments) --- README.md | 34 ++++++++++++++++++++++++++++++++++ evolved.d.tl | 6 ++++++ 2 files changed, 40 insertions(+) diff --git a/README.md b/README.md index ba1ade2..fb3483e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) + - [vX.Y.Z](#vxyz) - [v1.7.0](#v170) - [v1.6.0](#v160) - [v1.5.0](#v150) @@ -1171,6 +1172,9 @@ storage :: component[] default :: component duplicate :: {component -> component} +realloc :: {storage?, integer, integer -> storage} +compmove :: {storage, integer, integer, integer, storage} + execute :: {chunk, entity[], integer, any...} prologue :: {any...} epilogue :: {any...} @@ -1200,6 +1204,9 @@ INTERNAL :: fragment DEFAULT :: fragment DUPLICATE :: fragment +REALLOC :: fragment +COMPMOVE :: fragment + PREFAB :: fragment DISABLED :: fragment @@ -1335,6 +1342,9 @@ builder_mt:internal :: builder builder_mt:default :: component -> builder builder_mt:duplicate :: {component -> component} -> builder +builder_mt:realloc :: {storage?, integer, integer -> storage} -> builder +builder_mt:compmove :: {storage, integer, integer, integer, storage} -> builder + builder_mt:prefab :: builder builder_mt:disabled :: builder @@ -1361,6 +1371,10 @@ builder_mt:destruction_policy :: id -> builder ## Changelog +### vX.Y.Z + +- Added the new [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove) fragment traits that allow customizing component storages + ### v1.7.0 - Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries @@ -1430,6 +1444,10 @@ builder_mt:destruction_policy :: id -> builder ### `evolved.DUPLICATE` +### `evolved.REALLOC` + +### `evolved.COMPMOVE` + ### `evolved.PREFAB` ### `evolved.DISABLED` @@ -2047,6 +2065,22 @@ function evolved.builder_mt:default(default) end function evolved.builder_mt:duplicate(duplicate) end ``` +#### `evolved.builder_mt:realloc` + +```lua +---@param realloc evolved.realloc +---@return evolved.builder builder +function evolved.builder_mt:realloc(realloc) end +``` + +#### `evolved.builder_mt:compmove` + +```lua +---@param compmove evolved.compmove +---@return evolved.builder builder +function evolved.builder_mt:compmove(compmove) end +``` + #### `evolved.builder_mt:prefab` ```lua diff --git a/evolved.d.tl b/evolved.d.tl index 8e390b0..d0d7df1 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -64,6 +64,9 @@ default: function(self: Builder, default: Component): Builder duplicate: function(self: Builder, duplicate: function(Component): Component): Builder + realloc: function(self: Builder, realloc: function({ Component } | nil, integer, integer): { Component }): Builder + compmove: function(self: Builder, compmove: function({ Component }, integer, integer, integer, { Component })): Builder + prefab: function(self: Builder): Builder disabled: function(self: Builder): Builder @@ -98,6 +101,9 @@ DEFAULT: Fragment DUPLICATE: Fragment + REALLOC: Fragment + COMPMOVE: Fragment + PREFAB: Fragment DISABLED: Fragment From 4f78c8245c677d1d3d49d0f260e953daed0c50b7 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 14 Jan 2026 16:12:24 +0700 Subject: [PATCH 06/13] update roadmap --- develop/ROADMAP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index b46e2fe..26519c1 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -11,6 +11,8 @@ - We can create component storages on-demand rather than in advance - We should have a way to not copy components on deferred spawn/clone +- Not all assoc_list_remove operations need to keep order, we can have an unordered variant also +- We still have several places where we use __lua_next without deterministic order, we should fix that ## Known Issues From b9cdbe961b56bd06c9569fc6ac02e778ed8f86b6 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 04:12:17 +0700 Subject: [PATCH 07/13] custom storage free semantic --- develop/ROADMAP.md | 4 +- develop/testing/realloc_tests.lua | 227 ++++++++++++++++++-- evolved.lua | 335 +++++++++++++++++------------- 3 files changed, 397 insertions(+), 169 deletions(-) diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index 26519c1..c188e8c 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -9,10 +9,12 @@ ## Thoughts -- We can create component storages on-demand rather than in advance - We should have a way to not copy components on deferred spawn/clone - Not all assoc_list_remove operations need to keep order, we can have an unordered variant also - We still have several places where we use __lua_next without deterministic order, we should fix that +- Having a light version of the gargabe collector can be useful for some use-cases +- We can shrink the table pool tables on garbage collection if they are too large +- Should we sort chunk children by fragment id? ## Known Issues diff --git a/develop/testing/realloc_tests.lua b/develop/testing/realloc_tests.lua index c7a755d..2d81905 100644 --- a/develop/testing/realloc_tests.lua +++ b/develop/testing/realloc_tests.lua @@ -22,37 +22,59 @@ local DOUBLE_STORAGE_TYPEOF = ffi.typeof('$[?]', DOUBLE_TYPEOF) local STORAGE_SIZES = {} ---@type evolved.realloc -local function float_realloc(old_storage, old_size, new_size) - if old_storage then - assert(STORAGE_SIZES[old_storage] == old_size) - end +local function float_realloc(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + STORAGE_SIZES[src] = nil + return + else + if src then + assert(src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + else + assert(src_size == 0) + end - local new_storage = ffi.new(FLOAT_STORAGE_TYPEOF, new_size + 1) + local dst = ffi.new(FLOAT_STORAGE_TYPEOF, dst_size + 1) + STORAGE_SIZES[dst] = dst_size - STORAGE_SIZES[new_storage] = new_size + if src then + ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * FLOAT_SIZEOF) + end - if old_storage then - ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * FLOAT_SIZEOF) + return dst end - - return new_storage end ---@type evolved.realloc -local function double_realloc(old_storage, old_size, new_size) - if old_storage then - assert(STORAGE_SIZES[old_storage] == old_size) - end +local function double_realloc(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + STORAGE_SIZES[src] = nil + return + else + if src then + assert(src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + else + assert(src_size == 0) + end - local new_storage = ffi.new(DOUBLE_STORAGE_TYPEOF, new_size + 1) + local dst = ffi.new(DOUBLE_STORAGE_TYPEOF, dst_size + 1) + STORAGE_SIZES[dst] = dst_size - STORAGE_SIZES[new_storage] = new_size + if src then + ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * DOUBLE_SIZEOF) + end - if old_storage then - ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * DOUBLE_SIZEOF) + return dst end - - return new_storage end ---@type evolved.compmove @@ -399,3 +421,168 @@ do evo.collect_garbage() end + +do + evo.collect_garbage() + + local alloc_call_count = 0 + local free_call_count = 0 + local resize_call_count = 0 + + local function ctor_realloc() + ---@type evolved.realloc + return function(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + free_call_count = free_call_count + 1 + return + else + if src then + assert(src_size > 0) + resize_call_count = resize_call_count + 1 + else + assert(src_size == 0) + alloc_call_count = alloc_call_count + 1 + end + + local dst = {} + + if src then + for i = 1, math.min(src_size, dst_size) do + dst[i] = src[i] + end + end + + return dst + end + end + end + + do + local realloc1 = ctor_realloc() + local realloc2 = ctor_realloc() + + local f1 = evo.builder():default(44):realloc(realloc1):build() + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 21):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(alloc_call_count == 1 and free_call_count == 0) + + local e2 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(alloc_call_count == 1 and free_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e1) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e2) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local es, ec = evo.multi_spawn(10, { [f1] = 84 }) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + for i = 1, ec / 2 do evo.destroy(es[i]) end + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 1) + + evo.set(f1, evo.REALLOC, realloc2) + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 1) + + for i = 1, ec do evo.destroy(es[i]) end + evo.collect_garbage() + assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 1) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 24):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 24) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + local es, ec = evo.multi_spawn(20, { [f1] = 48 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.remove(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 44) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 44) end + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0) + + evo.destroy(e1) + for i = 1, ec do evo.destroy(es[i]) end + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 0) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 100):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 100) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + local es, ec = evo.multi_spawn(20, { [f1] = 48 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.destroy(e1) + for i = 1, ec do evo.destroy(es[i]) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + end + + + do + local realloc = ctor_realloc() + + local f1 = evo.builder():realloc(realloc):build() + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 42) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e1) + assert(not evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + end +end diff --git a/evolved.lua b/evolved.lua index 9e2b434..f2b3126 100644 --- a/evolved.lua +++ b/evolved.lua @@ -40,7 +40,7 @@ local evolved = { ---@alias evolved.default evolved.component ---@alias evolved.duplicate fun(component: evolved.component): evolved.component ----@alias evolved.realloc fun(src?: evolved.storage, old_size: integer, new_size: integer): evolved.storage +---@alias evolved.realloc fun(src?: evolved.storage, src_size: integer, dst_size: integer): evolved.storage? ---@alias evolved.compmove fun(src: evolved.storage, f: integer, e: integer, t: integer, dst: evolved.storage) ---@alias evolved.execute fun( @@ -1111,6 +1111,9 @@ local __id_name local __new_chunk +local __default_realloc +local __default_compmove + local __update_chunk_caches local __update_chunk_queries local __update_chunk_storages @@ -1183,8 +1186,6 @@ local __defer_multi_clone_entity ---@return string ---@nodiscard function __id_name(id) - local id_primary, id_secondary = __evolved_unpack(id) - ---@type string? local id_name = __evolved_get(id, __NAME) @@ -1192,6 +1193,7 @@ function __id_name(id) return id_name end + local id_primary, id_secondary = __evolved_unpack(id) return __lua_string_format('$%d#%d:%d', id, id_primary, id_secondary) end @@ -1322,6 +1324,37 @@ function __new_chunk(chunk_parent, chunk_fragment) return chunk end +---@param src? evolved.storage +---@param src_size integer +---@param dst_size integer +---@return evolved.storage? +function __default_realloc(src, src_size, dst_size) + if dst_size == 0 then + return + end + + if src and dst_size >= src_size then + return src + end + + local dst = __lua_table_new(dst_size) + + if src then + __lua_table_move(src, 1, dst_size, 1, dst) + end + + return dst +end + +---@param src evolved.storage +---@param f integer +---@param e integer +---@param t integer +---@param dst evolved.storage +function __default_compmove(src, f, e, t, dst) + __lua_table_move(src, f, e, t, dst) +end + ---@param chunk evolved.chunk function __update_chunk_caches(chunk) local chunk_parent = chunk.__parent @@ -1469,6 +1502,18 @@ function __update_chunk_storages(chunk) local is_fragment_tag = __evolved_has(fragment, __TAG) if component_index and is_fragment_tag then + if entity_capacity > 0 then + local component_storage = component_storages[component_index] + + if component_realloc then + component_realloc(component_storage, entity_capacity, 0) + else + __default_realloc(component_storage, entity_capacity, 0) + end + + component_storages[component_index] = nil + end + if component_index ~= component_count then local last_component_storage = component_storages[component_count] local last_component_fragment = component_fragments[component_count] @@ -1500,40 +1545,52 @@ function __update_chunk_storages(chunk) component_count = component_count + 1 chunk.__component_count = component_count - local component_storage = fragment_realloc - and fragment_realloc(nil, 0, entity_capacity) - or __lua_table_new(entity_capacity) - local component_storage_index = component_count - - component_indices[fragment] = component_storage_index - component_storages[component_storage_index] = component_storage - component_fragments[component_storage_index] = fragment - component_defaults[component_storage_index] = fragment_default - component_duplicates[component_storage_index] = fragment_duplicate - component_reallocs[component_storage_index] = fragment_realloc - component_compmoves[component_storage_index] = fragment_compmove - - if fragment_duplicate then - for place = 1, entity_count do + component_index = component_count + + component_indices[fragment] = component_index + component_storages[component_index] = nil + component_fragments[component_index] = fragment + component_defaults[component_index] = fragment_default + component_duplicates[component_index] = fragment_duplicate + component_reallocs[component_index] = fragment_realloc + component_compmoves[component_index] = fragment_compmove + + if entity_capacity > 0 then + local new_component_storage ---@type evolved.storage? + + if fragment_realloc then + new_component_storage = fragment_realloc(nil, 0, entity_capacity) + else + new_component_storage = __default_realloc(nil, 0, entity_capacity) + end + + if fragment_duplicate then + for place = 1, entity_count do + local new_component = fragment_default + if new_component ~= nil then new_component = fragment_duplicate(new_component) end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + else local new_component = fragment_default - if new_component ~= nil then new_component = fragment_duplicate(new_component) end if new_component == nil then new_component = true end - component_storage[place] = new_component - end - else - local new_component = fragment_default - if new_component == nil then new_component = true end - for place = 1, entity_count do - component_storage[place] = new_component + for place = 1, entity_count do + new_component_storage[place] = new_component + end end + + component_storages[component_index] = new_component_storage end elseif component_index then - if component_realloc ~= fragment_realloc then + if fragment_realloc ~= component_realloc and entity_capacity > 0 then + local new_component_storage ---@type evolved.storage? local old_component_storage = component_storages[component_index] - local new_component_storage = fragment_realloc - and fragment_realloc(nil, 0, entity_capacity) - or __lua_table_new(entity_capacity) + if fragment_realloc then + new_component_storage = fragment_realloc(nil, 0, entity_capacity) + else + new_component_storage = __default_realloc(nil, 0, entity_capacity) + end if fragment_duplicate then for place = 1, entity_count do @@ -1552,6 +1609,12 @@ function __update_chunk_storages(chunk) end end + if component_realloc then + component_realloc(old_component_storage, entity_capacity, 0) + else + __default_realloc(old_component_storage, entity_capacity, 0) + end + component_storages[component_index] = new_component_storage end @@ -2839,6 +2902,10 @@ function __purge_chunk(chunk) __error_fmt('chunk should be empty before purging') end + if chunk.__entity_capacity > 0 then + __shrink_chunk(chunk, 0) + end + local chunk_parent = chunk.__parent local chunk_fragment = chunk.__fragment @@ -2922,7 +2989,7 @@ function __expand_chunk(chunk, min_capacity) new_capacity = 4 end - if chunk.__has_storage_reallocs then + do local component_count = chunk.__component_count local component_storages = chunk.__component_storages local component_reallocs = chunk.__component_reallocs @@ -2930,14 +2997,18 @@ function __expand_chunk(chunk, min_capacity) for component_index = 1, component_count do local component_realloc = component_reallocs[component_index] - if component_realloc then - local old_component_storage = component_storages[component_index] + local new_component_storage ---@type evolved.storage? + local old_component_storage = component_storages[component_index] - local new_component_storage = component_realloc( + if component_realloc then + new_component_storage = component_realloc( + old_component_storage, old_capacity, new_capacity) + else + new_component_storage = __default_realloc( old_component_storage, old_capacity, new_capacity) - - component_storages[component_index] = new_component_storage end + + component_storages[component_index] = new_component_storage end end @@ -2957,6 +3028,10 @@ function __shrink_chunk(chunk, min_capacity) min_capacity = entity_count end + if min_capacity > 0 and min_capacity < 4 then + min_capacity = 4 + end + local old_capacity = chunk.__entity_capacity if old_capacity <= min_capacity then -- no need to shrink, the chunk is already small enough @@ -2964,9 +3039,14 @@ function __shrink_chunk(chunk, min_capacity) end do - local entity_list = __lua_table_new(min_capacity) - __lua_table_move(chunk.__entity_list, 1, entity_count, 1, entity_list) - chunk.__entity_list = entity_list + local old_entity_list = chunk.__entity_list + local new_entity_list = __lua_table_new(min_capacity) + + __lua_table_move( + old_entity_list, 1, entity_count, + 1, new_entity_list) + + chunk.__entity_list = new_entity_list end do @@ -2977,15 +3057,15 @@ function __shrink_chunk(chunk, min_capacity) for component_index = 1, component_count do local component_realloc = component_reallocs[component_index] - local old_component_storage = component_storages[component_index] local new_component_storage ---@type evolved.storage? + local old_component_storage = component_storages[component_index] if component_realloc then new_component_storage = component_realloc( old_component_storage, old_capacity, min_capacity) else - new_component_storage = __lua_table_new(min_capacity) - __lua_table_move(old_component_storage, 1, entity_count, 1, new_component_storage) + new_component_storage = __default_realloc( + old_component_storage, old_capacity, min_capacity) end component_storages[component_index] = new_component_storage @@ -3339,41 +3419,7 @@ function __chunk_set(old_chunk, fragment, component) local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count - if new_entity_count == 0 then - old_chunk.__entity_list, new_chunk.__entity_list = - new_entity_list, old_entity_list - - old_entity_list, new_entity_list = - new_entity_list, old_entity_list - - for old_ci = 1, old_component_count do - local old_f = old_component_fragments[old_ci] - - local new_ci = new_component_indices[old_f] - local new_cr = new_component_reallocs[new_ci] - - if new_cr then - local old_cs = old_component_storages[old_ci] - - local new_cs = new_component_storages[new_ci] - local new_cm = new_component_compmoves[new_ci] - - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - else - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] - end - end - else - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] - end - end - - new_chunk.__entity_count = sum_entity_count - else + do for old_ci = 1, old_component_count do local old_f = old_component_fragments[old_ci] local old_cs = old_component_storages[old_ci] @@ -3381,26 +3427,36 @@ function __chunk_set(old_chunk, fragment, component) local new_ci = new_component_indices[old_f] local new_cs = new_component_storages[new_ci] local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] - if new_cr then - local new_cm = new_component_compmoves[new_ci] - - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - else - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] - end + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] end else - __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + if new_entity_count > 0 then + __default_compmove(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end end end - __lua_table_move( - old_entity_list, 1, old_entity_count, - new_entity_count + 1, new_entity_list) + if new_entity_count > 0 then + __lua_table_move( + old_entity_list, 1, old_entity_count, + new_entity_count + 1, new_entity_list) + else + old_chunk.__entity_list, new_chunk.__entity_list = + new_entity_list, old_entity_list + + old_entity_list, new_entity_list = + new_entity_list, old_entity_list + end new_chunk.__entity_count = sum_entity_count end @@ -3688,68 +3744,44 @@ function __chunk_remove(old_chunk, ...) local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count - if new_entity_count == 0 then - old_chunk.__entity_list, new_chunk.__entity_list = - new_entity_list, old_entity_list - - old_entity_list, new_entity_list = - new_entity_list, old_entity_list - - for new_ci = 1, new_component_count do - local new_f = new_component_fragments[new_ci] - local new_cr = new_component_reallocs[new_ci] - - local old_ci = old_component_indices[new_f] - - if new_cr then - local new_cs = new_component_storages[new_ci] - local new_cm = new_component_compmoves[new_ci] - - local old_cs = old_component_storages[old_ci] - - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - else - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] - end - end - else - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] - end - end - - new_chunk.__entity_count = sum_entity_count - else + do for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] local new_cs = new_component_storages[new_ci] local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] local old_ci = old_component_indices[new_f] local old_cs = old_component_storages[old_ci] - if new_cr then - local new_cm = new_component_compmoves[new_ci] - - if new_cm then - new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) - else - for old_place = 1, old_entity_count do - local new_place = new_entity_count + old_place - new_cs[new_place] = old_cs[old_place] - end + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] end else - __lua_table_move(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + if new_entity_count > 0 then + __default_compmove(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end end end - __lua_table_move( - old_entity_list, 1, old_entity_count, - new_entity_count + 1, new_entity_list) + if new_entity_count > 0 then + __lua_table_move( + old_entity_list, 1, old_entity_count, + new_entity_count + 1, new_entity_list) + else + old_chunk.__entity_list, new_chunk.__entity_list = + new_entity_list, old_entity_list + + old_entity_list, new_entity_list = + new_entity_list, old_entity_list + end new_chunk.__entity_count = sum_entity_count end @@ -3867,7 +3899,7 @@ local __defer_op = { } ---@type table -local __defer_ops = __list_new(__defer_op.__count) +local __defer_ops = __lua_table_new(__defer_op.__count) ---@param hook fun(...) ---@param ... any hook arguments @@ -4477,7 +4509,7 @@ function __evolved_multi_spawn(entity_count, components) end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() @@ -4557,7 +4589,7 @@ function __evolved_multi_clone(entity_count, prefab, components) end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() @@ -5703,13 +5735,20 @@ function __evolved_collect_garbage() for postorder_chunk_index = postorder_chunk_stack_size, 1, -1 do local postorder_chunk = postorder_chunk_stack[postorder_chunk_index] + local postorder_chunk_child_count = postorder_chunk.__child_count + local postorder_chunk_entity_count = postorder_chunk.__entity_count + local postorder_chunk_entity_capacity = postorder_chunk.__entity_capacity + local should_be_purged = - postorder_chunk.__child_count == 0 and - postorder_chunk.__entity_count == 0 + postorder_chunk_child_count == 0 and + postorder_chunk_entity_count == 0 + + local should_be_shrunk = + postorder_chunk_entity_count < postorder_chunk_entity_capacity if should_be_purged then __purge_chunk(postorder_chunk) - else + elseif should_be_shrunk then __shrink_chunk(postorder_chunk, 0) end end @@ -6013,7 +6052,7 @@ function __builder_mt:multi_spawn(entity_count) end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() @@ -6091,7 +6130,7 @@ function __builder_mt:multi_clone(entity_count, prefab) end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() From b17118544dc7d5ef78d1f45f0d8666acfe1f744d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 04:17:09 +0700 Subject: [PATCH 08/13] little cleanup --- README.md | 4 ++-- evolved.d.tl | 2 +- evolved.lua | 13 ------------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fb3483e..553c4ef 100644 --- a/README.md +++ b/README.md @@ -1172,7 +1172,7 @@ storage :: component[] default :: component duplicate :: {component -> component} -realloc :: {storage?, integer, integer -> storage} +realloc :: {storage?, integer, integer -> storage?} compmove :: {storage, integer, integer, integer, storage} execute :: {chunk, entity[], integer, any...} @@ -1342,7 +1342,7 @@ builder_mt:internal :: builder builder_mt:default :: component -> builder builder_mt:duplicate :: {component -> component} -> builder -builder_mt:realloc :: {storage?, integer, integer -> storage} -> builder +builder_mt:realloc :: {storage?, integer, integer -> storage?} -> builder builder_mt:compmove :: {storage, integer, integer, integer, storage} -> builder builder_mt:prefab :: builder diff --git a/evolved.d.tl b/evolved.d.tl index d0d7df1..1cc3b78 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -64,7 +64,7 @@ default: function(self: Builder, default: Component): Builder duplicate: function(self: Builder, duplicate: function(Component): Component): Builder - realloc: function(self: Builder, realloc: function({ Component } | nil, integer, integer): { Component }): Builder + realloc: function(self: Builder, realloc: function({ Component } | nil, integer, integer): { Component } | nil): Builder compmove: function(self: Builder, compmove: function({ Component }, integer, integer, integer, { Component })): Builder prefab: function(self: Builder): Builder diff --git a/evolved.lua b/evolved.lua index f2b3126..4419a89 100644 --- a/evolved.lua +++ b/evolved.lua @@ -192,8 +192,6 @@ local __structural_changes = 0 ---@type integer ---@field package __has_internal_major boolean ---@field package __has_internal_minors boolean ---@field package __has_internal_fragments boolean ----@field package __has_storage_reallocs boolean ----@field package __has_storage_compmoves boolean ---@field package __has_required_fragments boolean local __chunk_mt = {} __chunk_mt.__index = __chunk_mt @@ -1267,8 +1265,6 @@ function __new_chunk(chunk_parent, chunk_fragment) __has_internal_major = false, __has_internal_minors = false, __has_internal_fragments = false, - __has_storage_reallocs = false, - __has_storage_compmoves = false, __has_required_fragments = false, }, __chunk_mt) @@ -1388,12 +1384,6 @@ function __update_chunk_caches(chunk) local has_internal_minors = chunk_parent ~= nil and chunk_parent.__has_internal_fragments local has_internal_fragments = has_internal_major or has_internal_minors - local has_storage_reallocs = chunk_parent ~= nil and chunk_parent.__has_storage_reallocs - or __evolved_has(chunk_fragment, __REALLOC) - - local has_storage_compmoves = chunk_parent ~= nil and chunk_parent.__has_storage_compmoves - or __evolved_has(chunk_fragment, __COMPMOVE) - local has_required_fragments = false for chunk_fragment_index = 1, chunk_fragment_count do @@ -1434,9 +1424,6 @@ function __update_chunk_caches(chunk) chunk.__has_internal_minors = has_internal_minors chunk.__has_internal_fragments = has_internal_fragments - chunk.__has_storage_reallocs = has_storage_reallocs - chunk.__has_storage_compmoves = has_storage_compmoves - chunk.__has_required_fragments = has_required_fragments if has_required_fragments then From a0f252f47cacdb7ce13e916cbb674dd2159b0589 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 06:14:59 +0700 Subject: [PATCH 09/13] additional realloc checks --- develop/ROADMAP.md | 2 +- evolved.lua | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index c188e8c..668acf4 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -18,4 +18,4 @@ ## Known Issues -- Errors in hooks are cannot be handled properly right now +- Errors in hooks or rellocs/compmoves are cannot be handled properly right now diff --git a/evolved.lua b/evolved.lua index 4419a89..a71f741 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1551,6 +1551,11 @@ function __update_chunk_storages(chunk) new_component_storage = __default_realloc(nil, 0, entity_capacity) end + if not new_component_storage then + __error_fmt('component storage allocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(fragment)) + end + if fragment_duplicate then for place = 1, entity_count do local new_component = fragment_default @@ -1579,6 +1584,11 @@ function __update_chunk_storages(chunk) new_component_storage = __default_realloc(nil, 0, entity_capacity) end + if not new_component_storage then + __error_fmt('component storage allocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(fragment)) + end + if fragment_duplicate then for place = 1, entity_count do local new_component = old_component_storage[place] @@ -2995,6 +3005,17 @@ function __expand_chunk(chunk, min_capacity) old_component_storage, old_capacity, new_capacity) end + if min_capacity > 0 and not new_component_storage then + __error_fmt( + 'component storage reallocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + elseif min_capacity == 0 and new_component_storage then + __warning_fmt( + 'component storage reallocation for zero capacity should return nil: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + new_component_storage = nil + end + component_storages[component_index] = new_component_storage end end @@ -3055,6 +3076,17 @@ function __shrink_chunk(chunk, min_capacity) old_component_storage, old_capacity, min_capacity) end + if min_capacity > 0 and not new_component_storage then + __error_fmt( + 'component storage reallocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + elseif min_capacity == 0 and new_component_storage then + __warning_fmt( + 'component storage reallocation for zero capacity should return nil: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + new_component_storage = nil + end + component_storages[component_index] = new_component_storage end end From 12c86ca679ec50687a9fae7eb26e9570f578b93d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 07:24:38 +0700 Subject: [PATCH 10/13] update README --- README.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/README.md b/README.md index 553c4ef..a7204f9 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - [Shared Components](#shared-components) - [Fragment Requirements](#fragment-requirements) - [Destruction Policies](#destruction-policies) + - [Custom Component Storages](#custom-component-storages) - [Cheat Sheet](#cheat-sheet) - [Aliases](#aliases) - [Predefs](#predefs) @@ -1154,6 +1155,145 @@ evolved.destroy(world) assert(not evolved.alive(entity)) ``` +#### Custom Component Storages + +In some cases, you might want custom storages for fragment components. For example, you might want to store components in a specialized way for performance reasons. The library provides two fragment traits for this purpose: [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove). + +The [`evolved.REALLOC`](#evolvedrealloc) trait expects a function that is called when the fragment storage needs to be reallocated. The [`evolved.COMPMOVE`](#evolvedcompmove) trait expects a function that is called when components need to be moved from one storage to another. + +A canonical example of using custom storages is implementing a fragment that stores components in an FFI-backed storage for better processing performance in LuaJIT. This is an advanced topic and requires a good understanding of LuaJIT FFI and memory management. So I won't cover it here in detail, but here is a simple example to give you an idea of how it works. More information can be found on the [LuaJIT](https://luajit.org/ext_ffi.html) website. + +```lua +local ffi = require 'ffi' +local evolved = require 'evolved' + +-- +-- +-- Define FFI double storage realloc and compmove functions +-- +-- + +local FFI_DOUBLE_TYPEOF = ffi.typeof('double') +local FFI_DOUBLE_SIZEOF = ffi.sizeof(FFI_DOUBLE_TYPEOF) +local FFI_DOUBLE_STORAGE_TYPEOF = ffi.typeof('double[?]') + +---@param src ffi.cdata*? +---@param src_size integer +---@param dst_size integer +---@return ffi.cdata*? +local function FFI_DOUBLE_STORAGE_REALLOC(src, src_size, dst_size) + if dst_size == 0 then + -- freeing the src storage, just let the GC handle it + return + end + + -- to support 1-based indexing, allocate one extra element + local dst = ffi.new(FFI_DOUBLE_STORAGE_TYPEOF, dst_size + 1) + + if src and src_size > 0 then + -- handle both expanding and shrinking + local min_size = math.min(src_size, dst_size) + ffi.copy(dst + 1, src + 1, min_size * FFI_DOUBLE_SIZEOF) + end + + return dst +end + +---@param src ffi.cdata* +---@param f integer +---@param e integer +---@param t integer +---@param dst ffi.cdata* +local function FFI_DOUBLE_STORAGE_COMPMOVE(src, f, e, t, dst) + ffi.copy(dst + t, src + f, (e - f + 1) * FFI_DOUBLE_SIZEOF) +end + +-- +-- +-- Define fragments with our custom FFI storages +-- +-- + +local POSITION_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local POSITION_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +-- +-- +-- Define a movement system that uses these components +-- +-- + +local MOVEMENT_SYSTEM = evolved.builder() + :include(POSITION_X, POSITION_Y) + :include(VELOCITY_X, VELOCITY_Y) + :execute(function(chunk, entity_list, entity_count, delta_time) + local position_xs, position_ys = chunk:components(POSITION_X, POSITION_Y) + local velocity_xs, velocity_ys = chunk:components(VELOCITY_X, VELOCITY_Y) + + for i = 1, entity_count do + local px, py = position_xs[i], position_ys[i] + local vx, vy = velocity_xs[i], velocity_ys[i] + + px = px + vx * delta_time + py = py + vy * delta_time + + position_xs[i], position_ys[i] = px, py + end + end):build() + +-- +-- +-- Spawn some entities with these components +-- +-- + +do + local entity_list, entity_count = evolved.builder() + :set(POSITION_X) + :set(POSITION_Y) + :set(VELOCITY_X) + :set(VELOCITY_Y) + :multi_build(10000) + + for i = 1, entity_count do + local entity = entity_list[i] + evolved.set(entity, POSITION_X, math.random(0, 640)) + evolved.set(entity, POSITION_Y, math.random(0, 480)) + evolved.set(entity, VELOCITY_X, math.random(-100, 100)) + evolved.set(entity, VELOCITY_Y, math.random(-100, 100)) + end +end + +-- +-- +-- Process the movement system with a delta time payload +-- +-- + +evolved.process_with(MOVEMENT_SYSTEM, 0.016) +``` + ## Cheat Sheet ### Aliases From 21d5091d1419565fde666ac7ad4f508748cda15b Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 17:14:20 +0700 Subject: [PATCH 11/13] first mappers impl --- README.md | 105 ++++--- develop/ROADMAP.md | 3 +- develop/all.lua | 1 + develop/testing/mappers_tests.lua | 242 +++++++++++++++ evolved.lua | 470 ++++++++++++++++++------------ example/main.lua | 33 ++- 6 files changed, 601 insertions(+), 253 deletions(-) create mode 100644 develop/testing/mappers_tests.lua diff --git a/README.md b/README.md index a7204f9..c93b7bc 100644 --- a/README.md +++ b/README.md @@ -420,17 +420,19 @@ You should try to avoid structural changes, especially in performance-critical c #### Spawning Entities ```lua ----@param components? table +---@param component_table? table +---@param component_mapper? fun(chunk: evolved.chunk, place: integer) ---@return evolved.entity -function evolved.spawn(components) end +function evolved.spawn(component_table, component_mapper) end ---@param prefab evolved.entity ----@param components? table +---@param component_table? table +---@param component_mapper? fun(chunk: evolved.chunk, place: integer) ---@return evolved.entity -function evolved.clone(prefab, components) end +function evolved.clone(prefab, component_table, component_mapper) end ``` -The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. +The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `component_table` every time; consider using a predefined table for maximum performance. You can also use the [`evolved.clone`](#evolvedclone) function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. @@ -1268,22 +1270,22 @@ local MOVEMENT_SYSTEM = evolved.builder() -- -- -do - local entity_list, entity_count = evolved.builder() - :set(POSITION_X) - :set(POSITION_Y) - :set(VELOCITY_X) - :set(VELOCITY_Y) - :multi_build(10000) +evolved.builder() + :set(POSITION_X) + :set(POSITION_Y) + :set(VELOCITY_X) + :set(VELOCITY_Y) + :multi_spawn(10000, function(chunk, b_place, e_place) + local position_xs, position_ys = chunk:components(POSITION_X, POSITION_Y) + local velocity_xs, velocity_ys = chunk:components(VELOCITY_X, VELOCITY_Y) - for i = 1, entity_count do - local entity = entity_list[i] - evolved.set(entity, POSITION_X, math.random(0, 640)) - evolved.set(entity, POSITION_Y, math.random(0, 480)) - evolved.set(entity, VELOCITY_X, math.random(-100, 100)) - evolved.set(entity, VELOCITY_Y, math.random(-100, 100)) - end -end + for place = b_place, e_place do + position_xs[place] = math.random(0, 640) + position_ys[place] = math.random(0, 480) + velocity_xs[place] = math.random(-100, 100) + velocity_ys[place] = math.random(-100, 100) + end + end) -- -- @@ -1309,6 +1311,9 @@ system :: id component :: any storage :: component[] +component_table :: +component_mapper :: {chunk, integer, integer} + default :: component duplicate :: {component -> component} @@ -1387,11 +1392,11 @@ depth :: integer commit :: boolean cancel :: boolean -spawn :: ? -> entity -multi_spawn :: integer, ? -> entity[], integer +spawn :: component_table?, component_mapper? -> entity +multi_spawn :: integer, component_table?, component_mapper? -> entity[], integer -clone :: entity, ? -> entity -multi_clone :: integer, entity, ? -> entity[], integer +clone :: entity, component_table?, component_mapper? -> entity +multi_clone :: integer, entity, component_table?, component_mapper? -> entity[], integer alive :: entity -> boolean alive_all :: entity... -> boolean @@ -1405,7 +1410,7 @@ has :: entity, fragment -> boolean has_all :: entity, fragment... -> boolean has_any :: entity, fragment... -> boolean -get :: entity, fragment... -> component... +get :: entity, fragment... -> component... set :: entity, fragment, component -> () remove :: entity, fragment... -> () @@ -1453,14 +1458,14 @@ chunk_mt:components :: fragment... -> storage... ``` builder :: builder -builder_mt:build :: entity? -> entity -builder_mt:multi_build :: integer, entity? -> entity[], integer +builder_mt:build :: entity?, component_mapper? -> entity +builder_mt:multi_build :: integer, entity?, component_mapper? -> entity[], integer -builder_mt:spawn :: entity -builder_mt:multi_spawn :: integer -> entity[], integer +builder_mt:spawn :: component_mapper? -> entity +builder_mt:multi_spawn :: integer, component_mapper? -> entity[], integer -builder_mt:clone :: entity -> entity -builder_mt:multi_clone :: integer, entity -> entity[], integer +builder_mt:clone :: entity, component_mapper? -> entity +builder_mt:multi_clone :: integer, entity, component_mapper? -> entity[], integer builder_mt:has :: fragment -> boolean builder_mt:has_all :: fragment... -> boolean @@ -1696,28 +1701,31 @@ function evolved.cancel() end ### `evolved.spawn` ```lua ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.spawn(components) end +function evolved.spawn(component_table, component_mapper) end ``` ### `evolved.multi_spawn` ```lua ---@param entity_count integer ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.multi_spawn(entity_count, components) end +function evolved.multi_spawn(entity_count, component_table, component_mapper) end ``` ### `evolved.clone` ```lua ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.clone(prefab, components) end +function evolved.clone(prefab, component_table, component_mapper) end ``` ### `evolved.multi_clone` @@ -1725,10 +1733,11 @@ function evolved.clone(prefab, components) end ```lua ---@param entity_count integer ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.multi_clone(entity_count, prefab, components) end +function evolved.multi_clone(entity_count, prefab, component_table, component_mapper) end ``` ### `evolved.alive` @@ -2045,8 +2054,9 @@ function evolved.builder() end ```lua ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:build(prefab) end +function evolved.builder_mt:build(prefab, component_mapper) end ``` ### `evolved.builder_mt:multi_build` @@ -2054,33 +2064,37 @@ function evolved.builder_mt:build(prefab) end ```lua ---@param entity_count integer ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_build(entity_count, prefab) end +function evolved.builder_mt:multi_build(entity_count, prefab, component_mapper) end ``` #### `evolved.builder_mt:spawn` ```lua +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:spawn() end +function evolved.builder_mt:spawn(component_mapper) end ``` #### `evolved.builder_mt:multi_spawn` ```lua ---@param entity_count integer +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_spawn(entity_count) end +function evolved.builder_mt:multi_spawn(entity_count, component_mapper) end ``` #### `evolved.builder_mt:clone` ```lua ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:clone(prefab) end +function evolved.builder_mt:clone(prefab, component_mapper) end ``` #### `evolved.builder_mt:multi_clone` @@ -2088,9 +2102,10 @@ function evolved.builder_mt:clone(prefab) end ```lua ---@param entity_count integer ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_clone(entity_count, prefab) end +function evolved.builder_mt:multi_clone(entity_count, prefab, component_mapper) end ``` #### `evolved.builder_mt:has` diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index 668acf4..5b53895 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -15,7 +15,8 @@ - Having a light version of the gargabe collector can be useful for some use-cases - We can shrink the table pool tables on garbage collection if they are too large - Should we sort chunk children by fragment id? +- Basic default component value as true looks awful, should we use something else? ## Known Issues -- Errors in hooks or rellocs/compmoves are cannot be handled properly right now +- Errors in hooks or rellocs/compmoves/mappers are cannot be handled properly right now diff --git a/develop/all.lua b/develop/all.lua index 9615cde..7e01a14 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -5,6 +5,7 @@ require 'develop.testing.depth_tests' require 'develop.testing.destroy_tests' require 'develop.testing.locate_tests' require 'develop.testing.main_tests' +require 'develop.testing.mappers_tests' require 'develop.testing.multi_spawn_tests' require 'develop.testing.name_tests' require 'develop.testing.process_with_tests' diff --git a/develop/testing/mappers_tests.lua b/develop/testing/mappers_tests.lua new file mode 100644 index 0000000..1e84950 --- /dev/null +++ b/develop/testing/mappers_tests.lua @@ -0,0 +1,242 @@ +local evo = require 'evolved' + +do + local f1, f2 = evo.id(2) + + local e1 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + local e2 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 11) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 22) + + local e11 = evo.clone(e1, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 3 + f2s[p] = f2s[p] * 4 + end) + + local e22 = evo.clone(e2, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 30 + f2s[p] = f2s[p] + 40 + end) + + assert(evo.has(e11, f1) and evo.get(e11, f1) == 6) + assert(evo.has(e11, f2) and evo.get(e11, f2) == 24) + assert(evo.has(e22, f1) and evo.get(e22, f1) == 41) + assert(evo.has(e22, f2) and evo.get(e22, f2) == 62) +end + +do + local f1, f2 = evo.id(2) + + evo.defer() + + local e1 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + local e2 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(not evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(not evo.has(e1, f2) and evo.get(e1, f2) == nil) + assert(not evo.has(e2, f1) and evo.get(e2, f1) == nil) + assert(not evo.has(e2, f2) and evo.get(e2, f2) == nil) + + evo.commit() + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 11) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 22) + + evo.defer() + + local e11 = evo.clone(e1, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 3 + f2s[p] = f2s[p] * 4 + end) + + local e22 = evo.clone(e2, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 30 + f2s[p] = f2s[p] + 40 + end) + + assert(not evo.has(e11, f1) and evo.get(e11, f1) == nil) + assert(not evo.has(e11, f2) and evo.get(e11, f2) == nil) + assert(not evo.has(e22, f1) and evo.get(e22, f1) == nil) + assert(not evo.has(e22, f2) and evo.get(e22, f2) == nil) + + evo.commit() + + assert(evo.has(e11, f1) and evo.get(e11, f1) == 6) + assert(evo.has(e11, f2) and evo.get(e11, f2) == 24) + assert(evo.has(e22, f1) and evo.get(e22, f1) == 41) + assert(evo.has(e22, f2) and evo.get(e22, f2) == 62) +end + +do + local f1, f2 = evo.id(2) + + local es, ec = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + local es2, ec2 = evo.multi_clone(10, es[1], nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end + +do + local f1, f2 = evo.id(2) + + evo.defer() + + local es, ec = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + end + + evo.commit() + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + evo.defer() + + local es2, ec2 = evo.multi_clone(10, es[1], nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + end + + evo.commit() + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end + +do + local f1, f2 = evo.id(2) + + local e1 = evo.builder():set(f1, 1):set(f2, 2):build(nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + + local e2 = evo.builder():build(e1, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e2, f1) and evo.get(e2, f1) == 12) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 26) + + local e3 = evo.builder():set(f2, 3):build(e1, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e3, f1) and evo.get(e3, f1) == 12) + assert(evo.has(e3, f2) and evo.get(e3, f2) == 23) +end + +do + local f1, f2 = evo.id(2) + + local es, ec = evo.builder():set(f1, 1):set(f2, 2):multi_build(10, nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + local es2, ec2 = evo.builder():multi_build(10, es[1], function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end diff --git a/evolved.lua b/evolved.lua index a71f741..d69b4d2 100644 --- a/evolved.lua +++ b/evolved.lua @@ -37,6 +37,9 @@ local evolved = { ---@alias evolved.component any ---@alias evolved.storage evolved.component[] +---@alias evolved.component_table table +---@alias evolved.component_mapper fun(chunk: evolved.chunk, b_place: integer, e_place: integer) + ---@alias evolved.default evolved.component ---@alias evolved.duplicate fun(component: evolved.component): evolved.component @@ -198,7 +201,7 @@ __chunk_mt.__index = __chunk_mt ---@class evolved.builder ---@field package __chunk? evolved.chunk ----@field package __components table +---@field package __component_table evolved.component_table local __builder_mt = {} __builder_mt.__index = __builder_mt @@ -682,13 +685,11 @@ local __table_pool_tag = { system_list = 3, each_state = 4, execute_state = 5, - entity_set = 6, - entity_list = 7, - fragment_set = 8, - fragment_list = 9, - component_map = 10, - component_list = 11, - __count = 11, + entity_list = 6, + fragment_set = 7, + fragment_list = 8, + component_table = 9, + __count = 9, } ---@class (exact) evolved.table_pool @@ -1017,21 +1018,6 @@ local __safe_tbls = { __newindex = function() __error_fmt 'attempt to modify empty fragment set' end }) --[[@as table]], - __EMPTY_FRAGMENT_LIST = __lua_setmetatable({}, { - __tostring = function() return 'empty fragment list' end, - __newindex = function() __error_fmt 'attempt to modify empty fragment list' end - }) --[=[@as evolved.fragment[]]=], - - __EMPTY_COMPONENT_MAP = __lua_setmetatable({}, { - __tostring = function() return 'empty component map' end, - __newindex = function() __error_fmt 'attempt to modify empty component map' end - }) --[[@as table]], - - __EMPTY_COMPONENT_LIST = __lua_setmetatable({}, { - __tostring = function() return 'empty component list' end, - __newindex = function() __error_fmt 'attempt to modify empty component list' end - }) --[=[@as evolved.component[]]=], - __EMPTY_COMPONENT_STORAGE = __lua_setmetatable({}, { __tostring = function() return 'empty component storage' end, __newindex = function() __error_fmt 'attempt to modify empty component storage' end @@ -1906,7 +1892,7 @@ function __chunk_with_fragment(chunk, fragment) end ---@param chunk? evolved.chunk ----@param components table +---@param components evolved.component_table ---@return evolved.chunk? ---@nodiscard function __chunk_with_components(chunk, components) @@ -2063,7 +2049,7 @@ function __chunk_fragments(head_fragment, ...) return chunk end ----@param components table +---@param components evolved.component_table ---@return evolved.chunk? ---@nodiscard function __chunk_components(components) @@ -2313,14 +2299,15 @@ end ---@param chunk? evolved.chunk ---@param entity evolved.entity ----@param components table -function __spawn_entity(chunk, entity, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __spawn_entity(chunk, entity, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('spawn entity operations should be deferred') end if not chunk or chunk.__unreachable_or_collected then - chunk = __chunk_components(components) + chunk = component_table and __chunk_components(component_table) end while chunk and chunk.__has_required_fragments do @@ -2369,7 +2356,7 @@ function __spawn_entity(chunk, entity, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then ini_component = chunk_component_defaults[component_index] @@ -2392,6 +2379,10 @@ function __spawn_entity(chunk, entity, components) end end + if component_mapper then + component_mapper(chunk, place, place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2435,14 +2426,15 @@ end ---@param chunk? evolved.chunk ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param components table -function __multi_spawn_entity(chunk, entity_list, entity_count, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('spawn entity operations should be deferred') end if not chunk or chunk.__unreachable_or_collected then - chunk = __chunk_components(components) + chunk = component_table and __chunk_components(component_table) end while chunk and chunk.__has_required_fragments do @@ -2498,7 +2490,7 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then ini_component = chunk_component_defaults[component_index] @@ -2525,6 +2517,10 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) end end + if component_mapper then + component_mapper(chunk, b_place, e_place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2575,8 +2571,9 @@ end ---@param prefab evolved.entity ---@param entity evolved.entity ----@param components table -function __clone_entity(prefab, entity, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __clone_entity(prefab, entity, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('clone entity operations should be deferred') end @@ -2592,12 +2589,12 @@ function __clone_entity(prefab, entity, components) end if not prefab_chunk or not prefab_chunk.__without_unique_fragments then - return __spawn_entity(nil, entity, components) + return __spawn_entity(nil, entity, component_table, component_mapper) end - local chunk = __chunk_with_components( - prefab_chunk.__without_unique_fragments, - components) + local chunk = component_table + and __chunk_with_components(prefab_chunk.__without_unique_fragments, component_table) + or prefab_chunk.__without_unique_fragments while chunk and chunk.__has_required_fragments do local required_chunk = chunk.__with_required_fragments @@ -2648,7 +2645,7 @@ function __clone_entity(prefab, entity, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then if chunk == prefab_chunk then @@ -2681,6 +2678,10 @@ function __clone_entity(prefab, entity, components) end end + if component_mapper then + component_mapper(chunk, place, place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2724,8 +2725,9 @@ end ---@param prefab evolved.entity ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param components table -function __multi_clone_entity(prefab, entity_list, entity_count, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('clone entity operations should be deferred') end @@ -2741,12 +2743,12 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) end if not prefab_chunk or not prefab_chunk.__without_unique_fragments then - return __multi_spawn_entity(nil, entity_list, entity_count, components) + return __multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) end - local chunk = __chunk_with_components( - prefab_chunk.__without_unique_fragments, - components) + local chunk = component_table + and __chunk_with_components(prefab_chunk.__without_unique_fragments, component_table) + or prefab_chunk.__without_unique_fragments while chunk and chunk.__has_required_fragments do local required_chunk = chunk.__with_required_fragments @@ -2804,7 +2806,7 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then if chunk == prefab_chunk then @@ -2841,6 +2843,10 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) end end + if component_mapper then + component_mapper(chunk, b_place, e_place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -3995,13 +4001,18 @@ end ---@param chunk? evolved.chunk ---@param entity evolved.entity ----@param component_map table -function __defer_spawn_entity(chunk, entity, component_map) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_spawn_entity(chunk, entity, component_table, component_mapper) + ---@type evolved.component_table? + local component_table2 + + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -4010,43 +4021,53 @@ function __defer_spawn_entity(chunk, entity, component_map) bytecode[length + 1] = __defer_op.spawn_entity bytecode[length + 2] = chunk bytecode[length + 3] = entity - bytecode[length + 4] = component_map_dup + bytecode[length + 4] = component_table2 + bytecode[length + 5] = component_mapper - __defer_length = length + 4 + __defer_length = length + 5 end __defer_ops[__defer_op.spawn_entity] = function(bytes, index) local chunk = bytes[index + 0] local entity = bytes[index + 1] - local component_map_dup = bytes[index + 2] + local component_table2 = bytes[index + 2] + local component_mapper = bytes[index + 3] __evolved_defer() do - __spawn_entity(chunk, entity, component_map_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __spawn_entity(chunk, entity, component_table2, component_mapper) + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 3 + return 4 end ---@param chunk? evolved.chunk ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param component_map table -function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) ---@type evolved.entity[] - local entity_list_dup = __acquire_table(__table_pool_tag.entity_list) + local entity_list2 = __acquire_table(__table_pool_tag.entity_list) __lua_table_move( entity_list, 1, entity_count, - 1, entity_list_dup) + 1, entity_list2) + + ---@type evolved.component_table? + local component_table2 - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -4055,38 +4076,51 @@ function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_ bytecode[length + 1] = __defer_op.multi_spawn_entity bytecode[length + 2] = chunk bytecode[length + 3] = entity_count - bytecode[length + 4] = entity_list_dup - bytecode[length + 5] = component_map_dup + bytecode[length + 4] = entity_list2 + bytecode[length + 5] = component_table2 + bytecode[length + 6] = component_mapper - __defer_length = length + 5 + __defer_length = length + 6 end __defer_ops[__defer_op.multi_spawn_entity] = function(bytes, index) local chunk = bytes[index + 0] local entity_count = bytes[index + 1] - local entity_list_dup = bytes[index + 2] - local component_map_dup = bytes[index + 3] + local entity_list2 = bytes[index + 2] + local component_table2 = bytes[index + 3] + local component_mapper = bytes[index + 4] __evolved_defer() do - __multi_spawn_entity(chunk, entity_list_dup, entity_count, component_map_dup) - __release_table(__table_pool_tag.entity_list, entity_list_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __multi_spawn_entity(chunk, entity_list2, entity_count, component_table2, component_mapper) + + if entity_list2 then + __release_table(__table_pool_tag.entity_list, entity_list2) + end + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 4 + return 5 end ---@param prefab evolved.entity ---@param entity evolved.entity ----@param component_map table -function __defer_clone_entity(prefab, entity, component_map) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_clone_entity(prefab, entity, component_table, component_mapper) + ---@type evolved.component_table? + local component_table2 + + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -4095,43 +4129,53 @@ function __defer_clone_entity(prefab, entity, component_map) bytecode[length + 1] = __defer_op.clone_entity bytecode[length + 2] = prefab bytecode[length + 3] = entity - bytecode[length + 4] = component_map_dup + bytecode[length + 4] = component_table2 + bytecode[length + 5] = component_mapper - __defer_length = length + 4 + __defer_length = length + 5 end __defer_ops[__defer_op.clone_entity] = function(bytes, index) local prefab = bytes[index + 0] local entity = bytes[index + 1] - local component_map_dup = bytes[index + 2] + local component_table2 = bytes[index + 2] + local component_mapper = bytes[index + 3] __evolved_defer() do - __clone_entity(prefab, entity, component_map_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __clone_entity(prefab, entity, component_table2, component_mapper) + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 3 + return 4 end ---@param prefab evolved.entity ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param component_map table -function __defer_multi_clone_entity(prefab, entity_list, entity_count, component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) ---@type evolved.entity[] - local entity_list_dup = __acquire_table(__table_pool_tag.entity_list) + local entity_list2 = __acquire_table(__table_pool_tag.entity_list) __lua_table_move( entity_list, 1, entity_count, - 1, entity_list_dup) + 1, entity_list2) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) + ---@type evolved.component_table? + local component_table2 - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) + + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -4140,27 +4184,35 @@ function __defer_multi_clone_entity(prefab, entity_list, entity_count, component bytecode[length + 1] = __defer_op.multi_clone_entity bytecode[length + 2] = prefab bytecode[length + 3] = entity_count - bytecode[length + 4] = entity_list_dup - bytecode[length + 5] = component_map_dup + bytecode[length + 4] = entity_list2 + bytecode[length + 5] = component_table2 + bytecode[length + 6] = component_mapper - __defer_length = length + 5 + __defer_length = length + 6 end __defer_ops[__defer_op.multi_clone_entity] = function(bytes, index) local prefab = bytes[index + 0] local entity_count = bytes[index + 1] - local entity_list_dup = bytes[index + 2] - local component_map_dup = bytes[index + 3] + local entity_list2 = bytes[index + 2] + local component_table2 = bytes[index + 3] + local component_mapper = bytes[index + 4] __evolved_defer() do - __multi_clone_entity(prefab, entity_list_dup, entity_count, component_map_dup) - __release_table(__table_pool_tag.entity_list, entity_list_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __multi_clone_entity(prefab, entity_list2, entity_count, component_table2, component_mapper) + + if entity_list2 then + __release_table(__table_pool_tag.entity_list, entity_list2) + end + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 4 + return 5 end --- @@ -4479,28 +4531,33 @@ function __evolved_cancel() return __evolved_commit() end ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __evolved_spawn(components) - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - +function __evolved_spawn(component_table, component_mapper) if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end local entity = __acquire_id() + if not component_table or not __lua_next(component_table) then + return entity + end + if __defer_depth > 0 then - __defer_spawn_entity(nil, entity, components) + __defer_spawn_entity(nil, entity, component_table, component_mapper) else __evolved_defer() do - __spawn_entity(nil, entity, components) + __spawn_entity(nil, entity, component_table, component_mapper) end __evolved_commit() end @@ -4509,21 +4566,22 @@ function __evolved_spawn(components) end ---@param entity_count integer ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __evolved_multi_spawn(entity_count, components) +function __evolved_multi_spawn(entity_count, component_table, component_mapper) if entity_count <= 0 then return {}, 0 end - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -4534,12 +4592,16 @@ function __evolved_multi_spawn(entity_count, components) entity_list[entity_index] = __acquire_id() end + if not component_table or not __lua_next(component_table) then + return entity_list, entity_count + end + if __defer_depth > 0 then - __defer_multi_spawn_entity(nil, entity_list, entity_count, components) + __defer_multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_spawn_entity(nil, entity_list, entity_count, components) + __multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -4548,21 +4610,22 @@ function __evolved_multi_spawn(entity_count, components) end ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __evolved_clone(prefab, components) - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - +function __evolved_clone(prefab, component_table, component_mapper) if __debug_mode then if not __evolved_alive(prefab) then __error_fmt('the prefab (%s) is not alive and cannot be used', __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -4570,11 +4633,11 @@ function __evolved_clone(prefab, components) local entity = __acquire_id() if __defer_depth > 0 then - __defer_clone_entity(prefab, entity, components) + __defer_clone_entity(prefab, entity, component_table, component_mapper) else __evolved_defer() do - __clone_entity(prefab, entity, components) + __clone_entity(prefab, entity, component_table, component_mapper) end __evolved_commit() end @@ -4584,26 +4647,27 @@ end ---@param entity_count integer ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __evolved_multi_clone(entity_count, prefab, components) +function __evolved_multi_clone(entity_count, prefab, component_table, component_mapper) if entity_count <= 0 then return {}, 0 end - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - if __debug_mode then if not __evolved_alive(prefab) then __error_fmt('the prefab (%s) is not alive and cannot be used', __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -4615,11 +4679,11 @@ function __evolved_multi_clone(entity_count, prefab, components) end if __defer_depth > 0 then - __defer_multi_clone_entity(prefab, entity_list, entity_count, components) + __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_clone_entity(prefab, entity_list, entity_count, components) + __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -5976,7 +6040,7 @@ end ---@nodiscard function __evolved_builder() return __lua_setmetatable({ - __components = {}, + __component_table = {}, }, __builder_mt) end @@ -5984,7 +6048,7 @@ function __builder_mt:__tostring() local fragment_list = {} ---@type evolved.fragment[] local fragment_count = 0 ---@type integer - for fragment in __lua_next, self.__components do + for fragment in __lua_next, self.__component_table do fragment_count = fragment_count + 1 fragment_list[fragment_count] = fragment end @@ -6001,49 +6065,58 @@ function __builder_mt:__tostring() end ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:build(prefab) +function __builder_mt:build(prefab, component_mapper) if prefab then - return self:clone(prefab) + return self:clone(prefab, component_mapper) else - return self:spawn() + return self:spawn(component_mapper) end end ---@param entity_count integer ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_build(entity_count, prefab) +function __builder_mt:multi_build(entity_count, prefab, component_mapper) if prefab then - return self:multi_clone(entity_count, prefab) + return self:multi_clone(entity_count, prefab, component_mapper) else - return self:multi_spawn(entity_count) + return self:multi_spawn(entity_count, component_mapper) end end +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:spawn() +function __builder_mt:spawn(component_mapper) local chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end local entity = __acquire_id() + if not component_table or not __lua_next(component_table) then + return entity + end + if __defer_depth > 0 then - __defer_spawn_entity(chunk, entity, components) + __defer_spawn_entity(chunk, entity, component_table, component_mapper) else __evolved_defer() do - __spawn_entity(chunk, entity, components) + __spawn_entity(chunk, entity, component_table, component_mapper) end __evolved_commit() end @@ -6052,21 +6125,24 @@ function __builder_mt:spawn() end ---@param entity_count integer +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_spawn(entity_count) +function __builder_mt:multi_spawn(entity_count, component_mapper) if entity_count <= 0 then return {}, 0 end local chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -6077,12 +6153,16 @@ function __builder_mt:multi_spawn(entity_count) entity_list[entity_index] = __acquire_id() end + if not component_table or not __lua_next(component_table) then + return entity_list, entity_count + end + if __defer_depth > 0 then - __defer_multi_spawn_entity(chunk, entity_list, entity_count, components) + __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_spawn_entity(chunk, entity_list, entity_count, components) + __multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -6091,9 +6171,10 @@ function __builder_mt:multi_spawn(entity_count) end ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:clone(prefab) - local components = self.__components +function __builder_mt:clone(prefab, component_mapper) + local component_table = self.__component_table if __debug_mode then if not __evolved_alive(prefab) then @@ -6101,10 +6182,12 @@ function __builder_mt:clone(prefab) __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -6112,11 +6195,11 @@ function __builder_mt:clone(prefab) local entity = __acquire_id() if __defer_depth > 0 then - __defer_clone_entity(prefab, entity, components) + __defer_clone_entity(prefab, entity, component_table, component_mapper) else __evolved_defer() do - __clone_entity(prefab, entity, components) + __clone_entity(prefab, entity, component_table, component_mapper) end __evolved_commit() end @@ -6126,14 +6209,15 @@ end ---@param entity_count integer ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_clone(entity_count, prefab) +function __builder_mt:multi_clone(entity_count, prefab, component_mapper) if entity_count <= 0 then return {}, 0 end - local components = self.__components + local component_table = self.__component_table if __debug_mode then if not __evolved_alive(prefab) then @@ -6141,10 +6225,12 @@ function __builder_mt:multi_clone(entity_count, prefab) __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -6156,11 +6242,11 @@ function __builder_mt:multi_clone(entity_count, prefab) end if __defer_depth > 0 then - __defer_multi_clone_entity(prefab, entity_list, entity_count, components) + __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_clone_entity(prefab, entity_list, entity_count, components) + __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -6217,7 +6303,7 @@ function __builder_mt:get(...) return end - local cs = self.__components + local cs = self.__component_table if fragment_count == 1 then local f1 = ... @@ -6260,7 +6346,7 @@ function __builder_mt:set(fragment, component) end local new_chunk = __chunk_with_fragment(self.__chunk, fragment) - local components = self.__components + local component_table = self.__component_table if new_chunk.__has_setup_hooks then ---@type evolved.default?, evolved.duplicate? @@ -6272,12 +6358,12 @@ function __builder_mt:set(fragment, 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 - components[fragment] = new_component + component_table[fragment] = new_component else local new_component = component if new_component == nil then new_component = true end - components[fragment] = new_component + component_table[fragment] = new_component end self.__chunk = new_chunk @@ -6294,12 +6380,12 @@ function __builder_mt:remove(...) end local new_chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table for fragment_index = 1, fragment_count do ---@type evolved.fragment local fragment = __lua_select(fragment_index, ...) - new_chunk, components[fragment] = __chunk_without_fragment(new_chunk, fragment), nil + new_chunk, component_table[fragment] = __chunk_without_fragment(new_chunk, fragment), nil end self.__chunk = new_chunk @@ -6309,7 +6395,7 @@ end ---@return evolved.builder builder function __builder_mt:clear() self.__chunk = nil - __lua_table_clear(self.__components) + __lua_table_clear(self.__component_table) return self end diff --git a/example/main.lua b/example/main.lua index a7ac0c6..84abfec 100644 --- a/example/main.lua +++ b/example/main.lua @@ -52,25 +52,28 @@ evolved.builder() :name('SYSTEMS.STARTUP') :group(STAGES.ON_SETUP) :prologue(function() - local screen_width, screen_height = love.graphics.getDimensions() - - local circle_list, circle_count = evolved.multi_clone(100, PREFABS.CIRCLE) + evolved.multi_clone(500, PREFABS.CIRCLE, nil, function(chunk, b_place, e_place) + local screen_width, screen_height = love.graphics.getDimensions() - for i = 1, circle_count do - local circle = circle_list[i] + ---@type number[], number[] + local position_xs, position_ys = chunk:components( + FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) - local px = math.random() * screen_width - local py = math.random() * screen_height + ---@type number[], number[] + local velocity_xs, velocity_ys = chunk:components( + FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y) - local vx = math.random(-100, 100) - local vy = math.random(-100, 100) + for i = b_place, e_place do + local px = math.random() * screen_width + local py = math.random() * screen_height - evolved.set(circle, FRAGMENTS.POSITION_X, px) - evolved.set(circle, FRAGMENTS.POSITION_Y, py) + local vx = math.random(-100, 100) + local vy = math.random(-100, 100) - evolved.set(circle, FRAGMENTS.VELOCITY_X, vx) - evolved.set(circle, FRAGMENTS.VELOCITY_Y, vy) - end + position_xs[i], position_ys[i] = px, py + velocity_xs[i], velocity_ys[i] = vx, vy + end + end) end):build() evolved.builder() @@ -124,7 +127,7 @@ evolved.builder() for i = 1, entity_count do local x, y = position_xs[i], position_ys[i] - love.graphics.circle('fill', x, y, 10) + love.graphics.circle('fill', x, y, 5) end end):build() From c3760c40bf661101f1ab08e93bc1e02ad8c8e647 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 18 Jan 2026 20:20:13 +0700 Subject: [PATCH 12/13] update README --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ evolved.d.tl | 52 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c93b7bc..95503aa 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ - [Structural Changes](#structural-changes) - [Spawning Entities](#spawning-entities) - [Entity Builders](#entity-builders) + - [Multi-Entity Spawning](#multi-entity-spawning) - [Access Operations](#access-operations) - [Iterating Over Fragments](#iterating-over-fragments) - [Modifying Operations](#modifying-operations) @@ -476,6 +477,54 @@ local enemy = evolved.builder() Builders can be reused, so you can create a builder with a specific set of fragments and components and then use it to spawn multiple entities with the same fragments and components. +#### Multi-Entity Spawning + +When you need to spawn multiple entities with identical fragments, use `multi_spawn` and `multi_clone` to optimize performance and reduce overhead. + +```lua +---@param entity_count integer +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +---@return evolved.entity[] entity_list +---@return integer entity_count +function evolved.multi_spawn(entity_count, component_table, component_mapper) end + +---@param entity_count integer +---@param prefab evolved.entity +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +---@return evolved.entity[] entity_list +---@return integer entity_count +function evolved.multi_clone(entity_count, prefab, component_table, component_mapper) end +``` + +These functions behave like their single-entity counterparts, but they allow you to spawn or clone multiple entities in one call. This approach minimizes the overhead of repeated function calls and structural changes, improving performance when handling large numbers of entities. + +Typically, when spawning multiple entities, they share the same set of fragments, but their components can differ. You can achieve this by providing a `component_mapper` function, which receives the chunk and the range of places for the newly spawned entities. This avoids many `evolved.set` calls after spawning, which can be costly when creating many entities. + +Here is a small example of using `evolved.multi_spawn` with a `component_mapper`: + +```lua +local evolved = require 'evolved' + +local position_x, position_y = evolved.id(2) + +evolved.multi_spawn(1000, { + [position_x] = 0, + [position_y] = 0, +}, function(chunk, b_place, e_place) + local x_components = chunk:components(position_x) + local y_components = chunk:components(position_y) + + for i = b_place, e_place do + x_components[i] = math.random(-100, 100) + y_components[i] = math.random(-100, 100) + end +end) +``` + +Of course, you can use `evolved.multi_clone` in the same way. Builders can also be used for multi-entity spawning and cloning by calling the corresponding methods on the builder object. + ### Access Operations The library provides all the necessary functions to access entities and their components. I'm not going to cover all the accessor functions here, because they are pretty straightforward and self-explanatory. You can check the [API Reference](#api-reference) for all of them. Here are some of the most important ones: @@ -1519,6 +1568,7 @@ builder_mt:destruction_policy :: id -> builder ### vX.Y.Z - Added the new [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove) fragment traits that allow customizing component storages +- Added `component_mapper` argument to the spawning and cloning functions that allows filling components in chunks during the operation ### v1.7.0 diff --git a/evolved.d.tl b/evolved.d.tl index 1cc3b78..54d3ac4 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -31,14 +31,30 @@ end interface Builder - build: function(self: Builder, prefab?: Entity): Entity - multi_build: function(self: Builder, entity_count: integer, prefab?: Entity): { Entity }, integer + build: function(self: Builder, + prefab?: Entity, + component_mapper?: function(Chunk, integer)): Entity - spawn: function(self: Builder): Entity - multi_spawn: function(self: Builder, entity_count: integer): { Entity }, integer + multi_build: function(self: Builder, + entity_count: integer, + prefab?: Entity, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer - clone: function(self: Builder, prefab: Entity): Entity - multi_clone: function(self: Builder, entity_count: integer, prefab: Entity): { Entity }, integer + spawn: function(self: Builder, + component_mapper?: function(Chunk, integer)): Entity + + multi_spawn: function(self: Builder, + entity_count: integer, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer + + clone: function(self: Builder, + prefab: Entity, + component_mapper?: function(Chunk, integer)): Entity + + multi_clone: function(self: Builder, + entity_count: integer, + prefab: Entity, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer has: function(self: Builder, fragment: Fragment): boolean has_all: function(self: Builder, ...: Fragment): boolean @@ -139,11 +155,25 @@ commit: function(): boolean cancel: function(): boolean - spawn: function(components?: { Fragment: any }): Entity - multi_spawn: function(entity_count: integer, components?: { Fragment: any }): { Entity }, integer - - clone: function(prefab: Entity, components?: { Fragment: any }): Entity - multi_clone: function(entity_count: integer, prefab: Entity, components?: { Fragment: any }): { Entity }, integer + spawn: function( + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer)): Entity + + multi_spawn: function( + entity_count: integer, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer + + clone: function( + prefab: Entity, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer)): Entity + + multi_clone: function( + entity_count: integer, + prefab: Entity, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer alive: function(entity: Entity): boolean alive_all: function(...: Entity): boolean From e9824a4776f6c96ae9f5da75ed78899c735fc212 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 18 Jan 2026 20:24:53 +0700 Subject: [PATCH 13/13] v1.8.0 --- README.md | 4 +-- evolved.lua | 2 +- rockspecs/evolved.lua-1.8.0-0.rockspec | 34 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 rockspecs/evolved.lua-1.8.0-0.rockspec diff --git a/README.md b/README.md index 95503aa..d738460 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) - - [vX.Y.Z](#vxyz) + - [v1.8.0](#v180) - [v1.7.0](#v170) - [v1.6.0](#v160) - [v1.5.0](#v150) @@ -1565,7 +1565,7 @@ builder_mt:destruction_policy :: id -> builder ## Changelog -### vX.Y.Z +### v1.8.0 - Added the new [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove) fragment traits that allow customizing component storages - Added `component_mapper` argument to the spawning and cloning functions that allows filling components in chunks during the operation diff --git a/evolved.lua b/evolved.lua index d69b4d2..6562d7f 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1,7 +1,7 @@ local evolved = { __HOMEPAGE = 'https://github.com/BlackMATov/evolved.lua', __DESCRIPTION = 'Evolved ECS (Entity-Component-System) for Lua', - __VERSION = '1.7.0', + __VERSION = '1.8.0', __LICENSE = [[ MIT License diff --git a/rockspecs/evolved.lua-1.8.0-0.rockspec b/rockspecs/evolved.lua-1.8.0-0.rockspec new file mode 100644 index 0000000..96be015 --- /dev/null +++ b/rockspecs/evolved.lua-1.8.0-0.rockspec @@ -0,0 +1,34 @@ +rockspec_format = "3.0" +package = "evolved.lua" +version = "1.8.0-0" +source = { + url = "git://github.com/BlackMATov/evolved.lua", + tag = "v1.8.0", +} +description = { + homepage = "https://github.com/BlackMATov/evolved.lua", + summary = "Evolved ECS (Entity-Component-System) for Lua", + detailed = [[ + `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. + It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. + ]], + license = "MIT", + labels = { + "ecs", + "entity", + "entities", + "component", + "components", + "entity-component", + "entity-component-system", + }, +} +dependencies = { + "lua >= 5.1", +} +build = { + type = "builtin", + modules = { + evolved = "evolved.lua", + } +}