From efc644f180584038a925c10b386ee16a6dea6362 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 00:45:25 +0700 Subject: [PATCH 01/23] simple benchmarks template --- develop/unbench.lua | 63 +++++++++++++++++++++++++++++++++++++++++++++ develop/untests.lua | 1 + 2 files changed, 64 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index 2099a1a..48a19e7 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -1 +1,64 @@ +package.loaded['evolved'] = nil local evo = require 'evolved' + +local __table_pack = (function() + return table.pack or function(...) + return { n = select('#', ...), ... } + end +end)() + +local __table_unpack = (function() + return table.unpack or unpack +end)() + +---@param name string +---@param loop fun(...): ... +---@param init? fun(): ... +local function __bench_describe(name, loop, init) + collectgarbage('collect') + collectgarbage('stop') + + print(string.format('| %s ... |', name)) + + local iters = 0 + local state = init and __table_pack(init()) or {} + + local start_s = os.clock() + local start_kb = collectgarbage('count') + + local success, result = pcall(function() + repeat + iters = iters + 1 + loop(__table_unpack(state)) + until os.clock() - start_s > 0.2 + end) + + local finish_s = os.clock() + local finish_kb = collectgarbage('count') + + print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f', + success and 'PASS' or 'FAIL', + (finish_s - start_s) * 1e6 / iters, + iters / (finish_s - start_s), + (finish_kb - start_kb) / iters)) + + if not success then print(' ' .. result) end + + collectgarbage('restart') + collectgarbage('collect') +end + +__bench_describe('create and destroy 10k evolved ids', function() + local id = evo.id + local destroy = evo.destroy + + local ids = {} + + for i = 1, 10000 do + ids[i] = id() + end + + for i = 1, 10000 do + destroy(ids[i]) + end +end) diff --git a/develop/untests.lua b/develop/untests.lua index c979982..8a3e06e 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -1,3 +1,4 @@ +package.loaded['evolved'] = nil local evo = require 'evolved' do From 5476d79586c74fcce3bd40cd96c6130de2243e09 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 02:12:07 +0700 Subject: [PATCH 02/23] first brute-force insert/destroy benches --- develop/unbench.lua | 110 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/develop/unbench.lua b/develop/unbench.lua index 48a19e7..aa73f81 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -48,17 +48,113 @@ local function __bench_describe(name, loop, init) collectgarbage('collect') end -__bench_describe('create and destroy 10k evolved ids', function() +---@param entities evolved.id[] +__bench_describe('create and destroy 1k entities', function(entities) local id = evo.id local destroy = evo.destroy - local ids = {} - - for i = 1, 10000 do - ids[i] = id() + for i = 1, 1000 do + local e = id() + entities[i] = e end - for i = 1, 10000 do - destroy(ids[i]) + for i = 1, #entities do + destroy(entities[i]) end +end, function() + return {} end) + +---@param f1 evolved.fragment +---@param entities evolved.id[] +__bench_describe('create and destroy 1k entities with one component', function(f1, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, 1000 do + local e = id() + entities[i] = e + + insert(e, f1) + end + + for i = 1, #entities do + destroy(entities[i]) + end +end, function() + local f1 = evo.id(2) + return f1, {} +end) + +---@param f1 evolved.fragment +---@param f2 evolved.fragment +---@param entities evolved.id[] +__bench_describe('create and destroy 1k entities with two components', function(f1, f2, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, 1000 do + local e = id() + entities[i] = e + + insert(e, f1) + insert(e, f2) + end + + for i = 1, #entities do + destroy(entities[i]) + end +end, function() + local f1, f2 = evo.id(2) + return f1, f2, {} +end) + +---@param f1 evolved.fragment +---@param f2 evolved.fragment +---@param f3 evolved.fragment +---@param entities evolved.id[] +__bench_describe('create and destroy 1k entities with three components', function(f1, f2, f3, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, 1000 do + local e = id() + entities[i] = e + + insert(e, f1) + insert(e, f2) + insert(e, f3) + end + + for i = 1, #entities do + destroy(entities[i]) + end +end, function() + local f1, f2, f3 = evo.id(3) + return f1, f2, f3, {} +end) + +--[[ lua 5.1 +| create and destroy 1k entities ... | + PASS | us: 312.60 | op/s: 3199.00 | kb/i: 0.05 +| create and destroy 1k entities with one component ... | + PASS | us: 1570.31 | op/s: 636.82 | kb/i: 0.63 +| create and destroy 1k entities with two components ... | + PASS | us: 2780.82 | op/s: 359.61 | kb/i: 0.91 +| create and destroy 1k entities with three components ... | + PASS | us: 4060.00 | op/s: 246.31 | kb/i: 1.67 +]] + +--[[ luajit 2.1 +| create and destroy 1k entities ... | + PASS | us: 12.22 | op/s: 81840.80 | kb/i: 0.00 +| create and destroy 1k entities with one component ... | + PASS | us: 56.22 | op/s: 17786.07 | kb/i: 0.02 +| create and destroy 1k entities with two components ... | + PASS | us: 412.73 | op/s: 2422.89 | kb/i: 0.11 +| create and destroy 1k entities with three components ... | + PASS | us: 611.62 | op/s: 1635.00 | kb/i: 0.17 +]] From 1cd0414517718375436399289bf5af08e74fbb50 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 02:50:51 +0700 Subject: [PATCH 03/23] opt: unpack ids without dedicated functions --- evolved.lua | 227 ++++++++++++++++++++++++---------------------------- 1 file changed, 105 insertions(+), 122 deletions(-) diff --git a/evolved.lua b/evolved.lua index 0875d02..f8865f3 100644 --- a/evolved.lua +++ b/evolved.lua @@ -149,39 +149,6 @@ local __table_clear = (function() end end)() - ---- ---- ---- ---- ---- - ----@param index integer ----@param version integer ----@return evolved.id ----@nodiscard -local function __pack_id(index, version) - if index < 1 or index > 0xFFFFF then - error('id index out of range [1;0xFFFFF]', 2) - end - - if version < 1 or version > 0x7FF then - error('id version out of range [1;0x7FF]', 2) - end - - return index + version * 0x100000 -end - ----@param id evolved.id ----@return integer index ----@return integer version ----@nodiscard -local function __unpack_id(id) - local index = id % 0x100000 - local version = (id - index) / 0x100000 - return index, version -end - --- --- --- @@ -214,14 +181,6 @@ local function __acquire_id() end end ----@param id evolved.id ----@return boolean ----@nodiscard -local function __is_id_alive(id) - local index = id % 0x100000 - return __freelist_ids[index] == id -end - ---@param id evolved.id local function __release_id(id) local index = id % 0x100000 @@ -948,9 +907,9 @@ local function __chunk_insert(chunk, fragment, ...) for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] - local index = __unpack_id(entity) - __entity_chunks[index] = new_chunk - __entity_places[index] = new_place + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place end do @@ -1025,16 +984,16 @@ local function __chunk_remove(chunk, ...) for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] - local index = __unpack_id(entity) - __entity_chunks[index] = new_chunk - __entity_places[index] = new_place + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place end else for old_place = 1, old_chunk_size do local entity = old_chunk_entities[old_place] - local index = __unpack_id(entity) - __entity_chunks[index] = nil - __entity_places[index] = nil + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = nil + __entity_places[entity_index] = nil end end @@ -1084,9 +1043,9 @@ local function __chunk_clear(chunk) for place = 1, chunk_size do local entity = chunk_entities[place] - local index = __unpack_id(entity) - __entity_chunks[index] = nil - __entity_places[index] = nil + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = nil + __entity_places[entity_index] = nil end do @@ -1135,9 +1094,9 @@ local function __chunk_destroy(chunk) for place = 1, chunk_size do local entity = chunk_entities[place] - local index = __unpack_id(entity) - __entity_chunks[index] = nil - __entity_places[index] = nil + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = nil + __entity_places[entity_index] = nil __release_id(entity) end @@ -1161,9 +1120,9 @@ end ---@param entity evolved.entity local function __detach_entity(entity) - local index = __unpack_id(entity) + local entity_index = entity % 0x100000 - local old_chunk = __entity_chunks[index] + local old_chunk = __entity_chunks[entity_index] if not old_chunk then return @@ -1172,7 +1131,7 @@ local function __detach_entity(entity) local old_chunk_entities = old_chunk.__entities local old_chunk_components = old_chunk.__components - local old_place = __entity_places[index] + local old_place = __entity_places[entity_index] local old_chunk_size = #old_chunk_entities if old_place == old_chunk_size then @@ -1183,7 +1142,8 @@ local function __detach_entity(entity) end else local last_chunk_entity = old_chunk_entities[old_chunk_size] - __entity_places[__unpack_id(last_chunk_entity)] = old_place + local last_chunk_entity_index = last_chunk_entity % 0x100000 + __entity_places[last_chunk_entity_index] = old_place old_chunk_entities[old_place] = last_chunk_entity old_chunk_entities[old_chunk_size] = nil @@ -1195,8 +1155,8 @@ local function __detach_entity(entity) end end - __entity_chunks[index] = nil - __entity_places[index] = nil + __entity_chunks[entity_index] = nil + __entity_places[entity_index] = nil __structural_changes = __structural_changes + 1 end @@ -1588,7 +1548,15 @@ end ---@return evolved.id ---@nodiscard function evolved.pack(index, version) - return __pack_id(index, version) + if index < 1 or index > 0xFFFFF then + error('id index out of range [1;0xFFFFF]', 2) + end + + if version < 1 or version > 0x7FF then + error('id version out of range [1;0x7FF]', 2) + end + + return index + version * 0x100000 end ---@param id evolved.id @@ -1596,7 +1564,9 @@ end ---@return integer version ---@nodiscard function evolved.unpack(id) - return __unpack_id(id) + local index = id % 0x100000 + local version = (id - index) / 0x100000 + return index, version end ---@return boolean started @@ -1613,15 +1583,22 @@ end ---@return boolean ---@nodiscard function evolved.is_alive(entity) - return __is_id_alive(entity) + local entity_index = entity % 0x100000 + + return __freelist_ids[entity_index] == entity end ---@param entity evolved.entity ---@return boolean ---@nodiscard function evolved.is_empty(entity) - return not __is_id_alive(entity) - or not __entity_chunks[__unpack_id(entity)] + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then + return true + end + + return not __entity_chunks[entity_index] end ---@param entity evolved.entity @@ -1629,18 +1606,19 @@ end ---@return evolved.component ... components ---@nodiscard function evolved.get(entity, ...) - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return end - local index = __unpack_id(entity) - local chunk = __entity_chunks[index] + local chunk = __entity_chunks[entity_index] if not chunk then return end - local place = __entity_places[index] + local place = __entity_places[entity_index] return __chunk_get_components(chunk, place, ...) end @@ -1649,12 +1627,13 @@ end ---@return boolean ---@nodiscard function evolved.has(entity, fragment) - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false end - local index = __unpack_id(entity) - local chunk = __entity_chunks[index] + local chunk = __entity_chunks[entity_index] if not chunk then return false @@ -1668,12 +1647,13 @@ end ---@return boolean ---@nodiscard function evolved.has_all(entity, ...) - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false end - local index = __unpack_id(entity) - local chunk = __entity_chunks[index] + local chunk = __entity_chunks[entity_index] if not chunk then return select('#', ...) == 0 @@ -1687,12 +1667,13 @@ end ---@return boolean ---@nodiscard function evolved.has_any(entity, ...) - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false end - local index = __unpack_id(entity) - local chunk = __entity_chunks[index] + local chunk = __entity_chunks[entity_index] if not chunk then return false @@ -1712,14 +1693,14 @@ function evolved.set(entity, fragment, ...) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false, false end - local index = __unpack_id(entity) - - local old_chunk = __entity_chunks[index] - local old_place = __entity_places[index] + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) local new_place = #new_chunk.__entities + 1 @@ -1769,8 +1750,8 @@ function evolved.set(entity, fragment, ...) __detach_entity(entity) end - __entity_chunks[index] = new_chunk - __entity_places[index] = new_place + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -1789,14 +1770,14 @@ function evolved.assign(entity, fragment, ...) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false, false end - local index = __unpack_id(entity) - - local chunk = __entity_chunks[index] - local place = __entity_places[index] + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] if not chunk or not chunk.__fragments[fragment] then return false, false @@ -1828,14 +1809,14 @@ function evolved.insert(entity, fragment, ...) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false, false end - local index = __unpack_id(entity) - - local old_chunk = __entity_chunks[index] - local old_place = __entity_places[index] + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) local new_place = #new_chunk.__entities + 1 @@ -1873,8 +1854,8 @@ function evolved.insert(entity, fragment, ...) __detach_entity(entity) end - __entity_chunks[index] = new_chunk - __entity_places[index] = new_place + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -1892,14 +1873,14 @@ function evolved.remove(entity, ...) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false, false end - local index = __unpack_id(entity) - - local old_chunk = __entity_chunks[index] - local old_place = __entity_places[index] + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] local new_chunk = __chunk_without_fragments(old_chunk, ...) @@ -1942,8 +1923,8 @@ function evolved.remove(entity, ...) __detach_entity(entity) - __entity_chunks[index] = new_chunk - __entity_places[index] = new_place + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place else __detach_entity(entity) end @@ -1963,14 +1944,14 @@ function evolved.clear(entity) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return false, false end - local index = __unpack_id(entity) - - local chunk = __entity_chunks[index] - local place = __entity_places[index] + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] if not chunk then return true, false @@ -2010,14 +1991,14 @@ function evolved.destroy(entity) return false, true end - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return true, false end - local index = __unpack_id(entity) - - local chunk = __entity_chunks[index] - local place = __entity_places[index] + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] if not chunk then __release_id(entity) @@ -2437,14 +2418,14 @@ end ---@return evolved.each_state? ---@nodiscard function evolved.each(entity) - if not __is_id_alive(entity) then + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then return __each_iterator end - local index = __unpack_id(entity) - - local chunk = __entity_chunks[index] - local place = __entity_places[index] + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] if not chunk then return __each_iterator @@ -2466,7 +2447,9 @@ end ---@return evolved.execute_state? ---@nodiscard function evolved.execute(query) - if not __is_id_alive(query) then + local query_index = query % 0x100000 + + if __freelist_ids[query_index] ~= query then return __execute_iterator end From ea7199cb11db26b4d3826229898a1bf1144ffe0f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 02:53:06 +0700 Subject: [PATCH 04/23] update bench results --- develop/unbench.lua | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index aa73f81..edc8dd5 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -137,6 +137,10 @@ end, function() return f1, f2, f3, {} end) +--- +--- initial +--- + --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 312.60 | op/s: 3199.00 | kb/i: 0.05 @@ -158,3 +162,29 @@ end) | create and destroy 1k entities with three components ... | PASS | us: 611.62 | op/s: 1635.00 | kb/i: 0.17 ]] + +--- +--- unpack ids without dedicated functions +--- + +--[[ lua 5.1 +| create and destroy 1k entities ... | + PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 +| create and destroy 1k entities with one component ... | + PASS | us: 1248.45 | op/s: 801.00 | kb/i: 0.50 +| create and destroy 1k entities with two components ... | + PASS | us: 2208.79 | op/s: 452.74 | kb/i: 0.73 +| create and destroy 1k entities with three components ... | + PASS | us: 3278.69 | op/s: 305.00 | kb/i: 1.37 +]] + +--[[ luajit 2.1 +| create and destroy 1k entities ... | + PASS | us: 12.12 | op/s: 82482.59 | kb/i: 0.00 +| create and destroy 1k entities with one component ... | + PASS | us: 69.05 | op/s: 14482.59 | kb/i: 0.03 +| create and destroy 1k entities with two components ... | + PASS | us: 400.40 | op/s: 2497.51 | kb/i: 0.09 +| create and destroy 1k entities with three components ... | + PASS | us: 574.71 | op/s: 1740.00 | kb/i: 0.14 +]] From 0fd07295a66f07b825544f45ecfaa11a11904bab Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 03:34:29 +0700 Subject: [PATCH 05/23] opt: has_set/assign/insert/remove_hooks flags for chunks --- ROADMAP.md | 1 - evolved.lua | 231 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 140 insertions(+), 92 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index db86606..1a8299f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,6 @@ - should insert/assign throw errors on failure? - add auto chunk count reducing -- every chunk can hold has_on_assign/has_on_insert/has_on_remove tags - optimize batch operations for cases with moving entities to empty chunks - should we clear chunk's components by on_insert tag callback? - replace id type aliases with separated types to hide implementation details diff --git a/evolved.lua b/evolved.lua index f8865f3..5d66187 100644 --- a/evolved.lua +++ b/evolved.lua @@ -42,6 +42,9 @@ local evolved = { ---@field package __components table ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table +---@field package __has_set_or_assign_hooks boolean +---@field package __has_set_or_insert_hooks boolean +---@field package __has_remove_hooks boolean ---@class (exact) evolved.each_state ---@field package [1] integer structural_changes @@ -368,7 +371,7 @@ end ---@param fragment evolved.fragment ---@param new_component evolved.component ---@param old_component evolved.component -local function __fragment_on_set_and_assign(entity, fragment, new_component, old_component) +local function __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) local on_set, on_assign = evolved.get(fragment, evolved.ON_SET, evolved.ON_ASSIGN) if on_set then on_set(entity, fragment, new_component, old_component) end if on_assign then on_assign(entity, fragment, new_component, old_component) end @@ -377,7 +380,7 @@ end ---@param entity evolved.entity ---@param fragment evolved.fragment ---@param new_component evolved.component -local function __fragment_on_set_and_insert(entity, fragment, new_component) +local function __fragment_call_set_and_insert_hooks(entity, fragment, new_component) local on_set, on_insert = evolved.get(fragment, evolved.ON_SET, evolved.ON_INSERT) if on_set then on_set(entity, fragment, new_component) end if on_insert then on_insert(entity, fragment, new_component) end @@ -386,7 +389,7 @@ end ---@param entity evolved.entity ---@param fragment evolved.fragment ---@param old_component evolved.component -local function __fragment_on_remove(entity, fragment, old_component) +local function __fragment_call_remove_hook(entity, fragment, old_component) local on_remove = evolved.get(fragment, evolved.ON_REMOVE) if on_remove then on_remove(entity, fragment, old_component) end end @@ -401,21 +404,21 @@ end ---@param fragment evolved.fragment ---@return boolean ---@nodiscard -local function __fragment_has_on_set_or_assign(fragment) +local function __fragment_has_set_or_assign_hooks(fragment) return evolved.has_any(fragment, evolved.ON_SET, evolved.ON_ASSIGN) end ---@param fragment evolved.fragment ---@return boolean ---@nodiscard -local function __fragment_has_on_set_or_insert(fragment) +local function __fragment_has_set_or_insert_hooks(fragment) return evolved.has_any(fragment, evolved.ON_SET, evolved.ON_INSERT) end ---@param fragment evolved.fragment ---@return boolean ---@nodiscard -local function __fragment_has_on_remove(fragment) +local function __fragment_has_remove_hook(fragment) return evolved.has(fragment, evolved.ON_REMOVE) end @@ -434,6 +437,10 @@ local function __root_chunk(fragment) if root_chunk then return root_chunk end end + local has_set_or_assign_hooks = __fragment_has_set_or_assign_hooks(fragment) + local has_set_or_insert_hooks = __fragment_has_set_or_insert_hooks(fragment) + local has_remove_hooks = __fragment_has_remove_hook(fragment) + ---@type evolved.chunk local root_chunk = { __parent = nil, @@ -444,6 +451,9 @@ local function __root_chunk(fragment) __components = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, + __has_set_or_assign_hooks = has_set_or_assign_hooks, + __has_set_or_insert_hooks = has_set_or_insert_hooks, + __has_remove_hooks = has_remove_hooks, } do @@ -506,6 +516,10 @@ local function __chunk_with_fragment(chunk, fragment) return sibling_chunk end + local has_set_or_assign_hooks = chunk.__has_set_or_assign_hooks or __fragment_has_set_or_assign_hooks(fragment) + local has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks or __fragment_has_set_or_insert_hooks(fragment) + local has_remove_hooks = chunk.__has_remove_hooks or __fragment_has_remove_hook(fragment) + ---@type evolved.chunk local child_chunk = { __parent = chunk, @@ -516,6 +530,9 @@ local function __chunk_with_fragment(chunk, fragment) __components = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, + __has_set_or_assign_hooks = has_set_or_assign_hooks, + __has_set_or_insert_hooks = has_set_or_insert_hooks, + __has_remove_hooks = has_remove_hooks, } do @@ -758,7 +775,7 @@ local function __chunk_assign(chunk, fragment, ...) local chunk_size = #chunk_entities local chunk_fragment_components = chunk_components[fragment] - if __fragment_has_on_set_or_assign(fragment) then + if chunk.__has_set_or_assign_hooks and __fragment_has_set_or_assign_hooks(fragment) then if chunk_fragment_components then if __fragment_has_default_or_construct(fragment) then for place = 1, chunk_size do @@ -766,7 +783,7 @@ local function __chunk_assign(chunk, fragment, ...) local old_component = chunk_fragment_components[place] local new_component = __component_construct(entity, fragment, ...) chunk_fragment_components[place] = new_component - __fragment_on_set_and_assign(entity, fragment, new_component, old_component) + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) end else local new_component = ... @@ -779,13 +796,13 @@ local function __chunk_assign(chunk, fragment, ...) local entity = chunk_entities[place] local old_component = chunk_fragment_components[place] chunk_fragment_components[place] = new_component - __fragment_on_set_and_assign(entity, fragment, new_component, old_component) + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) end end else for place = 1, chunk_size do local entity = chunk_entities[place] - __fragment_on_set_and_assign(entity, fragment) + __fragment_call_set_and_assign_hooks(entity, fragment) end end else @@ -853,14 +870,14 @@ local function __chunk_insert(chunk, fragment, ...) end end - if __fragment_has_on_set_or_insert(fragment) then + if new_chunk.__has_set_or_insert_hooks and __fragment_has_set_or_insert_hooks(fragment) then if new_chunk_fragment_components then if __fragment_has_default_or_construct(fragment) then for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] local new_component = __component_construct(entity, fragment, ...) new_chunk_fragment_components[new_place] = new_component - __fragment_on_set_and_insert(entity, fragment, new_component) + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) end else local new_component = ... @@ -872,13 +889,13 @@ local function __chunk_insert(chunk, fragment, ...) for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] new_chunk_fragment_components[new_place] = new_component - __fragment_on_set_and_insert(entity, fragment, new_component) + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) end end else for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] - __fragment_on_set_and_insert(entity, fragment) + __fragment_call_set_and_insert_hooks(entity, fragment) end end else @@ -946,20 +963,24 @@ local function __chunk_remove(chunk, ...) local old_chunk_size = #old_chunk_entities - for i = 1, select('#', ...) do - local fragment = select(i, ...) - if old_chunk_fragments[fragment] and __fragment_has_on_remove(fragment) then - local old_chunk_fragment_components = old_chunk_components[fragment] - if old_chunk_fragment_components then - for old_place = 1, old_chunk_size do - local entity = old_chunk_entities[old_place] - local old_component = old_chunk_fragment_components[old_place] - __fragment_on_remove(entity, fragment, old_component) - end - else - for old_place = 1, old_chunk_size do - local entity = old_chunk_entities[old_place] - __fragment_on_remove(entity, fragment) + if old_chunk.__has_remove_hooks then + for i = 1, select('#', ...) do + local fragment = select(i, ...) + if old_chunk_fragments[fragment] then + if __fragment_has_remove_hook(fragment) then + local old_chunk_fragment_components = old_chunk_components[fragment] + if old_chunk_fragment_components then + for old_place = 1, old_chunk_size do + local entity = old_chunk_entities[old_place] + local old_component = old_chunk_fragment_components[old_place] + __fragment_call_remove_hook(entity, fragment, old_component) + end + else + for old_place = 1, old_chunk_size do + local entity = old_chunk_entities[old_place] + __fragment_call_remove_hook(entity, fragment) + end + end end end end @@ -1023,19 +1044,21 @@ local function __chunk_clear(chunk) local chunk_size = #chunk_entities - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_on_remove(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - for place = 1, chunk_size do - local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] - __fragment_on_remove(entity, fragment, old_component) - end - else - for place = 1, chunk_size do - local entity = chunk_entities[place] - __fragment_on_remove(entity, fragment) + if chunk.__has_remove_hooks then + for fragment, _ in pairs(chunk_fragments) do + if __fragment_has_remove_hook(fragment) then + local chunk_fragment_components = chunk_components[fragment] + if chunk_fragment_components then + for place = 1, chunk_size do + local entity = chunk_entities[place] + local old_component = chunk_fragment_components[place] + __fragment_call_remove_hook(entity, fragment, old_component) + end + else + for place = 1, chunk_size do + local entity = chunk_entities[place] + __fragment_call_remove_hook(entity, fragment) + end end end end @@ -1074,19 +1097,21 @@ local function __chunk_destroy(chunk) local chunk_size = #chunk_entities - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_on_remove(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - for place = 1, chunk_size do - local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] - __fragment_on_remove(entity, fragment, old_component) - end - else - for place = 1, chunk_size do - local entity = chunk_entities[place] - __fragment_on_remove(entity, fragment) + if chunk.__has_remove_hooks then + for fragment, _ in pairs(chunk_fragments) do + if __fragment_has_remove_hook(fragment) then + local chunk_fragment_components = chunk_components[fragment] + if chunk_fragment_components then + for place = 1, chunk_size do + local entity = chunk_entities[place] + local old_component = chunk_fragment_components[place] + __fragment_call_remove_hook(entity, fragment, old_component) + end + else + for place = 1, chunk_size do + local entity = chunk_entities[place] + __fragment_call_remove_hook(entity, fragment) + end end end end @@ -1713,9 +1738,13 @@ function evolved.set(entity, fragment, ...) local old_component = old_chunk_fragment_components[old_place] local new_component = __component_construct(entity, fragment, ...) old_chunk_fragment_components[old_place] = new_component - __fragment_on_set_and_assign(entity, fragment, new_component, old_component) + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end else - __fragment_on_set_and_assign(entity, fragment) + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment) + end end return true, false @@ -1732,9 +1761,13 @@ function evolved.set(entity, fragment, ...) if new_chunk_fragment_components then local new_component = __component_construct(entity, fragment, ...) new_chunk_fragment_components[new_place] = new_component - __fragment_on_set_and_insert(entity, fragment, new_component) + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end else - __fragment_on_set_and_insert(entity, fragment) + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment) + end end if old_chunk then @@ -1790,9 +1823,13 @@ function evolved.assign(entity, fragment, ...) local old_component = chunk_fragment_components[place] local new_component = __component_construct(entity, fragment, ...) chunk_fragment_components[place] = new_component - __fragment_on_set_and_assign(entity, fragment, new_component, old_component) + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end else - __fragment_on_set_and_assign(entity, fragment) + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment) + end end return true, false @@ -1836,9 +1873,13 @@ function evolved.insert(entity, fragment, ...) if new_chunk_fragment_components then local new_component = __component_construct(entity, fragment, ...) new_chunk_fragment_components[new_place] = new_component - __fragment_on_set_and_insert(entity, fragment, new_component) + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end else - __fragment_on_set_and_insert(entity, fragment) + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment) + end end if old_chunk then @@ -1893,15 +1934,19 @@ function evolved.remove(entity, ...) local old_chunk_fragments = old_chunk.__fragments local old_chunk_components = old_chunk.__components - for i = 1, select('#', ...) do - local fragment = select(i, ...) - if old_chunk_fragments[fragment] and __fragment_has_on_remove(fragment) then - local old_chunk_fragment_components = old_chunk_components[fragment] - if old_chunk_fragment_components then - local old_component = old_chunk_fragment_components[old_place] - __fragment_on_remove(entity, fragment, old_component) - else - __fragment_on_remove(entity, fragment) + if old_chunk.__has_remove_hooks then + for i = 1, select('#', ...) do + local fragment = select(i, ...) + if old_chunk_fragments[fragment] then + if __fragment_has_remove_hook(fragment) then + local old_chunk_fragment_components = old_chunk_components[fragment] + if old_chunk_fragment_components then + local old_component = old_chunk_fragment_components[old_place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) + end + end end end end @@ -1959,17 +2004,19 @@ function evolved.clear(entity) __defer() do - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components + if chunk.__has_remove_hooks then + local chunk_fragments = chunk.__fragments + local chunk_components = chunk.__components - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_on_remove(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - local old_component = chunk_fragment_components[place] - __fragment_on_remove(entity, fragment, old_component) - else - __fragment_on_remove(entity, fragment) + for fragment, _ in pairs(chunk_fragments) do + if __fragment_has_remove_hook(fragment) then + local chunk_fragment_components = chunk_components[fragment] + if chunk_fragment_components then + local old_component = chunk_fragment_components[place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) + end end end end @@ -2007,17 +2054,19 @@ function evolved.destroy(entity) __defer() do - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components + if chunk.__has_remove_hooks then + local chunk_fragments = chunk.__fragments + local chunk_components = chunk.__components - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_on_remove(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - local old_component = chunk_fragment_components[place] - __fragment_on_remove(entity, fragment, old_component) - else - __fragment_on_remove(entity, fragment) + for fragment, _ in pairs(chunk_fragments) do + if __fragment_has_remove_hook(fragment) then + local chunk_fragment_components = chunk_components[fragment] + if chunk_fragment_components then + local old_component = chunk_fragment_components[place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) + end end end end From 0f1f708827f16300d79f3f9d6270f238f54da9f0 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 03:37:15 +0700 Subject: [PATCH 06/23] update bench results --- develop/unbench.lua | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index edc8dd5..1b6c4ed 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -48,6 +48,22 @@ local function __bench_describe(name, loop, init) collectgarbage('collect') end +---@param tables table[] +__bench_describe('create and destroy 1k tables', function(tables) + for i = 1, 1000 do + local t = {} + tables[i] = t + end + + for i = 1, #tables do + tables[i] = nil + end + + collectgarbage('collect') +end, function() + return {} +end) + ---@param entities evolved.id[] __bench_describe('create and destroy 1k entities', function(entities) local id = evo.id @@ -188,3 +204,29 @@ end) | create and destroy 1k entities with three components ... | PASS | us: 574.71 | op/s: 1740.00 | kb/i: 0.14 ]] + +--- +--- hook flags for chunks +--- + +--[[ lua 5.1 +| create and destroy 1k entities ... | + PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 +| create and destroy 1k entities with one component ... | + PASS | us: 1005.03 | op/s: 995.00 | kb/i: 0.41 +| create and destroy 1k entities with two components ... | + PASS | us: 1747.83 | op/s: 572.14 | kb/i: 0.59 +| create and destroy 1k entities with three components ... | + PASS | us: 2576.92 | op/s: 388.06 | kb/i: 1.08 +]] + +--[[ luajit 2.1 +| create and destroy 1k entities ... | + PASS | us: 12.20 | op/s: 81940.30 | kb/i: 0.00 +| create and destroy 1k entities with one component ... | + PASS | us: 53.66 | op/s: 18636.82 | kb/i: 0.02 +| create and destroy 1k entities with two components ... | + PASS | us: 357.02 | op/s: 2801.00 | kb/i: 0.09 +| create and destroy 1k entities with three components ... | + PASS | us: 533.33 | op/s: 1875.00 | kb/i: 0.15 +]] From b322f813bbd7ae63c18c3f004476254392334aa5 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 08:58:52 +0700 Subject: [PATCH 07/23] opt: has_defaults/constructs flag for chunks --- evolved.lua | 109 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/evolved.lua b/evolved.lua index 5d66187..090bd31 100644 --- a/evolved.lua +++ b/evolved.lua @@ -42,6 +42,7 @@ local evolved = { ---@field package __components table ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table +---@field package __has_defaults_or_constructs boolean ---@field package __has_set_or_assign_hooks boolean ---@field package __has_set_or_insert_hooks boolean ---@field package __has_remove_hooks boolean @@ -437,6 +438,7 @@ local function __root_chunk(fragment) if root_chunk then return root_chunk end end + local has_defaults_or_constructs = __fragment_has_default_or_construct(fragment) local has_set_or_assign_hooks = __fragment_has_set_or_assign_hooks(fragment) local has_set_or_insert_hooks = __fragment_has_set_or_insert_hooks(fragment) local has_remove_hooks = __fragment_has_remove_hook(fragment) @@ -451,6 +453,7 @@ local function __root_chunk(fragment) __components = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, + __has_defaults_or_constructs = has_defaults_or_constructs, __has_set_or_assign_hooks = has_set_or_assign_hooks, __has_set_or_insert_hooks = has_set_or_insert_hooks, __has_remove_hooks = has_remove_hooks, @@ -516,9 +519,14 @@ local function __chunk_with_fragment(chunk, fragment) return sibling_chunk end - local has_set_or_assign_hooks = chunk.__has_set_or_assign_hooks or __fragment_has_set_or_assign_hooks(fragment) - local has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks or __fragment_has_set_or_insert_hooks(fragment) - local has_remove_hooks = chunk.__has_remove_hooks or __fragment_has_remove_hook(fragment) + local has_defaults_or_constructs = chunk.__has_defaults_or_constructs + or __fragment_has_default_or_construct(fragment) + local has_set_or_assign_hooks = chunk.__has_set_or_assign_hooks + or __fragment_has_set_or_assign_hooks(fragment) + local has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks + or __fragment_has_set_or_insert_hooks(fragment) + local has_remove_hooks = chunk.__has_remove_hooks + or __fragment_has_remove_hook(fragment) ---@type evolved.chunk local child_chunk = { @@ -530,6 +538,7 @@ local function __chunk_with_fragment(chunk, fragment) __components = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, + __has_defaults_or_constructs = has_defaults_or_constructs, __has_set_or_assign_hooks = has_set_or_assign_hooks, __has_set_or_insert_hooks = has_set_or_insert_hooks, __has_remove_hooks = has_remove_hooks, @@ -777,7 +786,7 @@ local function __chunk_assign(chunk, fragment, ...) if chunk.__has_set_or_assign_hooks and __fragment_has_set_or_assign_hooks(fragment) then if chunk_fragment_components then - if __fragment_has_default_or_construct(fragment) then + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for place = 1, chunk_size do local entity = chunk_entities[place] local old_component = chunk_fragment_components[place] @@ -807,7 +816,7 @@ local function __chunk_assign(chunk, fragment, ...) end else if chunk_fragment_components then - if __fragment_has_default_or_construct(fragment) then + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for place = 1, chunk_size do local entity = chunk_entities[place] local new_component = __component_construct(entity, fragment, ...) @@ -872,7 +881,7 @@ local function __chunk_insert(chunk, fragment, ...) if new_chunk.__has_set_or_insert_hooks and __fragment_has_set_or_insert_hooks(fragment) then if new_chunk_fragment_components then - if __fragment_has_default_or_construct(fragment) then + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] local new_component = __component_construct(entity, fragment, ...) @@ -900,7 +909,7 @@ local function __chunk_insert(chunk, fragment, ...) end else if new_chunk_fragment_components then - if __fragment_has_default_or_construct(fragment) then + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do local entity = new_chunk_entities[new_place] local new_component = __component_construct(entity, fragment, ...) @@ -1736,10 +1745,24 @@ function evolved.set(entity, fragment, ...) if old_chunk_fragment_components then local old_component = old_chunk_fragment_components[old_place] - local new_component = __component_construct(entity, fragment, ...) - old_chunk_fragment_components[old_place] = new_component - if old_chunk.__has_set_or_assign_hooks then - __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + + if old_chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + old_chunk_fragment_components[old_place] = new_component + + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + old_chunk_fragment_components[old_place] = new_component + + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end end else if old_chunk.__has_set_or_assign_hooks then @@ -1759,10 +1782,23 @@ function evolved.set(entity, fragment, ...) new_chunk_entities[new_place] = entity if new_chunk_fragment_components then - local new_component = __component_construct(entity, fragment, ...) - new_chunk_fragment_components[new_place] = new_component - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + if new_chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + new_chunk_fragment_components[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + new_chunk_fragment_components[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end end else if new_chunk.__has_set_or_insert_hooks then @@ -1821,10 +1857,24 @@ function evolved.assign(entity, fragment, ...) if chunk_fragment_components then local old_component = chunk_fragment_components[place] - local new_component = __component_construct(entity, fragment, ...) - chunk_fragment_components[place] = new_component - if chunk.__has_set_or_assign_hooks then - __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + + if chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + chunk_fragment_components[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + chunk_fragment_components[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end end else if chunk.__has_set_or_assign_hooks then @@ -1871,10 +1921,23 @@ function evolved.insert(entity, fragment, ...) new_chunk_entities[new_place] = entity if new_chunk_fragment_components then - local new_component = __component_construct(entity, fragment, ...) - new_chunk_fragment_components[new_place] = new_component - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + if new_chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + new_chunk_fragment_components[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + new_chunk_fragment_components[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end end else if new_chunk.__has_set_or_insert_hooks then From 1002c76da3f3ba310d838419b9eb0a2a452d4b3c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 3 Jan 2025 09:00:40 +0700 Subject: [PATCH 08/23] update bench results --- develop/unbench.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index 1b6c4ed..6f51538 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -230,3 +230,29 @@ end) | create and destroy 1k entities with three components ... | PASS | us: 533.33 | op/s: 1875.00 | kb/i: 0.15 ]] + +--- +--- construct flags for chunks +--- + +--[[ lua 5.1 +| create and destroy 1k entities ... | + PASS | us: 253.49 | op/s: 3945.00 | kb/i: 0.04 +| create and destroy 1k entities with one component ... | + PASS | us: 913.64 | op/s: 1094.53 | kb/i: 0.37 +| create and destroy 1k entities with two components ... | + PASS | us: 1562.50 | op/s: 640.00 | kb/i: 0.53 +| create and destroy 1k entities with three components ... | + PASS | us: 2280.90 | op/s: 438.42 | kb/i: 0.97 +]] + +--[[ luajit 2.1 +| create and destroy 1k entities ... | + PASS | us: 12.05 | op/s: 82995.02 | kb/i: 0.00 +| create and destroy 1k entities with one component ... | + PASS | us: 53.61 | op/s: 18651.74 | kb/i: 0.02 +| create and destroy 1k entities with two components ... | + PASS | us: 232.02 | op/s: 4310.00 | kb/i: 0.06 +| create and destroy 1k entities with three components ... | + PASS | us: 329.49 | op/s: 3035.00 | kb/i: 0.10 +]] From 63f8c5e5d211dc5a82f3c790aa9b500938c422db Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 4 Jan 2025 07:24:03 +0700 Subject: [PATCH 09/23] update library titles --- README.md | 2 +- evolved.lua | 2 +- rockspecs/evolved.lua-scm-0.rockspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c59ae7..b841026 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # evolved.lua (work in progress) -> Evolved Entity-Component-System for Lua +> Evolved ECS (Entity-Component-System) for Lua [![language][badge.language]][language] [![license][badge.license]][license] diff --git a/evolved.lua b/evolved.lua index 090bd31..2ec8414 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1,6 +1,6 @@ local evolved = { __HOMEPAGE = 'https://github.com/BlackMATov/evolved.lua', - __DESCRIPTION = 'Evolved Entity-Component-System for Lua', + __DESCRIPTION = 'Evolved ECS (Entity-Component-System) for Lua', __VERSION = '0.0.1', __LICENSE = [[ MIT License diff --git a/rockspecs/evolved.lua-scm-0.rockspec b/rockspecs/evolved.lua-scm-0.rockspec index c9d24df..b138793 100644 --- a/rockspecs/evolved.lua-scm-0.rockspec +++ b/rockspecs/evolved.lua-scm-0.rockspec @@ -6,7 +6,7 @@ source = { } description = { homepage = "https://github.com/BlackMATov/evolved.lua", - summary = "Evolved Entity-Component-System for Lua", + summary = "Evolved ECS (Entity-Component-System) for Lua", license = "MIT", labels = { "ecs", From 527b327a870c91b5068edd592447f45196a5459f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 5 Jan 2025 00:23:01 +0700 Subject: [PATCH 10/23] opt: huge chunks refactoring --- evolved.lua | 861 +++++++++++++++++++++++++++++----------------------- 1 file changed, 488 insertions(+), 373 deletions(-) diff --git a/evolved.lua b/evolved.lua index 2ec8414..d557a65 100644 --- a/evolved.lua +++ b/evolved.lua @@ -28,18 +28,24 @@ local evolved = { } ---@alias evolved.id integer ----@alias evolved.query evolved.id + ---@alias evolved.entity evolved.id ---@alias evolved.fragment evolved.id +---@alias evolved.query evolved.id + ---@alias evolved.component any +---@alias evolved.component_storage evolved.component[] ---@class (exact) evolved.chunk ---@field package __parent? evolved.chunk ---@field package __children evolved.chunk[] ----@field package __fragment evolved.fragment ---@field package __entities evolved.entity[] ----@field package __fragments table ----@field package __components table +---@field package __fragment evolved.fragment +---@field package __fragment_set table +---@field package __fragment_list evolved.fragment[] +---@field package __component_indices table +---@field package __component_storages evolved.component_storage[] +---@field package __component_fragments evolved.fragment[] ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table ---@field package __has_defaults_or_constructs boolean @@ -51,7 +57,7 @@ local evolved = { ---@field package [1] integer structural_changes ---@field package [2] evolved.chunk entity_chunk ---@field package [3] integer entity_place ----@field package [4] evolved.fragment? fragment_index +---@field package [4] integer fragment_index ---@class (exact) evolved.execute_state ---@field package [1] integer structural_changes @@ -130,13 +136,8 @@ end)() ---@type fun(narray: integer, nhash: integer): table local __table_new = (function() local table_new_loader = package.preload['table.new'] - ---@param narray integer - ---@param nhash integer ---@return table - return table_new_loader and table_new_loader() or function(narray, nhash) - -- we have to check arguments here for consistency with table.new - if type(narray) ~= 'number' then error('narray must be a number', 2) end - if type(nhash) ~= 'number' then error('nhash must be a number', 2) end + return table_new_loader and table_new_loader() or function() return {} end end)() @@ -146,9 +147,7 @@ local __table_clear = (function() local table_clear_loader = package.preload['table.clear'] ---@param tab table return table_clear_loader and table_clear_loader() or function(tab) - -- we have to check arguments here for consistency with table.clear - if type(tab) ~= 'table' then error('tab must be a table', 2) end - for i = #tab, 1, -1 do tab[i] = nil end + for i = 1, #tab do tab[i] = nil end for k in pairs(tab) do tab[k] = nil end end end)() @@ -284,10 +283,16 @@ local function __each_iterator(each_state) error('structural changes are prohibited during iteration', 2) end - if fragment_index then - each_state[4] = next(entity_chunk.__fragments, fragment_index) - local fragment_components = entity_chunk.__components[fragment_index] - return fragment_index, fragment_components and fragment_components[entity_place] + local entity_chunk_fragment_list = entity_chunk.__fragment_list + local entity_chunk_component_indices = entity_chunk.__component_indices + local entity_chunk_component_storages = entity_chunk.__component_storages + + if fragment_index <= #entity_chunk_fragment_list then + each_state[4] = fragment_index + 1 + local fragment = entity_chunk_fragment_list[fragment_index] + local component_index = entity_chunk_component_indices[fragment] + local component_storage = entity_chunk_component_storages[component_index] + return fragment, component_storage and component_storage[entity_place] end __release_table(__TABLE_POOL_TAG__EACH_STATE, each_state) @@ -429,28 +434,38 @@ end --- --- ----@param fragment evolved.fragment +---@param root_fragment evolved.fragment ---@return evolved.chunk ---@nodiscard -local function __root_chunk(fragment) +local function __root_chunk(root_fragment) do - local root_chunk = __root_chunks[fragment] + local root_chunk = __root_chunks[root_fragment] if root_chunk then return root_chunk end end - local has_defaults_or_constructs = __fragment_has_default_or_construct(fragment) - local has_set_or_assign_hooks = __fragment_has_set_or_assign_hooks(fragment) - local has_set_or_insert_hooks = __fragment_has_set_or_insert_hooks(fragment) - local has_remove_hooks = __fragment_has_remove_hook(fragment) + local has_defaults_or_constructs = __fragment_has_default_or_construct(root_fragment) + local has_set_or_assign_hooks = __fragment_has_set_or_assign_hooks(root_fragment) + local has_set_or_insert_hooks = __fragment_has_set_or_insert_hooks(root_fragment) + local has_remove_hooks = __fragment_has_remove_hook(root_fragment) + + local root_fragment_set = {} ---@type table + local root_fragment_list = {} ---@type evolved.fragment[] + + local root_component_indices = {} ---@type table + local root_component_storages = {} ---@type evolved.component_storage[] + local root_component_fragments = {} ---@type evolved.fragment[] ---@type evolved.chunk local root_chunk = { __parent = nil, __children = {}, - __fragment = fragment, __entities = {}, - __fragments = {}, - __components = {}, + __fragment = root_fragment, + __fragment_set = root_fragment_set, + __fragment_list = root_fragment_list, + __component_indices = root_component_indices, + __component_storages = root_component_storages, + __component_fragments = root_component_fragments, __with_fragment_edges = {}, __without_fragment_edges = {}, __has_defaults_or_constructs = has_defaults_or_constructs, @@ -460,23 +475,28 @@ local function __root_chunk(fragment) } do - root_chunk.__fragments[fragment] = true + root_fragment_set[root_fragment] = true + root_fragment_list[#root_fragment_list + 1] = root_fragment - if not evolved.has(fragment, evolved.TAG) then - root_chunk.__components[fragment] = {} + if not evolved.has(root_fragment, evolved.TAG) then + local storage = {} + local storage_index = #root_component_storages + 1 + root_component_indices[root_fragment] = storage_index + root_component_storages[storage_index] = storage + root_component_fragments[storage_index] = root_fragment end end do - __root_chunks[fragment] = root_chunk + __root_chunks[root_fragment] = root_chunk end do - local fragment_chunks = __major_chunks[fragment] + local fragment_chunks = __major_chunks[root_fragment] if not fragment_chunks then fragment_chunks = {} - __major_chunks[fragment] = fragment_chunks + __major_chunks[root_fragment] = fragment_chunks end fragment_chunks[#fragment_chunks + 1] = root_chunk @@ -486,56 +506,62 @@ local function __root_chunk(fragment) return root_chunk end ----@param chunk? evolved.chunk ----@param fragment evolved.fragment +---@param parent_chunk? evolved.chunk +---@param child_fragment evolved.fragment ---@return evolved.chunk ---@nodiscard -local function __chunk_with_fragment(chunk, fragment) - if not chunk then - return __root_chunk(fragment) +local function __chunk_with_fragment(parent_chunk, child_fragment) + if not parent_chunk then + return __root_chunk(child_fragment) end - if chunk.__fragments[fragment] then - return chunk + if parent_chunk.__fragment_set[child_fragment] then + return parent_chunk end do - local with_fragment_chunk = chunk.__with_fragment_edges[fragment] + local with_fragment_chunk = parent_chunk.__with_fragment_edges[child_fragment] if with_fragment_chunk then return with_fragment_chunk end end - if fragment == chunk.__fragment then - return chunk - end - - if fragment < chunk.__fragment then + if child_fragment < parent_chunk.__fragment then local sibling_chunk = __chunk_with_fragment( - __chunk_with_fragment(chunk.__parent, fragment), - chunk.__fragment) + __chunk_with_fragment(parent_chunk.__parent, child_fragment), + parent_chunk.__fragment) - chunk.__with_fragment_edges[fragment] = sibling_chunk - sibling_chunk.__without_fragment_edges[fragment] = chunk + parent_chunk.__with_fragment_edges[child_fragment] = sibling_chunk + sibling_chunk.__without_fragment_edges[child_fragment] = parent_chunk return sibling_chunk end - local has_defaults_or_constructs = chunk.__has_defaults_or_constructs - or __fragment_has_default_or_construct(fragment) - local has_set_or_assign_hooks = chunk.__has_set_or_assign_hooks - or __fragment_has_set_or_assign_hooks(fragment) - local has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks - or __fragment_has_set_or_insert_hooks(fragment) - local has_remove_hooks = chunk.__has_remove_hooks - or __fragment_has_remove_hook(fragment) + local has_defaults_or_constructs = parent_chunk.__has_defaults_or_constructs + or __fragment_has_default_or_construct(child_fragment) + local has_set_or_assign_hooks = parent_chunk.__has_set_or_assign_hooks + or __fragment_has_set_or_assign_hooks(child_fragment) + local has_set_or_insert_hooks = parent_chunk.__has_set_or_insert_hooks + or __fragment_has_set_or_insert_hooks(child_fragment) + local has_remove_hooks = parent_chunk.__has_remove_hooks + or __fragment_has_remove_hook(child_fragment) + + local child_fragment_set = {} ---@type table + local child_fragment_list = {} ---@type evolved.fragment[] + + local child_component_indices = {} ---@type table + local child_component_storages = {} ---@type evolved.component_storage[] + local child_component_fragments = {} ---@type evolved.fragment[] ---@type evolved.chunk local child_chunk = { - __parent = chunk, + __parent = parent_chunk, __children = {}, - __fragment = fragment, __entities = {}, - __fragments = {}, - __components = {}, + __fragment = child_fragment, + __fragment_set = child_fragment_set, + __fragment_list = child_fragment_list, + __component_indices = child_component_indices, + __component_storages = child_component_storages, + __component_fragments = child_component_fragments, __with_fragment_edges = {}, __without_fragment_edges = {}, __has_defaults_or_constructs = has_defaults_or_constructs, @@ -544,38 +570,48 @@ local function __chunk_with_fragment(chunk, fragment) __has_remove_hooks = has_remove_hooks, } - do - child_chunk.__fragments[fragment] = true - - if not evolved.has(fragment, evolved.TAG) then - child_chunk.__components[fragment] = {} - end - end - - for parent_fragment, _ in pairs(chunk.__fragments) do - child_chunk.__fragments[parent_fragment] = true + for _, parent_fragment in ipairs(parent_chunk.__fragment_list) do + child_fragment_set[parent_fragment] = true + child_fragment_list[#child_fragment_list + 1] = parent_fragment if not evolved.has(parent_fragment, evolved.TAG) then - child_chunk.__components[parent_fragment] = {} + local storage = {} + local storage_index = #child_component_storages + 1 + child_component_indices[parent_fragment] = storage_index + child_component_storages[storage_index] = storage + child_component_fragments[storage_index] = parent_fragment end end do - local chunk_children = chunk.__children + child_fragment_set[child_fragment] = true + child_fragment_list[#child_fragment_list + 1] = child_fragment + + if not evolved.has(child_fragment, evolved.TAG) then + local storage = {} + local storage_index = #child_component_storages + 1 + child_component_indices[child_fragment] = storage_index + child_component_storages[storage_index] = storage + child_component_fragments[storage_index] = child_fragment + end + end + + do + local chunk_children = parent_chunk.__children chunk_children[#chunk_children + 1] = child_chunk end do - chunk.__with_fragment_edges[fragment] = child_chunk - child_chunk.__without_fragment_edges[fragment] = chunk + parent_chunk.__with_fragment_edges[child_fragment] = child_chunk + child_chunk.__without_fragment_edges[child_fragment] = parent_chunk end do - local fragment_chunks = __major_chunks[fragment] + local fragment_chunks = __major_chunks[child_fragment] if not fragment_chunks then fragment_chunks = {} - __major_chunks[fragment] = fragment_chunks + __major_chunks[child_fragment] = fragment_chunks end fragment_chunks[#fragment_chunks + 1] = child_chunk @@ -594,19 +630,19 @@ local function __chunk_without_fragment(chunk, fragment) return nil end - if not chunk.__fragments[fragment] then + if not chunk.__fragment_set[fragment] then return chunk end + if fragment == chunk.__fragment then + return chunk.__parent + end + do local without_fragment_edge = chunk.__without_fragment_edges[fragment] if without_fragment_edge then return without_fragment_edge end end - if fragment == chunk.__fragment then - return chunk.__parent - end - if fragment < chunk.__fragment then local sibling_chunk = __chunk_with_fragment( __chunk_without_fragment(chunk.__parent, fragment), @@ -633,7 +669,8 @@ local function __chunk_without_fragments(chunk, ...) end for i = 1, fragment_count do - chunk = __chunk_without_fragment(chunk, select(i, ...)) + local fragment = select(i, ...) + chunk = __chunk_without_fragment(chunk, fragment) end return chunk @@ -650,7 +687,7 @@ end ---@return boolean ---@nodiscard local function __chunk_has_fragment(chunk, fragment) - return chunk.__fragments[fragment] + return chunk.__fragment_set[fragment] end ---@param chunk evolved.chunk @@ -658,10 +695,11 @@ end ---@return boolean ---@nodiscard local function __chunk_has_all_fragments(chunk, ...) - local fragments = chunk.__fragments + local fragment_set = chunk.__fragment_set for i = 1, select('#', ...) do - if not fragments[select(i, ...)] then + local fragment = select(i, ...) + if not fragment_set[fragment] then return false end end @@ -674,10 +712,11 @@ end ---@return boolean ---@nodiscard local function __chunk_has_all_fragment_list(chunk, fragment_list) - local fragments = chunk.__fragments + local fragment_set = chunk.__fragment_set for i = 1, #fragment_list do - if not fragments[fragment_list[i]] then + local fragment = fragment_list[i] + if not fragment_set[fragment] then return false end end @@ -690,10 +729,11 @@ end ---@return boolean ---@nodiscard local function __chunk_has_any_fragments(chunk, ...) - local fragments = chunk.__fragments + local fragment_set = chunk.__fragment_set for i = 1, select('#', ...) do - if fragments[select(i, ...)] then + local fragment = select(i, ...) + if fragment_set[fragment] then return true end end @@ -706,10 +746,11 @@ end ---@return boolean ---@nodiscard local function __chunk_has_any_fragment_list(chunk, fragment_list) - local fragments = chunk.__fragments + local fragment_set = chunk.__fragment_set for i = 1, #fragment_list do - if fragments[fragment_list[i]] then + local fragment = fragment_list[i] + if fragment_set[fragment] then return true end end @@ -729,30 +770,40 @@ local function __chunk_get_components(chunk, place, ...) return end - local components = chunk.__components + local indices = chunk.__component_indices + local storages = chunk.__component_storages if fragment_count == 1 then local f1 = ... - local cs1 = components[f1] - return cs1 and cs1[place] + local i1 = indices[f1] + return + i1 and storages[i1][place] end if fragment_count == 2 then local f1, f2 = ... - local cs1, cs2 = components[f1], components[f2] - return cs1 and cs1[place], cs2 and cs2[place] + local i1, i2 = indices[f1], indices[f2] + return + i1 and storages[i1][place], + i2 and storages[i2][place] end if fragment_count == 3 then local f1, f2, f3 = ... - local cs1, cs2, cs3 = components[f1], components[f2], components[f3] - return cs1 and cs1[place], cs2 and cs2[place], cs3 and cs3[place] + local i1, i2, i3 = indices[f1], indices[f2], indices[f3] + return + i1 and storages[i1][place], + i2 and storages[i2][place], + i3 and storages[i3][place] end do local f1, f2, f3 = ... - local cs1, cs2, cs3 = components[f1], components[f2], components[f3] - return cs1 and cs1[place], cs2 and cs2[place], cs3 and cs3[place], + local i1, i2, i3 = indices[f1], indices[f2], indices[f3] + return + i1 and storages[i1][place], + i2 and storages[i2][place], + i3 and storages[i3][place], __chunk_get_components(chunk, place, select(4, ...)) end end @@ -773,25 +824,26 @@ local function __chunk_assign(chunk, fragment, ...) error('batched chunk operations should be deferred', 2) end - local chunk_entities = chunk.__entities - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components - - if not chunk_fragments[fragment] then + if not chunk.__fragment_set[fragment] then return 0 end + local chunk_entities = chunk.__entities local chunk_size = #chunk_entities - local chunk_fragment_components = chunk_components[fragment] + + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages if chunk.__has_set_or_assign_hooks and __fragment_has_set_or_assign_hooks(fragment) then - if chunk_fragment_components then + local component_index = chunk_component_indices[fragment] + if component_index then + local component_storage = chunk_component_storages[component_index] if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for place = 1, chunk_size do local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] + local old_component = component_storage[place] local new_component = __component_construct(entity, fragment, ...) - chunk_fragment_components[place] = new_component + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) end else @@ -803,8 +855,8 @@ local function __chunk_assign(chunk, fragment, ...) for place = 1, chunk_size do local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] - chunk_fragment_components[place] = new_component + local old_component = component_storage[place] + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) end end @@ -815,12 +867,14 @@ local function __chunk_assign(chunk, fragment, ...) end end else - if chunk_fragment_components then + local component_index = chunk_component_indices[fragment] + if component_index then + local component_storage = chunk_component_storages[component_index] if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then for place = 1, chunk_size do local entity = chunk_entities[place] local new_component = __component_construct(entity, fragment, ...) - chunk_fragment_components[place] = new_component + component_storage[place] = new_component end else local new_component = ... @@ -830,7 +884,7 @@ local function __chunk_assign(chunk, fragment, ...) end for place = 1, chunk_size do - chunk_fragment_components[place] = new_component + component_storage[place] = new_component end end else @@ -858,81 +912,94 @@ local function __chunk_insert(chunk, fragment, ...) return 0 end - local old_chunk_entities = old_chunk.__entities - local old_chunk_components = old_chunk.__components + local old_entities = old_chunk.__entities + local old_size = #old_entities - local new_chunk_entities = new_chunk.__entities - local new_chunk_components = new_chunk.__components + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments - local old_chunk_size = #old_chunk_entities - local new_chunk_size = #new_chunk_entities - local new_chunk_fragment_components = new_chunk_components[fragment] + local new_entities = new_chunk.__entities + local new_size = #new_entities - __table_move( - old_chunk_entities, 1, old_chunk_size, - new_chunk_size + 1, new_chunk_entities) + local new_component_indices = new_chunk.__component_indices + local new_component_storages = new_chunk.__component_storages - for old_f, old_cs in pairs(old_chunk_components) do - local new_cs = new_chunk_components[old_f] - if new_cs then - __table_move(old_cs, 1, old_chunk_size, new_chunk_size + 1, new_cs) + do + __table_move( + old_entities, 1, old_size, + new_size + 1, new_entities) + + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + local new_cs = new_component_storages[new_ci] + if new_cs then + __table_move(old_cs, 1, old_size, new_size + 1, new_cs) + end end end - if new_chunk.__has_set_or_insert_hooks and __fragment_has_set_or_insert_hooks(fragment) then - if new_chunk_fragment_components then - if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] - local new_component = __component_construct(entity, fragment, ...) - new_chunk_fragment_components[new_place] = new_component - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + do + if new_chunk.__has_set_or_insert_hooks and __fragment_has_set_or_insert_hooks(fragment) then + local new_component_index = new_component_indices[fragment] + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] + local new_component = __component_construct(entity, fragment, ...) + new_component_storage[new_place] = new_component + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = ... + + if new_component == nil then + new_component = true + end + + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] + new_component_storage[new_place] = new_component + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end end else - local new_component = ... - - if new_component == nil then - new_component = true - end - - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] - new_chunk_fragment_components[new_place] = new_component - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] + __fragment_call_set_and_insert_hooks(entity, fragment) end end else - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] - __fragment_call_set_and_insert_hooks(entity, fragment) - end - end - else - if new_chunk_fragment_components then - if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] - local new_component = __component_construct(entity, fragment, ...) - new_chunk_fragment_components[new_place] = new_component + local new_component_index = new_component_indices[fragment] + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] + if chunk.__has_defaults_or_constructs and __fragment_has_default_or_construct(fragment) then + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] + local new_component = __component_construct(entity, fragment, ...) + new_component_storage[new_place] = new_component + end + else + local new_component = ... + + if new_component == nil then + new_component = true + end + + for new_place = new_size + 1, new_size + old_size do + new_component_storage[new_place] = new_component + end end else - local new_component = ... - - if new_component == nil then - new_component = true - end - - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - new_chunk_fragment_components[new_place] = new_component - end + -- nothing end - else - -- nothing end end - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] local entity_index = entity % 0x100000 __entity_chunks[entity_index] = new_chunk __entity_places[entity_index] = new_place @@ -941,13 +1008,13 @@ local function __chunk_insert(chunk, fragment, ...) do old_chunk.__entities = {} - for old_f, _ in pairs(old_chunk_components) do - old_chunk_components[old_f] = {} + for i = 1, #old_component_storages do + old_component_storages[i] = {} end end - __structural_changes = __structural_changes + old_chunk_size - return old_chunk_size + __structural_changes = __structural_changes + old_size + return old_size end ---@param chunk evolved.chunk @@ -966,29 +1033,29 @@ local function __chunk_remove(chunk, ...) return 0 end - local old_chunk_entities = old_chunk.__entities - local old_chunk_fragments = old_chunk.__fragments - local old_chunk_components = old_chunk.__components + local old_entities = old_chunk.__entities + local old_size = #old_entities - local old_chunk_size = #old_chunk_entities + local old_component_indices = chunk.__component_indices + local old_component_storages = chunk.__component_storages if old_chunk.__has_remove_hooks then + local old_fragment_set = old_chunk.__fragment_set for i = 1, select('#', ...) do local fragment = select(i, ...) - if old_chunk_fragments[fragment] then - if __fragment_has_remove_hook(fragment) then - local old_chunk_fragment_components = old_chunk_components[fragment] - if old_chunk_fragment_components then - for old_place = 1, old_chunk_size do - local entity = old_chunk_entities[old_place] - local old_component = old_chunk_fragment_components[old_place] - __fragment_call_remove_hook(entity, fragment, old_component) - end - else - for old_place = 1, old_chunk_size do - local entity = old_chunk_entities[old_place] - __fragment_call_remove_hook(entity, fragment) - end + if old_fragment_set[fragment] and __fragment_has_remove_hook(fragment) then + local old_component_index = old_component_indices[fragment] + if old_component_index then + local old_component_storage = old_component_storages[old_component_index] + for old_place = 1, old_size do + local entity = old_entities[old_place] + local old_component = old_component_storage[old_place] + __fragment_call_remove_hook(entity, fragment, old_component) + end + else + for old_place = 1, old_size do + local entity = old_entities[old_place] + __fragment_call_remove_hook(entity, fragment) end end end @@ -996,31 +1063,35 @@ local function __chunk_remove(chunk, ...) end if new_chunk then - local new_chunk_entities = new_chunk.__entities - local new_chunk_components = new_chunk.__components + local new_entities = new_chunk.__entities + local new_size = #new_entities - local new_chunk_size = #new_chunk.__entities + local new_component_storages = new_chunk.__component_storages + local new_component_fragments = new_chunk.__component_fragments __table_move( - old_chunk_entities, 1, old_chunk_size, - new_chunk_size + 1, new_chunk_entities) + old_entities, 1, old_size, + new_size + 1, new_entities) - for new_f, new_cs in pairs(new_chunk_components) do - local old_cs = old_chunk_components[new_f] + for i = 1, #new_component_fragments do + local new_f = new_component_fragments[i] + local new_cs = new_component_storages[i] + local old_ci = old_component_indices[new_f] + local old_cs = old_component_storages[old_ci] if old_cs then - __table_move(old_cs, 1, old_chunk_size, new_chunk_size + 1, new_cs) + __table_move(old_cs, 1, old_size, new_size + 1, new_cs) end end - for new_place = new_chunk_size + 1, new_chunk_size + old_chunk_size do - local entity = new_chunk_entities[new_place] + for new_place = new_size + 1, new_size + old_size do + local entity = new_entities[new_place] local entity_index = entity % 0x100000 __entity_chunks[entity_index] = new_chunk __entity_places[entity_index] = new_place end else - for old_place = 1, old_chunk_size do - local entity = old_chunk_entities[old_place] + for old_place = 1, old_size do + local entity = old_entities[old_place] local entity_index = entity % 0x100000 __entity_chunks[entity_index] = nil __entity_places[entity_index] = nil @@ -1030,13 +1101,13 @@ local function __chunk_remove(chunk, ...) do old_chunk.__entities = {} - for old_f, _ in pairs(old_chunk_components) do - old_chunk_components[old_f] = {} + for i = 1, #old_component_storages do + old_component_storages[i] = {} end end - __structural_changes = __structural_changes + old_chunk_size - return old_chunk_size + __structural_changes = __structural_changes + old_size + return old_size end ---@param chunk evolved.chunk @@ -1048,19 +1119,20 @@ local function __chunk_clear(chunk) end local chunk_entities = chunk.__entities - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components - local chunk_size = #chunk_entities + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + if chunk.__has_remove_hooks then - for fragment, _ in pairs(chunk_fragments) do + for _, fragment in ipairs(chunk.__fragment_list) do if __fragment_has_remove_hook(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then + local component_index = chunk_component_indices[fragment] + if component_index then + local component_storage = chunk_component_storages[component_index] for place = 1, chunk_size do local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] + local old_component = component_storage[place] __fragment_call_remove_hook(entity, fragment, old_component) end else @@ -1083,8 +1155,8 @@ local function __chunk_clear(chunk) do chunk.__entities = {} - for f, _ in pairs(chunk_components) do - chunk_components[f] = {} + for i = 1, #chunk_component_storages do + chunk_component_storages[i] = {} end end @@ -1101,19 +1173,20 @@ local function __chunk_destroy(chunk) end local chunk_entities = chunk.__entities - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components - local chunk_size = #chunk_entities + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + if chunk.__has_remove_hooks then - for fragment, _ in pairs(chunk_fragments) do + for _, fragment in ipairs(chunk.__fragment_list) do if __fragment_has_remove_hook(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then + local component_index = chunk_component_indices[fragment] + if component_index then + local component_storage = chunk_component_storages[component_index] for place = 1, chunk_size do local entity = chunk_entities[place] - local old_component = chunk_fragment_components[place] + local old_component = component_storage[place] __fragment_call_remove_hook(entity, fragment, old_component) end else @@ -1137,8 +1210,8 @@ local function __chunk_destroy(chunk) do chunk.__entities = {} - for f, _ in pairs(chunk_components) do - chunk_components[f] = {} + for i = 1, #chunk_component_storages do + chunk_component_storages[i] = {} end end @@ -1162,30 +1235,32 @@ local function __detach_entity(entity) return end - local old_chunk_entities = old_chunk.__entities - local old_chunk_components = old_chunk.__components + local old_entities = old_chunk.__entities + local old_component_storages = old_chunk.__component_storages local old_place = __entity_places[entity_index] - local old_chunk_size = #old_chunk_entities + local old_size = #old_entities - if old_place == old_chunk_size then - old_chunk_entities[old_place] = nil + if old_place == old_size then + old_entities[old_place] = nil - for _, cs in pairs(old_chunk_components) do - cs[old_place] = nil + for i = 1, #old_component_storages do + local old_cs = old_component_storages[i] + old_cs[old_place] = nil end else - local last_chunk_entity = old_chunk_entities[old_chunk_size] - local last_chunk_entity_index = last_chunk_entity % 0x100000 - __entity_places[last_chunk_entity_index] = old_place + local last_entity = old_entities[old_size] + local last_entity_index = last_entity % 0x100000 + __entity_places[last_entity_index] = old_place - old_chunk_entities[old_place] = last_chunk_entity - old_chunk_entities[old_chunk_size] = nil + old_entities[old_place] = last_entity + old_entities[old_size] = nil - for _, cs in pairs(old_chunk_components) do - local last_chunk_component = cs[old_chunk_size] - cs[old_place] = last_chunk_component - cs[old_chunk_size] = nil + for i = 1, #old_component_storages do + local old_cs = old_component_storages[i] + local last_component = old_cs[old_size] + old_cs[old_place] = last_component + old_cs[old_size] = nil end end @@ -1737,19 +1812,21 @@ function evolved.set(entity, fragment, ...) local old_place = __entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) - local new_place = #new_chunk.__entities + 1 if old_chunk == new_chunk then - local old_chunk_components = old_chunk.__components - local old_chunk_fragment_components = old_chunk_components[fragment] + local old_component_indices = old_chunk.__component_indices + local old_component_storages = old_chunk.__component_storages - if old_chunk_fragment_components then - local old_component = old_chunk_fragment_components[old_place] + local old_component_index = old_component_indices[fragment] + + if old_component_index then + local old_component_storage = old_component_storages[old_component_index] + local old_component = old_component_storage[old_place] if old_chunk.__has_defaults_or_constructs then local new_component = __component_construct(entity, fragment, ...) - old_chunk_fragment_components[old_place] = new_component + old_component_storage[old_place] = new_component if old_chunk.__has_set_or_assign_hooks then __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) @@ -1758,7 +1835,7 @@ function evolved.set(entity, fragment, ...) local new_component = ... if new_component == nil then new_component = true end - old_chunk_fragment_components[old_place] = new_component + old_component_storage[old_place] = new_component if old_chunk.__has_set_or_assign_hooks then __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) @@ -1775,42 +1852,53 @@ function evolved.set(entity, fragment, ...) __defer() do - local new_chunk_entities = new_chunk.__entities - local new_chunk_components = new_chunk.__components - local new_chunk_fragment_components = new_chunk_components[fragment] + local new_entities = new_chunk.__entities + local new_component_indices = new_chunk.__component_indices + local new_component_storages = new_chunk.__component_storages - new_chunk_entities[new_place] = entity + local new_place = #new_entities + 1 + new_entities[new_place] = entity - if new_chunk_fragment_components then - if new_chunk.__has_defaults_or_constructs then - local new_component = __component_construct(entity, fragment, ...) + do + local new_component_index = new_component_indices[fragment] - new_chunk_fragment_components[new_place] = new_component + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + if new_chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end end else - local new_component = ... - if new_component == nil then new_component = true end - - new_chunk_fragment_components[new_place] = new_component - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + __fragment_call_set_and_insert_hooks(entity, fragment) end end - else - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment) - end end if old_chunk then - local old_chunk_components = old_chunk.__components + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments - for old_f, old_cs in pairs(old_chunk_components) do - local new_cs = new_chunk_components[old_f] + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + local new_cs = new_component_storages[new_ci] if new_cs then new_cs[new_place] = old_cs[old_place] end @@ -1848,38 +1936,43 @@ function evolved.assign(entity, fragment, ...) local chunk = __entity_chunks[entity_index] local place = __entity_places[entity_index] - if not chunk or not chunk.__fragments[fragment] then + if not chunk or not chunk.__fragment_set[fragment] then return false, false end - local chunk_components = chunk.__components - local chunk_fragment_components = chunk_components[fragment] + do + local component_indices = chunk.__component_indices + local component_storages = chunk.__component_storages - if chunk_fragment_components then - local old_component = chunk_fragment_components[place] + local component_index = component_indices[fragment] - if chunk.__has_defaults_or_constructs then - local new_component = __component_construct(entity, fragment, ...) + if component_index then + local component_storage = component_storages[component_index] + local old_component = component_storage[place] - chunk_fragment_components[place] = new_component + if chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) - if chunk.__has_set_or_assign_hooks then - __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + component_storage[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + component_storage[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end end else - local new_component = ... - if new_component == nil then new_component = true end - - chunk_fragment_components[place] = new_component - if chunk.__has_set_or_assign_hooks then - __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + __fragment_call_set_and_assign_hooks(entity, fragment) end end - else - if chunk.__has_set_or_assign_hooks then - __fragment_call_set_and_assign_hooks(entity, fragment) - end end return true, false @@ -1906,7 +1999,6 @@ function evolved.insert(entity, fragment, ...) local old_place = __entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) - local new_place = #new_chunk.__entities + 1 if old_chunk == new_chunk then return false, false @@ -1914,42 +2006,53 @@ function evolved.insert(entity, fragment, ...) __defer() do - local new_chunk_entities = new_chunk.__entities - local new_chunk_components = new_chunk.__components - local new_chunk_fragment_components = new_chunk_components[fragment] + local new_entities = new_chunk.__entities + local new_component_indices = new_chunk.__component_indices + local new_component_storages = new_chunk.__component_storages - new_chunk_entities[new_place] = entity + local new_place = #new_entities + 1 + new_entities[new_place] = entity - if new_chunk_fragment_components then - if new_chunk.__has_defaults_or_constructs then - local new_component = __component_construct(entity, fragment, ...) + do + local new_component_index = new_component_indices[fragment] - new_chunk_fragment_components[new_place] = new_component + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + if new_chunk.__has_defaults_or_constructs then + local new_component = __component_construct(entity, fragment, ...) + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = ... + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end end else - local new_component = ... - if new_component == nil then new_component = true end - - new_chunk_fragment_components[new_place] = new_component - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + __fragment_call_set_and_insert_hooks(entity, fragment) end end - else - if new_chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment) - end end if old_chunk then - local old_chunk_components = old_chunk.__components + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments - for old_f, old_cs in pairs(old_chunk_components) do - local new_cs = new_chunk_components[old_f] + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + local new_cs = new_component_storages[new_ci] if new_cs then new_cs[new_place] = old_cs[old_place] end @@ -1994,36 +2097,39 @@ function evolved.remove(entity, ...) __defer() do - local old_chunk_fragments = old_chunk.__fragments - local old_chunk_components = old_chunk.__components + local old_fragment_set = old_chunk.__fragment_set + local old_component_indices = old_chunk.__component_indices + local old_component_storages = old_chunk.__component_storages if old_chunk.__has_remove_hooks then for i = 1, select('#', ...) do local fragment = select(i, ...) - if old_chunk_fragments[fragment] then - if __fragment_has_remove_hook(fragment) then - local old_chunk_fragment_components = old_chunk_components[fragment] - if old_chunk_fragment_components then - local old_component = old_chunk_fragment_components[old_place] - __fragment_call_remove_hook(entity, fragment, old_component) - else - __fragment_call_remove_hook(entity, fragment) - end + if old_fragment_set[fragment] then + local old_component_index = old_component_indices[fragment] + if old_component_index then + local old_component_storage = old_component_storages[old_component_index] + local old_component = old_component_storage[old_place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) end end end end if new_chunk then - local new_chunk_entities = new_chunk.__entities - local new_chunk_components = new_chunk.__components + local new_entities = new_chunk.__entities + local new_component_storages = new_chunk.__component_storages + local new_component_fragments = new_chunk.__component_fragments - local new_place = #new_chunk_entities + 1 + local new_place = #new_entities + 1 + new_entities[new_place] = entity - new_chunk_entities[new_place] = entity - - for new_f, new_cs in pairs(new_chunk_components) do - local old_cs = old_chunk_components[new_f] + for i = 1, #new_component_fragments do + local new_f = new_component_fragments[i] + local new_cs = new_component_storages[i] + local old_ci = old_component_indices[new_f] + local old_cs = old_component_storages[old_ci] if old_cs then new_cs[new_place] = old_cs[old_place] end @@ -2067,19 +2173,20 @@ function evolved.clear(entity) __defer() do - if chunk.__has_remove_hooks then - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components + local fragment_list = chunk.__fragment_list + local component_indices = chunk.__component_indices + local component_storages = chunk.__component_storages - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_remove_hook(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - local old_component = chunk_fragment_components[place] - __fragment_call_remove_hook(entity, fragment, old_component) - else - __fragment_call_remove_hook(entity, fragment) - end + if chunk.__has_remove_hooks then + for i = 1, #fragment_list do + local fragment = fragment_list[i] + local component_index = component_indices[fragment] + if component_index then + local component_storage = component_storages[component_index] + local old_component = component_storage[place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) end end end @@ -2117,19 +2224,20 @@ function evolved.destroy(entity) __defer() do - if chunk.__has_remove_hooks then - local chunk_fragments = chunk.__fragments - local chunk_components = chunk.__components + local fragment_list = chunk.__fragment_list + local component_indices = chunk.__component_indices + local component_storages = chunk.__component_storages - for fragment, _ in pairs(chunk_fragments) do - if __fragment_has_remove_hook(fragment) then - local chunk_fragment_components = chunk_components[fragment] - if chunk_fragment_components then - local old_component = chunk_fragment_components[place] - __fragment_call_remove_hook(entity, fragment, old_component) - else - __fragment_call_remove_hook(entity, fragment) - end + if chunk.__has_remove_hooks then + for i = 1, #fragment_list do + local fragment = fragment_list[i] + local component_index = component_indices[fragment] + if component_index then + local component_storage = component_storages[component_index] + local old_component = component_storage[place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) end end end @@ -2492,35 +2600,42 @@ function evolved.select(chunk, ...) return end - local chunk_components = chunk.__components + local indices = chunk.__component_indices + local storages = chunk.__component_storages + + local EMPTY_COMPONENT_STORAGE = __EMPTY_COMPONENT_STORAGE if fragment_count == 1 then local f1 = ... + local i1 = indices[f1] return - chunk_components[f1] or __EMPTY_COMPONENT_STORAGE + i1 and storages[i1] or EMPTY_COMPONENT_STORAGE end if fragment_count == 2 then local f1, f2 = ... + local i1, i2 = indices[f1], indices[f2] return - chunk_components[f1] or __EMPTY_COMPONENT_STORAGE, - chunk_components[f2] or __EMPTY_COMPONENT_STORAGE + i1 and storages[i1] or EMPTY_COMPONENT_STORAGE, + i2 and storages[i2] or EMPTY_COMPONENT_STORAGE end if fragment_count == 3 then local f1, f2, f3 = ... + local i1, i2, i3 = indices[f1], indices[f2], indices[f3] return - chunk_components[f1] or __EMPTY_COMPONENT_STORAGE, - chunk_components[f2] or __EMPTY_COMPONENT_STORAGE, - chunk_components[f3] or __EMPTY_COMPONENT_STORAGE + i1 and storages[i1] or EMPTY_COMPONENT_STORAGE, + i2 and storages[i2] or EMPTY_COMPONENT_STORAGE, + i3 and storages[i3] or EMPTY_COMPONENT_STORAGE end do local f1, f2, f3 = ... + local i1, i2, i3 = indices[f1], indices[f2], indices[f3] return - chunk_components[f1] or __EMPTY_COMPONENT_STORAGE, - chunk_components[f2] or __EMPTY_COMPONENT_STORAGE, - chunk_components[f3] or __EMPTY_COMPONENT_STORAGE, + i1 and storages[i1] or EMPTY_COMPONENT_STORAGE, + i2 and storages[i2] or EMPTY_COMPONENT_STORAGE, + i3 and storages[i3] or EMPTY_COMPONENT_STORAGE, evolved.select(chunk, select(4, ...)) end end @@ -2549,7 +2664,7 @@ function evolved.each(entity) each_state[1] = __structural_changes each_state[2] = chunk each_state[3] = place - each_state[4] = next(chunk.__fragments) + each_state[4] = 1 return __each_iterator, each_state end From 8a2a64660b64fd4ba6e0c64e0dd537bc9eb978c1 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 5 Jan 2025 00:26:10 +0700 Subject: [PATCH 11/23] update bench results --- develop/unbench.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index 6f51538..1d2b8f5 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -256,3 +256,29 @@ end) | create and destroy 1k entities with three components ... | PASS | us: 329.49 | op/s: 3035.00 | kb/i: 0.10 ]] + +--- +--- after chunks refactoring +--- + +--[[ lua 5.1 +| create and destroy 1k entities ... | + PASS | us: 254.45 | op/s: 3930.00 | kb/i: 0.04 +| create and destroy 1k entities with one component ... | + PASS | us: 897.32 | op/s: 1114.43 | kb/i: 0.36 +| create and destroy 1k entities with two components ... | + PASS | us: 1481.48 | op/s: 675.00 | kb/i: 0.49 +| create and destroy 1k entities with three components ... | + PASS | us: 2126.32 | op/s: 470.30 | kb/i: 0.90 +]] + +--[[ luajit 2.1 +| create and destroy 1k entities ... | + PASS | us: 12.31 | op/s: 81248.76 | kb/i: 0.00 +| create and destroy 1k entities with one component ... | + PASS | us: 46.97 | op/s: 21288.56 | kb/i: 0.02 +| create and destroy 1k entities with two components ... | + PASS | us: 75.19 | op/s: 13300.00 | kb/i: 0.03 +| create and destroy 1k entities with three components ... | + PASS | us: 108.28 | op/s: 9235.00 | kb/i: 0.06 +]] From 1ebeb5d84bb3a8ce18a1670e6c755b7837786b03 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 5 Jan 2025 07:41:28 +0700 Subject: [PATCH 12/23] more bench references --- develop/basics.lua | 58 ++++++ develop/unbench.lua | 474 +++++++++++++++++++++++++++++--------------- 2 files changed, 377 insertions(+), 155 deletions(-) create mode 100644 develop/basics.lua diff --git a/develop/basics.lua b/develop/basics.lua new file mode 100644 index 0000000..63a6daa --- /dev/null +++ b/develop/basics.lua @@ -0,0 +1,58 @@ +local basics = {} + +local __table_pack = (function() + return table.pack or function(...) + return { n = select('#', ...), ... } + end +end)() + +local __table_unpack = (function() + return table.unpack or unpack +end)() + +---@param name string +---@param loop fun(...): ... +---@param init? fun(): ... +function basics.describe_bench(name, loop, init) + collectgarbage('collect') + collectgarbage('stop') + + print(string.format('| %s ... |', name)) + + local iters = 0 + local state = init and __table_pack(init()) or {} + + pcall(function() + local warmup_s = os.clock() + repeat + loop(__table_unpack(state)) + until os.clock() - warmup_s > 0.1 + end) + + local start_s = os.clock() + local start_kb = collectgarbage('count') + + local success, result = pcall(function() + repeat + iters = iters + 1 + loop(__table_unpack(state)) + until os.clock() - start_s > 0.1 + end) + + local finish_s = os.clock() + local finish_kb = collectgarbage('count') + + print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f | iters: %d', + success and 'PASS' or 'FAIL', + (finish_s - start_s) * 1e6 / iters, + iters / (finish_s - start_s), + (finish_kb - start_kb) / iters, + iters)) + + if not success then print(' ' .. result) end + + collectgarbage('restart') + collectgarbage('collect') +end + +return basics diff --git a/develop/unbench.lua b/develop/unbench.lua index 1d2b8f5..27cac4d 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -1,157 +1,321 @@ package.loaded['evolved'] = nil local evo = require 'evolved' -local __table_pack = (function() - return table.pack or function(...) - return { n = select('#', ...), ... } - end -end)() +local basics = require 'develop.basics' -local __table_unpack = (function() - return table.unpack or unpack -end)() +local N = 1000 ----@param name string ----@param loop fun(...): ... ----@param init? fun(): ... -local function __bench_describe(name, loop, init) - collectgarbage('collect') - collectgarbage('stop') +print '----------------------------------------' - print(string.format('| %s ... |', name)) - - local iters = 0 - local state = init and __table_pack(init()) or {} - - local start_s = os.clock() - local start_kb = collectgarbage('count') - - local success, result = pcall(function() - repeat - iters = iters + 1 - loop(__table_unpack(state)) - until os.clock() - start_s > 0.2 +basics.describe_bench(string.format('create %d tables', N), + ---@param tables table[] + function(tables) + for i = 1, N do + local t = {} + tables[i] = t + end + end, function() + return {} end) - local finish_s = os.clock() - local finish_kb = collectgarbage('count') +basics.describe_bench(string.format('create and collect %d tables', N), + ---@param tables table[] + function(tables) + for i = 1, N do + local t = {} + tables[i] = t + end - print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f', - success and 'PASS' or 'FAIL', - (finish_s - start_s) * 1e6 / iters, - iters / (finish_s - start_s), - (finish_kb - start_kb) / iters)) + for i = 1, #tables do + tables[i] = nil + end - if not success then print(' ' .. result) end + collectgarbage('collect') + end, function() + return {} + end) - collectgarbage('restart') - collectgarbage('collect') -end +print '----------------------------------------' ----@param tables table[] -__bench_describe('create and destroy 1k tables', function(tables) - for i = 1, 1000 do - local t = {} - tables[i] = t - end +basics.describe_bench(string.format('create %d tables with 1 component / AoS', N), + ---@param f1 table + ---@param tables table + function(f1, tables) + for i = 1, N do + local e = {} + e[f1] = true + tables[i] = e + end + end, function() + local f1 = {} + return f1, {} + end) - for i = 1, #tables do - tables[i] = nil - end +basics.describe_bench(string.format('create %d tables with 2 component / AoS', N), + ---@param f1 table + ---@param f2 table + ---@param tables table + function(f1, f2, tables) + for i = 1, N do + local e = {} + e[f1] = true + e[f2] = true + tables[i] = e + end + end, function() + local f1, f2 = {}, {} + return f1, f2, {} + end) - collectgarbage('collect') -end, function() - return {} -end) +basics.describe_bench(string.format('create %d tables with 3 component / AoS', N), + ---@param f1 table + ---@param f2 table + ---@param f3 table + ---@param tables table + function(f1, f2, f3, tables) + for i = 1, N do + local e = {} + e[f1] = true + e[f2] = true + e[f3] = true + tables[i] = e + end + end, function() + local f1, f2, f3 = {}, {}, {} + return f1, f2, f3, {} + end) ----@param entities evolved.id[] -__bench_describe('create and destroy 1k entities', function(entities) - local id = evo.id - local destroy = evo.destroy +basics.describe_bench(string.format('create %d tables with 4 component / AoS', N), + ---@param f1 table + ---@param f2 table + ---@param f3 table + ---@param f4 table + ---@param tables table + function(f1, f2, f3, f4, tables) + for i = 1, N do + local e = {} + e[f1] = true + e[f2] = true + e[f3] = true + e[f4] = true + tables[i] = e + end + end, function() + local f1, f2, f3, f4 = {}, {}, {}, {} + return f1, f2, f3, f4, {} + end) - for i = 1, 1000 do - local e = id() - entities[i] = e - end +print '----------------------------------------' - for i = 1, #entities do - destroy(entities[i]) - end -end, function() - return {} -end) +basics.describe_bench(string.format('create %d tables with 1 component / SoA', N), + ---@param f1 table + ---@param tables table + function(f1, tables) + local fs1 = {} + for i = 1, N do + local e = {} + fs1[i] = true + tables[i] = e + end + tables[f1] = fs1 + end, function() + local f1 = {} + return f1, {} + end) ----@param f1 evolved.fragment ----@param entities evolved.id[] -__bench_describe('create and destroy 1k entities with one component', function(f1, entities) - local id = evo.id - local insert = evo.insert - local destroy = evo.destroy +basics.describe_bench(string.format('create %d tables with 2 component / SoA', N), + ---@param f1 table + ---@param f2 table + ---@param tables table + function(f1, f2, tables) + local fs1 = {} + local fs2 = {} + for i = 1, N do + local e = {} + fs1[i] = true + fs2[i] = true + tables[i] = e + end + tables[f1] = fs1 + tables[f2] = fs2 + end, function() + local f1, f2 = {}, {} + return f1, f2, {} + end) - for i = 1, 1000 do - local e = id() - entities[i] = e +basics.describe_bench(string.format('create %d tables with 3 component / SoA', N), + ---@param f1 table + ---@param f2 table + ---@param f3 table + ---@param tables table + function(f1, f2, f3, tables) + local fs1 = {} + local fs2 = {} + local fs3 = {} + for i = 1, N do + local e = {} + fs1[i] = true + fs2[i] = true + fs3[i] = true + tables[i] = e + end + tables[f1] = fs1 + tables[f2] = fs2 + tables[f3] = fs3 + end, function() + local f1, f2, f3 = {}, {}, {} + return f1, f2, f3, {} + end) - insert(e, f1) - end +basics.describe_bench(string.format('create %d tables with 4 component / SoA', N), + ---@param f1 table + ---@param f2 table + ---@param f3 table + ---@param f4 table + ---@param tables table + function(f1, f2, f3, f4, tables) + local fs1 = {} + local fs2 = {} + local fs3 = {} + local fs4 = {} + for i = 1, N do + local e = {} + fs1[i] = i + fs2[i] = i + fs3[i] = i + fs4[i] = i + tables[i] = e + end + tables[f1] = fs1 + tables[f2] = fs2 + tables[f3] = fs3 + tables[f4] = fs4 + end, function() + local f1, f2, f3, f4 = {}, {}, {}, {} + return f1, f2, f3, f4, {} + end) - for i = 1, #entities do - destroy(entities[i]) - end -end, function() - local f1 = evo.id(2) - return f1, {} -end) +print '----------------------------------------' ----@param f1 evolved.fragment ----@param f2 evolved.fragment ----@param entities evolved.id[] -__bench_describe('create and destroy 1k entities with two components', function(f1, f2, entities) - local id = evo.id - local insert = evo.insert - local destroy = evo.destroy +basics.describe_bench(string.format('create and destroy %d entities', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local destroy = evo.destroy - for i = 1, 1000 do - local e = id() - entities[i] = e + for i = 1, N do + local e = id() + entities[i] = e + end - insert(e, f1) - insert(e, f2) - end + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) - for i = 1, #entities do - destroy(entities[i]) - end -end, function() - local f1, f2 = evo.id(2) - return f1, f2, {} -end) +basics.describe_bench(string.format('create and destroy %d entities with 1 component', N), + ---@param f1 evolved.fragment + ---@param entities evolved.id[] + function(f1, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy ----@param f1 evolved.fragment ----@param f2 evolved.fragment ----@param f3 evolved.fragment ----@param entities evolved.id[] -__bench_describe('create and destroy 1k entities with three components', function(f1, f2, f3, entities) - local id = evo.id - local insert = evo.insert - local destroy = evo.destroy + for i = 1, N do + local e = id() + insert(e, f1) + entities[i] = e + end - for i = 1, 1000 do - local e = id() - entities[i] = e + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local f1 = evo.id(2) + return f1, {} + end) - insert(e, f1) - insert(e, f2) - insert(e, f3) - end +basics.describe_bench(string.format('create and destroy %d entities with 2 components', N), + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param entities evolved.id[] + function(f1, f2, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy - for i = 1, #entities do - destroy(entities[i]) - end -end, function() - local f1, f2, f3 = evo.id(3) - return f1, f2, f3, {} -end) + for i = 1, N do + local e = id() + insert(e, f1) + insert(e, f2) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local f1, f2 = evo.id(2) + return f1, f2, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components', N), + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param f3 evolved.fragment + ---@param entities evolved.id[] + function(f1, f2, f3, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, N do + local e = id() + insert(e, f1) + insert(e, f2) + insert(e, f3) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local f1, f2, f3 = evo.id(3) + return f1, f2, f3, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components', N), + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param f3 evolved.fragment + ---@param f4 evolved.fragment + ---@param entities evolved.id[] + function(f1, f2, f3, f4, entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, N do + local e = id() + insert(e, f1) + insert(e, f2) + insert(e, f3) + insert(e, f4) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local f1, f2, f3, f4 = evo.id(4) + return f1, f2, f3, f4, {} + end) + +print '----------------------------------------' --- --- initial @@ -160,22 +324,22 @@ end) --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 312.60 | op/s: 3199.00 | kb/i: 0.05 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 1570.31 | op/s: 636.82 | kb/i: 0.63 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 2780.82 | op/s: 359.61 | kb/i: 0.91 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 4060.00 | op/s: 246.31 | kb/i: 1.67 ]] --[[ luajit 2.1 | create and destroy 1k entities ... | PASS | us: 12.22 | op/s: 81840.80 | kb/i: 0.00 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 56.22 | op/s: 17786.07 | kb/i: 0.02 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 412.73 | op/s: 2422.89 | kb/i: 0.11 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 611.62 | op/s: 1635.00 | kb/i: 0.17 ]] @@ -186,22 +350,22 @@ end) --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 1248.45 | op/s: 801.00 | kb/i: 0.50 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 2208.79 | op/s: 452.74 | kb/i: 0.73 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 3278.69 | op/s: 305.00 | kb/i: 1.37 ]] --[[ luajit 2.1 | create and destroy 1k entities ... | PASS | us: 12.12 | op/s: 82482.59 | kb/i: 0.00 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 69.05 | op/s: 14482.59 | kb/i: 0.03 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 400.40 | op/s: 2497.51 | kb/i: 0.09 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 574.71 | op/s: 1740.00 | kb/i: 0.14 ]] @@ -212,22 +376,22 @@ end) --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 1005.03 | op/s: 995.00 | kb/i: 0.41 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 1747.83 | op/s: 572.14 | kb/i: 0.59 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 2576.92 | op/s: 388.06 | kb/i: 1.08 ]] --[[ luajit 2.1 | create and destroy 1k entities ... | PASS | us: 12.20 | op/s: 81940.30 | kb/i: 0.00 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 53.66 | op/s: 18636.82 | kb/i: 0.02 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 357.02 | op/s: 2801.00 | kb/i: 0.09 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 533.33 | op/s: 1875.00 | kb/i: 0.15 ]] @@ -238,22 +402,22 @@ end) --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 253.49 | op/s: 3945.00 | kb/i: 0.04 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 913.64 | op/s: 1094.53 | kb/i: 0.37 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 1562.50 | op/s: 640.00 | kb/i: 0.53 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 2280.90 | op/s: 438.42 | kb/i: 0.97 ]] --[[ luajit 2.1 | create and destroy 1k entities ... | PASS | us: 12.05 | op/s: 82995.02 | kb/i: 0.00 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 53.61 | op/s: 18651.74 | kb/i: 0.02 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 232.02 | op/s: 4310.00 | kb/i: 0.06 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 329.49 | op/s: 3035.00 | kb/i: 0.10 ]] @@ -264,21 +428,21 @@ end) --[[ lua 5.1 | create and destroy 1k entities ... | PASS | us: 254.45 | op/s: 3930.00 | kb/i: 0.04 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 897.32 | op/s: 1114.43 | kb/i: 0.36 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 1481.48 | op/s: 675.00 | kb/i: 0.49 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 2126.32 | op/s: 470.30 | kb/i: 0.90 ]] --[[ luajit 2.1 | create and destroy 1k entities ... | PASS | us: 12.31 | op/s: 81248.76 | kb/i: 0.00 -| create and destroy 1k entities with one component ... | +| create and destroy 1k entities with 1 component ... | PASS | us: 46.97 | op/s: 21288.56 | kb/i: 0.02 -| create and destroy 1k entities with two components ... | +| create and destroy 1k entities with 2 components ... | PASS | us: 75.19 | op/s: 13300.00 | kb/i: 0.03 -| create and destroy 1k entities with three components ... | +| create and destroy 1k entities with 3 components ... | PASS | us: 108.28 | op/s: 9235.00 | kb/i: 0.06 ]] From 50e4372311d452b2d811027196ac6fddc40d328a Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 6 Jan 2025 05:32:46 +0700 Subject: [PATCH 13/23] fix bench basics --- develop/basics.lua | 70 +++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/develop/basics.lua b/develop/basics.lua index 63a6daa..0677c29 100644 --- a/develop/basics.lua +++ b/develop/basics.lua @@ -14,42 +14,54 @@ end)() ---@param loop fun(...): ... ---@param init? fun(): ... function basics.describe_bench(name, loop, init) + print(string.format('| %s ... |', name)) + + local state = init and __table_pack(init()) or {} + + do + local warmup_s = os.clock() + + local success, result = pcall(function() + repeat + loop(__table_unpack(state)) + until os.clock() - warmup_s > 0.1 + end) + + if not success then + print('|-- FAIL: ' .. result) + return + end + end + collectgarbage('collect') collectgarbage('stop') - print(string.format('| %s ... |', name)) + do + local iters = 0 - local iters = 0 - local state = init and __table_pack(init()) or {} + local start_s = os.clock() + local start_kb = collectgarbage('count') - pcall(function() - local warmup_s = os.clock() - repeat - loop(__table_unpack(state)) - until os.clock() - warmup_s > 0.1 - end) + local success, result = pcall(function() + repeat + iters = iters + 1 + loop(__table_unpack(state)) + until os.clock() - start_s > 0.1 + end) - local start_s = os.clock() - local start_kb = collectgarbage('count') + local finish_s = os.clock() + local finish_kb = collectgarbage('count') - local success, result = pcall(function() - repeat - iters = iters + 1 - loop(__table_unpack(state)) - until os.clock() - start_s > 0.1 - end) - - local finish_s = os.clock() - local finish_kb = collectgarbage('count') - - print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f | iters: %d', - success and 'PASS' or 'FAIL', - (finish_s - start_s) * 1e6 / iters, - iters / (finish_s - start_s), - (finish_kb - start_kb) / iters, - iters)) - - if not success then print(' ' .. result) end + if success then + print(string.format('|-- PASS | us: %.2f | op/s: %.2f | kb/i: %.2f | iters: %d', + (finish_s - start_s) * 1e6 / iters, + iters / (finish_s - start_s), + (finish_kb - start_kb) / iters, + iters)) + else + print('|-- FAIL: ' .. result) + end + end collectgarbage('restart') collectgarbage('collect') From 74247d39f9f75d0e073440a01fb3bd411b12819c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 7 Jan 2025 08:28:43 +0700 Subject: [PATCH 14/23] add builder benches --- develop/unbench.lua | 125 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index 27cac4d..22bf4c8 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -317,6 +317,131 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo print '----------------------------------------' +basics.describe_bench(string.format('create and destroy %d entities / builder', N), + ---@param b evolved.entity_builder + ---@param entities evolved.id[] + function(b, entities) + local build = b.build + local destroy = evo.destroy + + for i = 1, N do + entities[i] = build(b) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local b = evo.entity() + return b, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 1 components / builder', N), + ---@param b evolved.entity_builder + ---@param f1 evolved.fragment + ---@param entities evolved.id[] + function(b, f1, entities) + local set = b.set + local build = b.build + local destroy = evo.destroy + + for i = 1, N do + set(b, f1) + entities[i] = build(b) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local b = evo.entity() + local f1 = evo.id(1) + return b, f1, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / builder', N), + ---@param b evolved.entity_builder + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param entities evolved.id[] + function(b, f1, f2, entities) + local set = b.set + local build = b.build + local destroy = evo.destroy + + for i = 1, N do + set(b, f1) + set(b, f2) + entities[i] = build(b) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local b = evo.entity() + local f1, f2 = evo.id(2) + return b, f1, f2, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / builder', N), + ---@param b evolved.entity_builder + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param f3 evolved.fragment + ---@param entities evolved.id[] + function(b, f1, f2, f3, entities) + local set = b.set + local build = b.build + local destroy = evo.destroy + + for i = 1, N do + set(b, f1) + set(b, f2) + set(b, f3) + entities[i] = build(b) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local b = evo.entity() + local f1, f2, f3 = evo.id(3) + return b, f1, f2, f3, {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / builder', N), + ---@param b evolved.entity_builder + ---@param f1 evolved.fragment + ---@param f2 evolved.fragment + ---@param f3 evolved.fragment + ---@param f4 evolved.fragment + ---@param entities evolved.id[] + function(b, f1, f2, f3, f4, entities) + local set = b.set + local build = b.build + local destroy = evo.destroy + + for i = 1, N do + set(b, f1) + set(b, f2) + set(b, f3) + set(b, f4) + entities[i] = build(b) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + local b = evo.entity() + local f1, f2, f3, f4 = evo.id(4) + return b, f1, f2, f3, f4, {} + end) + +print '----------------------------------------' + --- --- initial --- From e576f383ca1c9fa016caeedfc5fc2eef7adab119 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 7 Jan 2025 11:47:09 +0700 Subject: [PATCH 15/23] update benches --- develop/unbench.lua | 333 ++++++++++++++++++++++++-------------------- 1 file changed, 179 insertions(+), 154 deletions(-) diff --git a/develop/unbench.lua b/develop/unbench.lua index 22bf4c8..d8cbc85 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -4,6 +4,8 @@ local evo = require 'evolved' local basics = require 'develop.basics' local N = 1000 +local B = evo.entity() +local F1, F2, F3, F4, F5 = evo.id(5) print '----------------------------------------' @@ -38,96 +40,94 @@ basics.describe_bench(string.format('create and collect %d tables', N), print '----------------------------------------' basics.describe_bench(string.format('create %d tables with 1 component / AoS', N), - ---@param f1 table ---@param tables table - function(f1, tables) + function(tables) for i = 1, N do local e = {} - e[f1] = true + e[F1] = true tables[i] = e end end, function() - local f1 = {} - return f1, {} + return {} end) basics.describe_bench(string.format('create %d tables with 2 component / AoS', N), - ---@param f1 table - ---@param f2 table ---@param tables table - function(f1, f2, tables) + function(tables) for i = 1, N do local e = {} - e[f1] = true - e[f2] = true + e[F1] = true + e[F2] = true tables[i] = e end end, function() - local f1, f2 = {}, {} - return f1, f2, {} + return {} end) basics.describe_bench(string.format('create %d tables with 3 component / AoS', N), - ---@param f1 table - ---@param f2 table - ---@param f3 table ---@param tables table - function(f1, f2, f3, tables) + function(tables) for i = 1, N do local e = {} - e[f1] = true - e[f2] = true - e[f3] = true + e[F1] = true + e[F2] = true + e[F3] = true tables[i] = e end end, function() - local f1, f2, f3 = {}, {}, {} - return f1, f2, f3, {} + return {} end) basics.describe_bench(string.format('create %d tables with 4 component / AoS', N), - ---@param f1 table - ---@param f2 table - ---@param f3 table - ---@param f4 table ---@param tables table - function(f1, f2, f3, f4, tables) + function(tables) for i = 1, N do local e = {} - e[f1] = true - e[f2] = true - e[f3] = true - e[f4] = true + e[F1] = true + e[F2] = true + e[F3] = true + e[F4] = true tables[i] = e end end, function() - local f1, f2, f3, f4 = {}, {}, {}, {} - return f1, f2, f3, f4, {} + return {} + end) + +basics.describe_bench(string.format('create %d tables with 5 component / AoS', N), + ---@param tables table + function(tables) + for i = 1, N do + local e = {} + e[F1] = true + e[F2] = true + e[F3] = true + e[F4] = true + e[F5] = true + tables[i] = e + end + end, function() + return {} end) print '----------------------------------------' basics.describe_bench(string.format('create %d tables with 1 component / SoA', N), - ---@param f1 table ---@param tables table - function(f1, tables) + function(tables) local fs1 = {} for i = 1, N do local e = {} fs1[i] = true tables[i] = e end - tables[f1] = fs1 + tables[F1] = fs1 end, function() - local f1 = {} - return f1, {} + return {} end) basics.describe_bench(string.format('create %d tables with 2 component / SoA', N), - ---@param f1 table - ---@param f2 table ---@param tables table - function(f1, f2, tables) + function(tables) local fs1 = {} local fs2 = {} for i = 1, N do @@ -136,19 +136,15 @@ basics.describe_bench(string.format('create %d tables with 2 component / SoA', N fs2[i] = true tables[i] = e end - tables[f1] = fs1 - tables[f2] = fs2 + tables[F1] = fs1 + tables[F2] = fs2 end, function() - local f1, f2 = {}, {} - return f1, f2, {} + return {} end) basics.describe_bench(string.format('create %d tables with 3 component / SoA', N), - ---@param f1 table - ---@param f2 table - ---@param f3 table ---@param tables table - function(f1, f2, f3, tables) + function(tables) local fs1 = {} local fs2 = {} local fs3 = {} @@ -159,21 +155,16 @@ basics.describe_bench(string.format('create %d tables with 3 component / SoA', N fs3[i] = true tables[i] = e end - tables[f1] = fs1 - tables[f2] = fs2 - tables[f3] = fs3 + tables[F1] = fs1 + tables[F2] = fs2 + tables[F3] = fs3 end, function() - local f1, f2, f3 = {}, {}, {} - return f1, f2, f3, {} + return {} end) basics.describe_bench(string.format('create %d tables with 4 component / SoA', N), - ---@param f1 table - ---@param f2 table - ---@param f3 table - ---@param f4 table ---@param tables table - function(f1, f2, f3, f4, tables) + function(tables) local fs1 = {} local fs2 = {} local fs3 = {} @@ -186,13 +177,38 @@ basics.describe_bench(string.format('create %d tables with 4 component / SoA', N fs4[i] = i tables[i] = e end - tables[f1] = fs1 - tables[f2] = fs2 - tables[f3] = fs3 - tables[f4] = fs4 + tables[F1] = fs1 + tables[F2] = fs2 + tables[F3] = fs3 + tables[F4] = fs4 end, function() - local f1, f2, f3, f4 = {}, {}, {}, {} - return f1, f2, f3, f4, {} + return {} + end) + +basics.describe_bench(string.format('create %d tables with 5 component / SoA', N), + ---@param tables table + function(tables) + local fs1 = {} + local fs2 = {} + local fs3 = {} + local fs4 = {} + local fs5 = {} + for i = 1, N do + local e = {} + fs1[i] = i + fs2[i] = i + fs3[i] = i + fs4[i] = i + fs5[i] = i + tables[i] = e + end + tables[F1] = fs1 + tables[F2] = fs2 + tables[F3] = fs3 + tables[F4] = fs4 + tables[F5] = fs5 + end, function() + return {} end) print '----------------------------------------' @@ -216,16 +232,15 @@ basics.describe_bench(string.format('create and destroy %d entities', N), end) basics.describe_bench(string.format('create and destroy %d entities with 1 component', N), - ---@param f1 evolved.fragment ---@param entities evolved.id[] - function(f1, entities) + function(entities) local id = evo.id local insert = evo.insert local destroy = evo.destroy for i = 1, N do local e = id() - insert(e, f1) + insert(e, F1) entities[i] = e end @@ -233,23 +248,20 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo destroy(entities[i]) end end, function() - local f1 = evo.id(2) - return f1, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 2 components', N), - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment ---@param entities evolved.id[] - function(f1, f2, entities) + function(entities) local id = evo.id local insert = evo.insert local destroy = evo.destroy for i = 1, N do local e = id() - insert(e, f1) - insert(e, f2) + insert(e, F1) + insert(e, F2) entities[i] = e end @@ -257,25 +269,21 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo destroy(entities[i]) end end, function() - local f1, f2 = evo.id(2) - return f1, f2, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 3 components', N), - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment - ---@param f3 evolved.fragment ---@param entities evolved.id[] - function(f1, f2, f3, entities) + function(entities) local id = evo.id local insert = evo.insert local destroy = evo.destroy for i = 1, N do local e = id() - insert(e, f1) - insert(e, f2) - insert(e, f3) + insert(e, F1) + insert(e, F2) + insert(e, F3) entities[i] = e end @@ -283,27 +291,22 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo destroy(entities[i]) end end, function() - local f1, f2, f3 = evo.id(3) - return f1, f2, f3, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 4 components', N), - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment - ---@param f3 evolved.fragment - ---@param f4 evolved.fragment ---@param entities evolved.id[] - function(f1, f2, f3, f4, entities) + function(entities) local id = evo.id local insert = evo.insert local destroy = evo.destroy for i = 1, N do local e = id() - insert(e, f1) - insert(e, f2) - insert(e, f3) - insert(e, f4) + insert(e, F1) + insert(e, F2) + insert(e, F3) + insert(e, F4) entities[i] = e end @@ -311,133 +314,155 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo destroy(entities[i]) end end, function() - local f1, f2, f3, f4 = evo.id(4) - return f1, f2, f3, f4, {} + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + for i = 1, N do + local e = id() + insert(e, F1) + insert(e, F2) + insert(e, F3) + insert(e, F4) + insert(e, F5) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} end) print '----------------------------------------' basics.describe_bench(string.format('create and destroy %d entities / builder', N), - ---@param b evolved.entity_builder ---@param entities evolved.id[] - function(b, entities) - local build = b.build + function(entities) + local build = B.build local destroy = evo.destroy for i = 1, N do - entities[i] = build(b) + entities[i] = build(B) end for i = 1, #entities do destroy(entities[i]) end end, function() - local b = evo.entity() - return b, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 1 components / builder', N), - ---@param b evolved.entity_builder - ---@param f1 evolved.fragment ---@param entities evolved.id[] - function(b, f1, entities) - local set = b.set - local build = b.build + function(entities) + local set = B.set + local build = B.build local destroy = evo.destroy for i = 1, N do - set(b, f1) - entities[i] = build(b) + set(B, F1) + entities[i] = build(B) end for i = 1, #entities do destroy(entities[i]) end end, function() - local b = evo.entity() - local f1 = evo.id(1) - return b, f1, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 2 components / builder', N), - ---@param b evolved.entity_builder - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment ---@param entities evolved.id[] - function(b, f1, f2, entities) - local set = b.set - local build = b.build + function(entities) + local set = B.set + local build = B.build local destroy = evo.destroy for i = 1, N do - set(b, f1) - set(b, f2) - entities[i] = build(b) + set(B, F1) + set(B, F2) + entities[i] = build(B) end for i = 1, #entities do destroy(entities[i]) end end, function() - local b = evo.entity() - local f1, f2 = evo.id(2) - return b, f1, f2, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 3 components / builder', N), - ---@param b evolved.entity_builder - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment - ---@param f3 evolved.fragment ---@param entities evolved.id[] - function(b, f1, f2, f3, entities) - local set = b.set - local build = b.build + function(entities) + local set = B.set + local build = B.build local destroy = evo.destroy for i = 1, N do - set(b, f1) - set(b, f2) - set(b, f3) - entities[i] = build(b) + set(B, F1) + set(B, F2) + set(B, F3) + entities[i] = build(B) end for i = 1, #entities do destroy(entities[i]) end end, function() - local b = evo.entity() - local f1, f2, f3 = evo.id(3) - return b, f1, f2, f3, {} + return {} end) basics.describe_bench(string.format('create and destroy %d entities with 4 components / builder', N), - ---@param b evolved.entity_builder - ---@param f1 evolved.fragment - ---@param f2 evolved.fragment - ---@param f3 evolved.fragment - ---@param f4 evolved.fragment ---@param entities evolved.id[] - function(b, f1, f2, f3, f4, entities) - local set = b.set - local build = b.build + function(entities) + local set = B.set + local build = B.build local destroy = evo.destroy for i = 1, N do - set(b, f1) - set(b, f2) - set(b, f3) - set(b, f4) - entities[i] = build(b) + set(B, F1) + set(B, F2) + set(B, F3) + set(B, F4) + entities[i] = build(B) end for i = 1, #entities do destroy(entities[i]) end end, function() - local b = evo.entity() - local f1, f2, f3, f4 = evo.id(4) - return b, f1, f2, f3, f4, {} + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / builder', N), + ---@param entities evolved.id[] + function(entities) + local set = B.set + local build = B.build + local destroy = evo.destroy + + for i = 1, N do + set(B, F1) + set(B, F2) + set(B, F3) + set(B, F4) + set(B, F5) + entities[i] = build(B) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} end) print '----------------------------------------' From d5a2a4ed41e6d64296db28b918afeeaed0d3e102 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 10 Jan 2025 09:04:25 +0700 Subject: [PATCH 16/23] add multi_set benches --- develop/unbench.lua | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/develop/unbench.lua b/develop/unbench.lua index d8cbc85..b694235 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -467,6 +467,103 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' +basics.describe_bench(string.format('create and destroy %d entities with 1 components / multi-set', N), + ---@param entities evolved.id[] + function(entities) + local set = evo.multi_set + local destroy = evo.destroy + + for i = 1, N do + local e = evo.id() + set(e, { F1 }) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / multi-set', N), + ---@param entities evolved.id[] + function(entities) + local set = evo.multi_set + local destroy = evo.destroy + + for i = 1, N do + local e = evo.id() + set(e, { F1, F2 }) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / multi-set', N), + ---@param entities evolved.id[] + function(entities) + local set = evo.multi_set + local destroy = evo.destroy + + for i = 1, N do + local e = evo.id() + set(e, { F1, F2, F3 }) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / multi-set', N), + ---@param entities evolved.id[] + function(entities) + local set = evo.multi_set + local destroy = evo.destroy + + for i = 1, N do + local e = evo.id() + set(e, { F1, F2, F3, F4 }) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / multi-set', N), + ---@param entities evolved.id[] + function(entities) + local set = evo.multi_set + local destroy = evo.destroy + + for i = 1, N do + local e = evo.id() + set(e, { F1, F2, F3, F4, F5 }) + entities[i] = e + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +print '----------------------------------------' + --- --- initial --- From e017cc746c130bc0559d2475060843b8232dd7b3 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 10 Jan 2025 10:44:11 +0700 Subject: [PATCH 17/23] add defer benches --- develop/unbench.lua | 111 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/develop/unbench.lua b/develop/unbench.lua index b694235..945d3f9 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -343,15 +343,20 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' -basics.describe_bench(string.format('create and destroy %d entities / builder', N), +basics.describe_bench(string.format('create and destroy %d entities with 1 components / defer', N), ---@param entities evolved.id[] function(entities) - local build = B.build + local id = evo.id + local insert = evo.insert local destroy = evo.destroy + evo.defer() for i = 1, N do - entities[i] = build(B) + local e = id() + insert(e, F1) + entities[i] = e end + evo.commit() for i = 1, #entities do destroy(entities[i]) @@ -360,6 +365,106 @@ basics.describe_bench(string.format('create and destroy %d entities / builder', return {} end) +basics.describe_bench(string.format('create and destroy %d entities with 2 components / defer', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + evo.defer() + for i = 1, N do + local e = id() + insert(e, F1) + insert(e, F2) + entities[i] = e + end + evo.commit() + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / defer', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + evo.defer() + for i = 1, N do + local e = id() + insert(e, F1) + insert(e, F2) + insert(e, F3) + entities[i] = e + end + evo.commit() + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / defer', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + evo.defer() + for i = 1, N do + local e = id() + insert(e, F1) + insert(e, F2) + insert(e, F3) + insert(e, F4) + entities[i] = e + end + evo.commit() + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / defer', N), + ---@param entities evolved.id[] + function(entities) + local id = evo.id + local insert = evo.insert + local destroy = evo.destroy + + evo.defer() + for i = 1, N do + local e = id() + insert(e, F1) + insert(e, F2) + insert(e, F3) + insert(e, F4) + insert(e, F5) + entities[i] = e + end + evo.commit() + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +print '----------------------------------------' + basics.describe_bench(string.format('create and destroy %d entities with 1 components / builder', N), ---@param entities evolved.id[] function(entities) From 0110899aaee27ad8eebc1bbc9ef7bc80ae8f67a2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 11 Jan 2025 12:09:41 +0700 Subject: [PATCH 18/23] opt: we don't need to read old component for hooks in some cases --- evolved.lua | 65 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/evolved.lua b/evolved.lua index 5dfea6c..e7222d6 100644 --- a/evolved.lua +++ b/evolved.lua @@ -2020,24 +2020,27 @@ function evolved.set(entity, fragment, ...) if old_component_index then local old_component_storage = old_component_storages[old_component_index] - local old_component = old_component_storage[old_place] if old_chunk.__has_defaults_or_constructs then local new_component = __component_construct(fragment, ...) - old_component_storage[old_place] = new_component - if old_chunk.__has_set_or_assign_hooks then + local old_component = old_component_storage[old_place] + old_component_storage[old_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + old_component_storage[old_place] = new_component end else local new_component = ... if new_component == nil then new_component = true end - old_component_storage[old_place] = new_component - if old_chunk.__has_set_or_assign_hooks then + local old_component = old_component_storage[old_place] + old_component_storage[old_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + old_component_storage[old_place] = new_component end end else @@ -2145,24 +2148,27 @@ function evolved.assign(entity, fragment, ...) if component_index then local component_storage = component_storages[component_index] - local old_component = component_storage[place] if chunk.__has_defaults_or_constructs then local new_component = __component_construct(fragment, ...) - component_storage[place] = new_component - if chunk.__has_set_or_assign_hooks then + local old_component = component_storage[place] + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + component_storage[place] = new_component end else local new_component = ... if new_component == nil then new_component = true end - component_storage[place] = new_component - if chunk.__has_set_or_assign_hooks then + local old_component = component_storage[place] + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + component_storage[place] = new_component end end else @@ -2507,7 +2513,6 @@ function evolved.multi_set(entity, fragments, components) if old_component_index then local old_component_storage = old_component_storages[old_component_index] - local old_component = old_component_storage[old_place] if old_chunk.__has_defaults_or_constructs then local new_component = components[i] @@ -2515,19 +2520,23 @@ function evolved.multi_set(entity, fragments, components) if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end if new_component == nil then new_component = true end - old_component_storage[old_place] = new_component - if old_chunk.__has_set_or_assign_hooks then + local old_component = old_component_storage[old_place] + old_component_storage[old_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + old_component_storage[old_place] = new_component end else local new_component = components[i] if new_component == nil then new_component = true end - old_component_storage[old_place] = new_component - if old_chunk.__has_set_or_assign_hooks then + local old_component = old_component_storage[old_place] + old_component_storage[old_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + old_component_storage[old_place] = new_component end end else @@ -2573,7 +2582,6 @@ function evolved.multi_set(entity, fragments, components) if new_component_index then local new_component_storage = new_component_storages[new_component_index] - local old_component = new_component_storage[new_place] if new_chunk.__has_defaults_or_constructs then local new_component = components[i] @@ -2581,19 +2589,23 @@ function evolved.multi_set(entity, fragments, components) if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end if new_component == nil then new_component = true end - new_component_storage[new_place] = new_component - if new_chunk.__has_set_or_assign_hooks then + local old_component = new_component_storage[new_place] + new_component_storage[new_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + new_component_storage[new_place] = new_component end else local new_component = components[i] if new_component == nil then new_component = true end - new_component_storage[new_place] = new_component - if new_chunk.__has_set_or_assign_hooks then + local old_component = new_component_storage[new_place] + new_component_storage[new_place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + new_component_storage[new_place] = new_component end end else @@ -2698,7 +2710,6 @@ function evolved.multi_assign(entity, fragments, components) if component_index then local component_storage = component_storages[component_index] - local old_component = component_storage[place] if chunk.__has_defaults_or_constructs then local new_component = components[i] @@ -2706,19 +2717,23 @@ function evolved.multi_assign(entity, fragments, components) if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end if new_component == nil then new_component = true end - component_storage[place] = new_component - if chunk.__has_set_or_assign_hooks then + local old_component = component_storage[place] + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + component_storage[place] = new_component end else local new_component = components[i] if new_component == nil then new_component = true end - component_storage[place] = new_component - if chunk.__has_set_or_assign_hooks then + local old_component = component_storage[place] + component_storage[place] = new_component __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + else + component_storage[place] = new_component end end else From 1620738b518f1721828c8c11d6236b30059c410d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 14 Jan 2025 14:31:32 +0700 Subject: [PATCH 19/23] opt: get chunk by fragments without sorting --- evolved.lua | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/evolved.lua b/evolved.lua index 54f2da0..faf4618 100644 --- a/evolved.lua +++ b/evolved.lua @@ -3318,27 +3318,16 @@ function evolved.chunk(...) return end - ---@type evolved.fragment[] - local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) - - for i = 1, fragment_count do - local fragment = select(i, ...) - fragment_list[#fragment_list + 1] = fragment - end - - table.sort(fragment_list) - - local root_fragment = fragment_list[1] + local root_fragment = select(1, ...) local chunk = __root_chunks[root_fragment] or __root_chunk(root_fragment) for i = 2, fragment_count do - local child_fragment = fragment_list[i] + local child_fragment = select(i, ...) chunk = chunk.__with_fragment_edges[child_fragment] or __chunk_with_fragment(chunk, child_fragment) end - __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) return chunk, chunk.__entities end From 76afc420f405a86353a54008554815ebccc9b00f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 16 Jan 2025 06:41:35 +0700 Subject: [PATCH 20/23] evolved.spawn/spawn_at impl (without deferring support yet) --- README.md | 5 + develop/unbench.lua | 256 +++++++++++++++++++++++++++++ develop/untests.lua | 391 ++++++++++++++++++++++++++++++++++++++++++++ evolved.lua | 345 +++++++++++++++++++++++++++++++++----- 4 files changed, 960 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index afb055e..49b8b92 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ each :: entity -> {each_state? -> fragment?, component?}, each_state? execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state? ``` +``` +spawn :: fragment[]?, component[]? -> entity, boolean +spawn_at :: chunk?, fragment[]?, component[]? -> entity, boolean +``` + ``` entity :: entity_builder entity_builder:set :: fragment, any... -> entity_builder diff --git a/develop/unbench.lua b/develop/unbench.lua index 945d3f9..3a8707b 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -669,6 +669,262 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' +basics.describe_bench(string.format('create and destroy %d entities / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = {} + local components = {} + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = { F1 } + local components = { true } + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = { F1, F2 } + local components = { true, true } + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = { F1, F2, F3 } + local components = { true, true, true } + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4 } + local components = { true, true, true, true } + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4, F5 } + local components = { true, true, true, true, true } + + for i = 1, N do + entities[i] = spawn(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +print '----------------------------------------' + +basics.describe_bench(string.format('create and destroy %d entities / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = {} + local components = {} + + local chunk = evo.chunk() + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = { F1 } + local components = { true } + + local chunk = evo.chunk(F1) + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 components / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = { F1, F2 } + local components = { true, true } + + local chunk = evo.chunk(F1, F2) + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 components / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = { F1, F2, F3 } + local components = { true, true, true } + + local chunk = evo.chunk(F1, F2, F3) + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4 } + local components = { true, true, true, true } + + local chunk = evo.chunk(F1, F2, F3, F4) + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / spawn_at', N), + ---@param entities evolved.id[] + function(entities) + local spawn_at = evo.spawn_at + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4, F5 } + local components = { true, true, true, true, true } + + local chunk = evo.chunk(F1, F2, F3, F4, F5) + + for i = 1, N do + entities[i] = spawn_at(chunk, fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +print '----------------------------------------' + --- --- initial --- diff --git a/develop/untests.lua b/develop/untests.lua index ddef23d..93aacf4 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -3146,3 +3146,394 @@ do assert(evo.is_alive(e) and evo.is_empty(e)) end end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f3, evo.TAG) + + do + local e = evo.spawn() + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn({}) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e1 = evo.spawn({ f1 }) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + + local e2 = evo.spawn({ f1 }, {}) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + + local e3 = evo.spawn({ f1 }, { 41 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) + end + + do + local e1 = evo.spawn({ f1, f2 }) + assert(evo.has_all(e1, f1, f2)) + assert(evo.get(e1, f1) == true and evo.get(e1, f2) == true) + + local e2 = evo.spawn({ f1, f2 }, {}) + assert(evo.has_all(e2, f1, f2)) + assert(evo.get(e2, f1) == true and evo.get(e2, f2) == true) + + local e3 = evo.spawn({ f1, f2 }, { 41 }) + assert(evo.has_all(e3, f1, f2)) + assert(evo.get(e3, f1) == 41 and evo.get(e3, f2) == true) + + local e4 = evo.spawn({ f1, f2 }, { nil, 42 }) + assert(evo.has_all(e4, f1, f2)) + assert(evo.get(e4, f1) == true and evo.get(e4, f2) == 42) + + local e5 = evo.spawn({ f1, f2 }, { 41, 42 }) + assert(evo.has_all(e5, f1, f2)) + assert(evo.get(e5, f1) == 41 and evo.get(e5, f2) == 42) + + local e6 = evo.spawn({ f1, f2 }, { 41, 42, 43 }) + assert(evo.has_all(e6, f1, f2)) + assert(evo.get(e6, f1) == 41 and evo.get(e6, f2) == 42) + end + + do + local e1 = evo.spawn({ f3 }) + assert(evo.has(e1, f3)) + assert(evo.get(e1, f3) == nil) + + local e2 = evo.spawn({ f2, f3 }) + assert(evo.has_all(e2, f2, f3)) + assert(evo.get(e2, f2) == true and evo.get(e2, f3) == nil) + + local e3 = evo.spawn({ f2, f3 }, { 42 }) + assert(evo.has_all(e3, f2, f3)) + assert(evo.get(e3, f2) == 42 and evo.get(e3, f3) == nil) + + local e4 = evo.spawn({ f2, f3 }, { 42, 43, 44 }) + assert(evo.has_all(e4, f2, f3)) + assert(evo.get(e4, f2) == 42 and evo.get(e4, f3) == nil) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f2, evo.DEFAULT, 21) + evo.set(f3, evo.TAG) + + do + local e = evo.spawn() + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e = evo.spawn({}) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local e1 = evo.spawn({ f1 }) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + + local e2 = evo.spawn({ f1 }, {}) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + + local e3 = evo.spawn({ f1 }, { 41 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) + end + + do + local e1 = evo.spawn({ f1, f2 }) + assert(evo.has_all(e1, f1, f2)) + assert(evo.get(e1, f1) == true and evo.get(e1, f2) == 21) + + local e2 = evo.spawn({ f1, f2 }, {}) + assert(evo.has_all(e2, f1, f2)) + assert(evo.get(e2, f1) == true and evo.get(e2, f2) == 21) + + local e3 = evo.spawn({ f1, f2 }, { 41 }) + assert(evo.has_all(e3, f1, f2)) + assert(evo.get(e3, f1) == 41 and evo.get(e3, f2) == 21) + + local e4 = evo.spawn({ f1, f2 }, { nil, 42 }) + assert(evo.has_all(e4, f1, f2)) + assert(evo.get(e4, f1) == true and evo.get(e4, f2) == 42) + + local e5 = evo.spawn({ f1, f2 }, { 41, 42 }) + assert(evo.has_all(e5, f1, f2)) + assert(evo.get(e5, f1) == 41 and evo.get(e5, f2) == 42) + + local e6 = evo.spawn({ f1, f2 }, { 41, 42, 43 }) + assert(evo.has_all(e6, f1, f2)) + assert(evo.get(e6, f1) == 41 and evo.get(e6, f2) == 42) + end + + do + local e1 = evo.spawn({ f3 }) + assert(evo.has(e1, f3)) + assert(evo.get(e1, f3) == nil) + + local e2 = evo.spawn({ f2, f3 }) + assert(evo.has_all(e2, f2, f3)) + assert(evo.get(e2, f2) == 21 and evo.get(e2, f3) == nil) + + local e3 = evo.spawn({ f2, f3 }, { 42 }) + assert(evo.has_all(e3, f2, f3)) + assert(evo.get(e3, f2) == 42 and evo.get(e3, f3) == nil) + + local e4 = evo.spawn({ f2, f3 }, { 42, 43, 44 }) + assert(evo.has_all(e4, f2, f3)) + assert(evo.get(e4, f2) == 42 and evo.get(e4, f3) == nil) + end +end + +do + local cf = evo.id() + local f1, f2 = evo.id(2) + + evo.set(f1, cf) + evo.set(f2, cf) + + evo.set(f2, evo.DEFAULT, 21) + + local set_count = 0 + local insert_count = 0 + + local last_set_entity = 0 + local last_set_component = 0 + local last_insert_entity = 0 + local last_insert_component = 0 + + local q = evo.query():include(cf):build() + + evo.batch_set(q, evo.ON_SET, function(e, f, c) + last_set_entity = e + assert(f == f1 or f == f2) + last_set_component = c + set_count = set_count + 1 + end) + + evo.batch_set(q, evo.ON_INSERT, function(e, f, c) + last_insert_entity = e + assert(f == f1 or f == f2) + last_insert_component = c + insert_count = insert_count + 1 + end) + + assert(set_count == 0 and insert_count == 0) + assert(last_set_entity == 0 and last_set_component == 0) + assert(last_insert_entity == 0 and last_insert_component == 0) + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local e = evo.spawn({ f1 }) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == true) + assert(last_insert_entity == e and last_insert_component == true) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local e = evo.spawn({ f2 }) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == 21) + assert(last_insert_entity == e and last_insert_component == 21) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local e = evo.spawn({ f1, f2 }) + assert(set_count == 2 and insert_count == 2) + assert(last_set_entity == e and last_set_component == 21) + assert(last_insert_entity == e and last_insert_component == 21) + end +end + +do + local f1, f2, f3, f4 = evo.id(4) + + evo.set(f3, evo.DEFAULT, 33) + evo.set(f4, evo.TAG) + + do + local e = evo.spawn_at() + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + local c = evo.chunk(f1) + + local e1 = evo.spawn_at(c) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + + local e2 = evo.spawn_at(c, { f1 }) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + + local e3 = evo.spawn_at(c, { f1, f2 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == true) + assert(not evo.has(e3, f2) and evo.get(e3, f2) == nil) + + local e4 = evo.spawn_at(c, { f1, f2 }, { 41 }) + assert(evo.has(e4, f1) and evo.get(e4, f1) == 41) + assert(not evo.has(e4, f2) and evo.get(e4, f2) == nil) + + local e5 = evo.spawn_at(c, { f1, f2 }, { 41, 42 }) + assert(evo.has(e5, f1) and evo.get(e5, f1) == 41) + assert(not evo.has(e5, f2) and evo.get(e5, f2) == nil) + + local e6 = evo.spawn_at(c, { f2 }, { 42 }) + assert(evo.has(e6, f1) and evo.get(e6, f1) == true) + assert(not evo.has(e6, f2) and evo.get(e6, f2) == nil) + end + + do + local c = evo.chunk(f1, f2) + + local e1 = evo.spawn_at(c) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + assert(evo.has(e1, f2) and evo.get(e1, f2) == true) + + local e2 = evo.spawn_at(c, { f1 }) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + + local e3 = evo.spawn_at(c, { f1, f2 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == true) + assert(evo.has(e3, f2) and evo.get(e3, f2) == true) + + local e4 = evo.spawn_at(c, { f1, f2, f3 }) + assert(evo.has(e4, f1) and evo.get(e4, f1) == true) + assert(evo.has(e4, f2) and evo.get(e4, f2) == true) + assert(not evo.has(e4, f3) and evo.get(e4, f3) == nil) + + local e5 = evo.spawn_at(c, { f1, f2 }, { 41 }) + assert(evo.has(e5, f1) and evo.get(e5, f1) == 41) + assert(evo.has(e5, f2) and evo.get(e5, f2) == true) + + local e6 = evo.spawn_at(c, { f1, f2 }, { 41, 42 }) + assert(evo.has(e6, f1) and evo.get(e6, f1) == 41) + assert(evo.has(e6, f2) and evo.get(e6, f2) == 42) + + local e7 = evo.spawn_at(c, { f1, f2, f3 }, { 41, 42, 43 }) + assert(evo.has(e7, f1) and evo.get(e7, f1) == 41) + assert(evo.has(e7, f2) and evo.get(e7, f2) == 42) + assert(not evo.has(e7, f3) and evo.get(e7, f3) == nil) + + local e8 = evo.spawn_at(c, { f3 }, { 43 }) + assert(evo.has(e8, f1) and evo.get(e8, f1) == true) + assert(evo.has(e8, f2) and evo.get(e8, f2) == true) + assert(not evo.has(e8, f3) and evo.get(e8, f3) == nil) + + local e9 = evo.spawn_at(c, { f2 }, { 42 }) + assert(evo.has(e9, f1) and evo.get(e9, f1) == true) + assert(evo.has(e9, f2) and evo.get(e9, f2) == 42) + assert(not evo.has(e9, f3) and evo.get(e9, f3) == nil) + end + + do + local c = evo.chunk(f2, f3, f4) + + local e1 = evo.spawn_at(c) + assert(evo.has(e1, f2) and evo.get(e1, f2) == true) + assert(evo.has(e1, f3) and evo.get(e1, f3) == 33) + assert(evo.has(e1, f4) and evo.get(e1, f4) == nil) + + local e2 = evo.spawn_at(c, { f1 }) + assert(not evo.has(e2, f1) and evo.get(e2, f1) == nil) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + assert(evo.has(e2, f3) and evo.get(e2, f3) == 33) + assert(evo.has(e2, f4) and evo.get(e2, f4) == nil) + + local e3 = evo.spawn_at(c, { f1 }, { 41 }) + assert(not evo.has(e3, f1) and evo.get(e3, f1) == nil) + assert(evo.has(e3, f2) and evo.get(e3, f2) == true) + assert(evo.has(e3, f3) and evo.get(e3, f3) == 33) + assert(evo.has(e3, f4) and evo.get(e3, f4) == nil) + + local e4 = evo.spawn_at(c, { f1, f3, f4 }, { 41, 43, 44 }) + assert(not evo.has(e4, f1) and evo.get(e4, f1) == nil) + assert(evo.has(e4, f2) and evo.get(e4, f2) == true) + assert(evo.has(e4, f3) and evo.get(e4, f3) == 43) + assert(evo.has(e4, f4) and evo.get(e4, f4) == nil) + end +end + +do + local cf = evo.id() + local f1, f2 = evo.id(2) + + evo.set(f1, cf) + evo.set(f2, cf) + + evo.set(f2, evo.DEFAULT, 22) + + local set_count = 0 + local insert_count = 0 + + local last_set_entity = 0 + local last_set_component = 0 + local last_insert_entity = 0 + local last_insert_component = 0 + + local q = evo.query():include(cf):build() + + evo.batch_set(q, evo.ON_SET, function(e, f, c) + last_set_entity = e + assert(f == f1 or f == f2) + last_set_component = c + set_count = set_count + 1 + end) + + evo.batch_set(q, evo.ON_INSERT, function(e, f, c) + last_insert_entity = e + assert(f == f1 or f == f2) + last_insert_component = c + insert_count = insert_count + 1 + end) + + assert(set_count == 0 and insert_count == 0) + assert(last_set_entity == 0 and last_set_component == 0) + assert(last_insert_entity == 0 and last_insert_component == 0) + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local c = evo.chunk(f1) + local e = evo.spawn_at(c) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == true) + assert(last_insert_entity == e and last_insert_component == true) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local c = evo.chunk(f2) + local e = evo.spawn_at(c) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == 22) + assert(last_insert_entity == e and last_insert_component == 22) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local c = evo.chunk(f2, f1) + local e = evo.spawn_at(c) + assert(set_count == 2 and insert_count == 2) + assert(last_set_entity == e and last_set_component == 22) + assert(last_insert_entity == e and last_insert_component == 22) + end +end diff --git a/evolved.lua b/evolved.lua index faf4618..c1fa210 100644 --- a/evolved.lua +++ b/evolved.lua @@ -751,6 +751,58 @@ end --- --- +---@param ... evolved.fragment fragments +---@return evolved.chunk? +---@nodiscard +local function __chunk_fragments(...) + local fragment_count = select('#', ...) + + if fragment_count == 0 then + return + end + + local root_fragment = select(1, ...) + local chunk = __root_chunks[root_fragment] + or __root_chunk(root_fragment) + + for i = 2, fragment_count do + local child_fragment = select(i, ...) + chunk = chunk.__with_fragment_edges[child_fragment] + or __chunk_with_fragment(chunk, child_fragment) + end + + return chunk +end + +---@param fragment_list evolved.fragment[] +---@return evolved.chunk? +---@nodiscard +local function __chunk_fragment_list(fragment_list) + local fragment_count = #fragment_list + + if fragment_count == 0 then + return + end + + local root_fragment = fragment_list[1] + local chunk = __root_chunks[root_fragment] + or __root_chunk(root_fragment) + + for i = 2, fragment_count do + local child_fragment = fragment_list[i] + chunk = chunk.__with_fragment_edges[child_fragment] + or __chunk_with_fragment(chunk, child_fragment) + end + + return chunk +end + +--- +--- +--- +--- +--- + ---@param chunk evolved.chunk ---@param fragment evolved.fragment ---@return boolean @@ -3310,24 +3362,14 @@ end) ---@param ... evolved.fragment fragments ---@return evolved.chunk? chunk ----@return evolved.entity[]? chunk_entities +---@return evolved.entity[]? entities function evolved.chunk(...) - local fragment_count = select('#', ...) + local chunk = __chunk_fragments(...) - if fragment_count == 0 then + if not chunk then return end - local root_fragment = select(1, ...) - local chunk = __root_chunks[root_fragment] - or __root_chunk(root_fragment) - - for i = 2, fragment_count do - local child_fragment = select(i, ...) - chunk = chunk.__with_fragment_edges[child_fragment] - or __chunk_with_fragment(chunk, child_fragment) - end - return chunk, chunk.__entities end @@ -3468,9 +3510,222 @@ end --- --- +---@param fragments? evolved.fragment[] +---@param components? evolved.component[] +---@return evolved.entity entity +---@return boolean is_deferred +function evolved.spawn(fragments, components) + if not fragments then + fragments = __EMPTY_FRAGMENT_LIST + end + + if not components then + components = __EMPTY_COMPONENT_LIST + end + + local entity = evolved.id() + + local chunk = __chunk_fragment_list(fragments) + + if not chunk then + return entity, false + end + + if __defer_depth > 0 then + error("spawn cannot be deferred yet") + end + + __defer() + + do + local chunk_entities = chunk.__entities + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + + local place = #chunk_entities + 1 + chunk_entities[place] = entity + + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then + new_component = evolved.get(fragment, evolved.DEFAULT) + end + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + + if chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = components[i] + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + + if chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + end + else + if chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk + __entity_places[entity_index] = place + + __structural_changes = __structural_changes + 1 + end + + __defer_commit() + return entity, false +end + +---@param chunk? evolved.chunk +---@param fragments? evolved.fragment[] +---@param components? evolved.component[] +---@return evolved.entity entity +---@return boolean is_deferred +function evolved.spawn_at(chunk, fragments, components) + if not fragments then + fragments = __EMPTY_FRAGMENT_LIST + end + + if not components then + components = __EMPTY_COMPONENT_LIST + end + + local entity = evolved.id() + + if not chunk then + return entity, false + end + + if __defer_depth > 0 then + error("spawn cannot be deferred yet") + end + + __defer() + + do + local chunk_entities = chunk.__entities + local chunk_fragment_list = chunk.__fragment_list + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + + local place = #chunk_entities + 1 + chunk_entities[place] = entity + + for i = 1, #chunk_fragment_list do + local fragment = chunk_fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = evolved.get(fragment, evolved.DEFAULT) + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + else + local new_component = true + + component_storage[place] = new_component + end + end + end + + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then + new_component = evolved.get(fragment, evolved.DEFAULT) + end + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + else + local new_component = components[i] + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + end + end + end + + if chunk.__has_set_or_insert_hooks then + for i = 1, #chunk_fragment_list do + local fragment = chunk_fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + local new_component = component_storage[place] + + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + else + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk + __entity_places[entity_index] = place + + __structural_changes = __structural_changes + 1 + end + + __defer_commit() + return entity, false +end + +--- +--- +--- +--- +--- + ---@class (exact) evolved.__entity_builder ---@field package __fragment_list? evolved.fragment[] ---@field package __component_list? evolved.component[] +---@field package __component_count integer ---@class evolved.entity_builder : evolved.__entity_builder local evolved_entity_builder = {} @@ -3483,6 +3738,7 @@ function evolved.entity() local builder = { __fragment_list = nil, __component_list = nil, + __component_count = 0, } ---@cast builder evolved.entity_builder return setmetatable(builder, evolved_entity_builder) @@ -3496,16 +3752,20 @@ function evolved_entity_builder:set(fragment, ...) local fragment_list = self.__fragment_list local component_list = self.__component_list + local component_count = self.__component_count - if not fragment_list then + if component_count == 0 then fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, 8, 0) component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, 8, 0) self.__fragment_list = fragment_list self.__component_list = component_list end - fragment_list[#fragment_list + 1] = fragment - component_list[#component_list + 1] = component + component_count = component_count + 1 + self.__component_count = component_count + + fragment_list[component_count] = fragment + component_list[component_count] = component return self end @@ -3514,17 +3774,17 @@ end function evolved_entity_builder:build() local fragment_list = self.__fragment_list local component_list = self.__component_list + local component_count = self.__component_count self.__fragment_list = nil self.__component_list = nil + self.__component_count = 0 - local entity = evolved.id() - - if not fragment_list then - return entity + if component_count == 0 then + return evolved.id() end - evolved.multi_set(entity, fragment_list, component_list) + local entity = evolved.spawn(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) @@ -3590,27 +3850,33 @@ function evolved_fragment_builder:build() self.__default = nil self.__construct = nil - local fragment = evolved.id() - local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, 3, 0) local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, 3, 0) + local component_count = 0 if tag then - fragment_list[#fragment_list + 1] = evolved.TAG - component_list[#component_list + 1] = true + component_count = component_count + 1 + fragment_list[component_count] = evolved.TAG + component_list[component_count] = true end if default ~= nil then - fragment_list[#fragment_list + 1] = evolved.DEFAULT - component_list[#component_list + 1] = default + component_count = component_count + 1 + fragment_list[component_count] = evolved.DEFAULT + component_list[component_count] = default end if construct ~= nil then - fragment_list[#fragment_list + 1] = evolved.CONSTRUCT - component_list[#component_list + 1] = construct + component_count = component_count + 1 + fragment_list[component_count] = evolved.CONSTRUCT + component_list[component_count] = construct end - evolved.multi_set(fragment, fragment_list, component_list) + if component_count == 0 then + return evolved.id() + end + + local fragment = evolved.spawn(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) @@ -3704,22 +3970,27 @@ function evolved_query_builder:build() self.__include_list = nil self.__exclude_list = nil - local query = evolved.id() - local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, 2, 0) local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, 2, 0) + local component_count = 0 if include_list then - fragment_list[#fragment_list + 1] = evolved.INCLUDES - component_list[#component_list + 1] = include_list + component_count = component_count + 1 + fragment_list[component_count] = evolved.INCLUDES + component_list[component_count] = include_list end if exclude_list then - fragment_list[#fragment_list + 1] = evolved.EXCLUDES - component_list[#component_list + 1] = exclude_list + component_count = component_count + 1 + fragment_list[component_count] = evolved.EXCLUDES + component_list[component_count] = exclude_list end - evolved.multi_set(query, fragment_list, component_list) + if component_count == 0 then + return evolved.id() + end + + local query = evolved.spawn(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) From 253d9e2246f2e38559fcf9af2e1892206160c92d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 18 Jan 2025 01:31:20 +0700 Subject: [PATCH 21/23] deferred spawning support --- README.md | 2 +- develop/unbench.lua | 344 +++++++++++------------------------ develop/untests.lua | 251 +++++++++++++++++++++---- evolved.lua | 433 +++++++++++++++++++++++++++----------------- 4 files changed, 584 insertions(+), 446 deletions(-) diff --git a/README.md b/README.md index 49b8b92..ba66222 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,8 @@ execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state? ``` ``` -spawn :: fragment[]?, component[]? -> entity, boolean spawn_at :: chunk?, fragment[]?, component[]? -> entity, boolean +spawn_with :: fragment[]?, component[]? -> entity, boolean ``` ``` diff --git a/develop/unbench.lua b/develop/unbench.lua index 3a8707b..89402ed 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -669,128 +669,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' -basics.describe_bench(string.format('create and destroy %d entities / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = {} - local components = {} - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = { F1 } - local components = { true } - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -basics.describe_bench(string.format('create and destroy %d entities with 2 components / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = { F1, F2 } - local components = { true, true } - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -basics.describe_bench(string.format('create and destroy %d entities with 3 components / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = { F1, F2, F3 } - local components = { true, true, true } - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -basics.describe_bench(string.format('create and destroy %d entities with 4 components / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = { F1, F2, F3, F4 } - local components = { true, true, true, true } - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -basics.describe_bench(string.format('create and destroy %d entities with 5 components / spawn', N), - ---@param entities evolved.id[] - function(entities) - local spawn = evo.spawn - local destroy = evo.destroy - - local fragments = { F1, F2, F3, F4, F5 } - local components = { true, true, true, true, true } - - for i = 1, N do - entities[i] = spawn(fragments, components) - end - - for i = 1, #entities do - destroy(entities[i]) - end - end, function() - return {} - end) - -print '----------------------------------------' - basics.describe_bench(string.format('create and destroy %d entities / spawn_at', N), ---@param entities evolved.id[] function(entities) @@ -925,132 +803,124 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' ---- ---- initial ---- +basics.describe_bench(string.format('create and destroy %d entities / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy ---[[ lua 5.1 -| create and destroy 1k entities ... | - PASS | us: 312.60 | op/s: 3199.00 | kb/i: 0.05 -| create and destroy 1k entities with 1 component ... | - PASS | us: 1570.31 | op/s: 636.82 | kb/i: 0.63 -| create and destroy 1k entities with 2 components ... | - PASS | us: 2780.82 | op/s: 359.61 | kb/i: 0.91 -| create and destroy 1k entities with 3 components ... | - PASS | us: 4060.00 | op/s: 246.31 | kb/i: 1.67 -]] + local fragments = {} + local components = {} ---[[ luajit 2.1 -| create and destroy 1k entities ... | - PASS | us: 12.22 | op/s: 81840.80 | kb/i: 0.00 -| create and destroy 1k entities with 1 component ... | - PASS | us: 56.22 | op/s: 17786.07 | kb/i: 0.02 -| create and destroy 1k entities with 2 components ... | - PASS | us: 412.73 | op/s: 2422.89 | kb/i: 0.11 -| create and destroy 1k entities with 3 components ... | - PASS | us: 611.62 | op/s: 1635.00 | kb/i: 0.17 -]] + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end ---- ---- unpack ids without dedicated functions ---- + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) ---[[ lua 5.1 -| create and destroy 1k entities ... | - PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 -| create and destroy 1k entities with 1 component ... | - PASS | us: 1248.45 | op/s: 801.00 | kb/i: 0.50 -| create and destroy 1k entities with 2 components ... | - PASS | us: 2208.79 | op/s: 452.74 | kb/i: 0.73 -| create and destroy 1k entities with 3 components ... | - PASS | us: 3278.69 | op/s: 305.00 | kb/i: 1.37 -]] +basics.describe_bench(string.format('create and destroy %d entities with 1 components / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy ---[[ luajit 2.1 -| create and destroy 1k entities ... | - PASS | us: 12.12 | op/s: 82482.59 | kb/i: 0.00 -| create and destroy 1k entities with 1 component ... | - PASS | us: 69.05 | op/s: 14482.59 | kb/i: 0.03 -| create and destroy 1k entities with 2 components ... | - PASS | us: 400.40 | op/s: 2497.51 | kb/i: 0.09 -| create and destroy 1k entities with 3 components ... | - PASS | us: 574.71 | op/s: 1740.00 | kb/i: 0.14 -]] + local fragments = { F1 } + local components = { true } ---- ---- hook flags for chunks ---- + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end ---[[ lua 5.1 -| create and destroy 1k entities ... | - PASS | us: 255.40 | op/s: 3915.42 | kb/i: 0.04 -| create and destroy 1k entities with 1 component ... | - PASS | us: 1005.03 | op/s: 995.00 | kb/i: 0.41 -| create and destroy 1k entities with 2 components ... | - PASS | us: 1747.83 | op/s: 572.14 | kb/i: 0.59 -| create and destroy 1k entities with 3 components ... | - PASS | us: 2576.92 | op/s: 388.06 | kb/i: 1.08 -]] + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) ---[[ luajit 2.1 -| create and destroy 1k entities ... | - PASS | us: 12.20 | op/s: 81940.30 | kb/i: 0.00 -| create and destroy 1k entities with 1 component ... | - PASS | us: 53.66 | op/s: 18636.82 | kb/i: 0.02 -| create and destroy 1k entities with 2 components ... | - PASS | us: 357.02 | op/s: 2801.00 | kb/i: 0.09 -| create and destroy 1k entities with 3 components ... | - PASS | us: 533.33 | op/s: 1875.00 | kb/i: 0.15 -]] +basics.describe_bench(string.format('create and destroy %d entities with 2 components / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy ---- ---- construct flags for chunks ---- + local fragments = { F1, F2 } + local components = { true, true } ---[[ lua 5.1 -| create and destroy 1k entities ... | - PASS | us: 253.49 | op/s: 3945.00 | kb/i: 0.04 -| create and destroy 1k entities with 1 component ... | - PASS | us: 913.64 | op/s: 1094.53 | kb/i: 0.37 -| create and destroy 1k entities with 2 components ... | - PASS | us: 1562.50 | op/s: 640.00 | kb/i: 0.53 -| create and destroy 1k entities with 3 components ... | - PASS | us: 2280.90 | op/s: 438.42 | kb/i: 0.97 -]] + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end ---[[ luajit 2.1 -| create and destroy 1k entities ... | - PASS | us: 12.05 | op/s: 82995.02 | kb/i: 0.00 -| create and destroy 1k entities with 1 component ... | - PASS | us: 53.61 | op/s: 18651.74 | kb/i: 0.02 -| create and destroy 1k entities with 2 components ... | - PASS | us: 232.02 | op/s: 4310.00 | kb/i: 0.06 -| create and destroy 1k entities with 3 components ... | - PASS | us: 329.49 | op/s: 3035.00 | kb/i: 0.10 -]] + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) ---- ---- after chunks refactoring ---- +basics.describe_bench(string.format('create and destroy %d entities with 3 components / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy ---[[ lua 5.1 -| create and destroy 1k entities ... | - PASS | us: 254.45 | op/s: 3930.00 | kb/i: 0.04 -| create and destroy 1k entities with 1 component ... | - PASS | us: 897.32 | op/s: 1114.43 | kb/i: 0.36 -| create and destroy 1k entities with 2 components ... | - PASS | us: 1481.48 | op/s: 675.00 | kb/i: 0.49 -| create and destroy 1k entities with 3 components ... | - PASS | us: 2126.32 | op/s: 470.30 | kb/i: 0.90 -]] + local fragments = { F1, F2, F3 } + local components = { true, true, true } ---[[ luajit 2.1 -| create and destroy 1k entities ... | - PASS | us: 12.31 | op/s: 81248.76 | kb/i: 0.00 -| create and destroy 1k entities with 1 component ... | - PASS | us: 46.97 | op/s: 21288.56 | kb/i: 0.02 -| create and destroy 1k entities with 2 components ... | - PASS | us: 75.19 | op/s: 13300.00 | kb/i: 0.03 -| create and destroy 1k entities with 3 components ... | - PASS | us: 108.28 | op/s: 9235.00 | kb/i: 0.06 -]] + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 components / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4 } + local components = { true, true, true, true } + + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 components / spawn_with', N), + ---@param entities evolved.id[] + function(entities) + local spawn_with = evo.spawn_with + local destroy = evo.destroy + + local fragments = { F1, F2, F3, F4, F5 } + local components = { true, true, true, true, true } + + for i = 1, N do + entities[i] = spawn_with(fragments, components) + end + + for i = 1, #entities do + destroy(entities[i]) + end + end, function() + return {} + end) + +print '----------------------------------------' diff --git a/develop/untests.lua b/develop/untests.lua index 93aacf4..aa8cb88 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -3153,66 +3153,66 @@ do evo.set(f3, evo.TAG) do - local e = evo.spawn() + local e = evo.spawn_with() assert(evo.is_alive(e) and evo.is_empty(e)) end do - local e = evo.spawn({}) + local e = evo.spawn_with({}) assert(evo.is_alive(e) and evo.is_empty(e)) end do - local e1 = evo.spawn({ f1 }) + local e1 = evo.spawn_with({ f1 }) assert(evo.has(e1, f1) and evo.get(e1, f1) == true) - local e2 = evo.spawn({ f1 }, {}) + local e2 = evo.spawn_with({ f1 }, {}) assert(evo.has(e2, f1) and evo.get(e2, f1) == true) - local e3 = evo.spawn({ f1 }, { 41 }) + local e3 = evo.spawn_with({ f1 }, { 41 }) assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) end do - local e1 = evo.spawn({ f1, f2 }) + local e1 = evo.spawn_with({ f1, f2 }) assert(evo.has_all(e1, f1, f2)) assert(evo.get(e1, f1) == true and evo.get(e1, f2) == true) - local e2 = evo.spawn({ f1, f2 }, {}) + local e2 = evo.spawn_with({ f1, f2 }, {}) assert(evo.has_all(e2, f1, f2)) assert(evo.get(e2, f1) == true and evo.get(e2, f2) == true) - local e3 = evo.spawn({ f1, f2 }, { 41 }) + local e3 = evo.spawn_with({ f1, f2 }, { 41 }) assert(evo.has_all(e3, f1, f2)) assert(evo.get(e3, f1) == 41 and evo.get(e3, f2) == true) - local e4 = evo.spawn({ f1, f2 }, { nil, 42 }) + local e4 = evo.spawn_with({ f1, f2 }, { nil, 42 }) assert(evo.has_all(e4, f1, f2)) assert(evo.get(e4, f1) == true and evo.get(e4, f2) == 42) - local e5 = evo.spawn({ f1, f2 }, { 41, 42 }) + local e5 = evo.spawn_with({ f1, f2 }, { 41, 42 }) assert(evo.has_all(e5, f1, f2)) assert(evo.get(e5, f1) == 41 and evo.get(e5, f2) == 42) - local e6 = evo.spawn({ f1, f2 }, { 41, 42, 43 }) + local e6 = evo.spawn_with({ f1, f2 }, { 41, 42, 43 }) assert(evo.has_all(e6, f1, f2)) assert(evo.get(e6, f1) == 41 and evo.get(e6, f2) == 42) end do - local e1 = evo.spawn({ f3 }) + local e1 = evo.spawn_with({ f3 }) assert(evo.has(e1, f3)) assert(evo.get(e1, f3) == nil) - local e2 = evo.spawn({ f2, f3 }) + local e2 = evo.spawn_with({ f2, f3 }) assert(evo.has_all(e2, f2, f3)) assert(evo.get(e2, f2) == true and evo.get(e2, f3) == nil) - local e3 = evo.spawn({ f2, f3 }, { 42 }) + local e3 = evo.spawn_with({ f2, f3 }, { 42 }) assert(evo.has_all(e3, f2, f3)) assert(evo.get(e3, f2) == 42 and evo.get(e3, f3) == nil) - local e4 = evo.spawn({ f2, f3 }, { 42, 43, 44 }) + local e4 = evo.spawn_with({ f2, f3 }, { 42, 43, 44 }) assert(evo.has_all(e4, f2, f3)) assert(evo.get(e4, f2) == 42 and evo.get(e4, f3) == nil) end @@ -3225,66 +3225,66 @@ do evo.set(f3, evo.TAG) do - local e = evo.spawn() + local e = evo.spawn_with() assert(evo.is_alive(e) and evo.is_empty(e)) end do - local e = evo.spawn({}) + local e = evo.spawn_with({}) assert(evo.is_alive(e) and evo.is_empty(e)) end do - local e1 = evo.spawn({ f1 }) + local e1 = evo.spawn_with({ f1 }) assert(evo.has(e1, f1) and evo.get(e1, f1) == true) - local e2 = evo.spawn({ f1 }, {}) + local e2 = evo.spawn_with({ f1 }, {}) assert(evo.has(e2, f1) and evo.get(e2, f1) == true) - local e3 = evo.spawn({ f1 }, { 41 }) + local e3 = evo.spawn_with({ f1 }, { 41 }) assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) end do - local e1 = evo.spawn({ f1, f2 }) + local e1 = evo.spawn_with({ f1, f2 }) assert(evo.has_all(e1, f1, f2)) assert(evo.get(e1, f1) == true and evo.get(e1, f2) == 21) - local e2 = evo.spawn({ f1, f2 }, {}) + local e2 = evo.spawn_with({ f1, f2 }, {}) assert(evo.has_all(e2, f1, f2)) assert(evo.get(e2, f1) == true and evo.get(e2, f2) == 21) - local e3 = evo.spawn({ f1, f2 }, { 41 }) + local e3 = evo.spawn_with({ f1, f2 }, { 41 }) assert(evo.has_all(e3, f1, f2)) assert(evo.get(e3, f1) == 41 and evo.get(e3, f2) == 21) - local e4 = evo.spawn({ f1, f2 }, { nil, 42 }) + local e4 = evo.spawn_with({ f1, f2 }, { nil, 42 }) assert(evo.has_all(e4, f1, f2)) assert(evo.get(e4, f1) == true and evo.get(e4, f2) == 42) - local e5 = evo.spawn({ f1, f2 }, { 41, 42 }) + local e5 = evo.spawn_with({ f1, f2 }, { 41, 42 }) assert(evo.has_all(e5, f1, f2)) assert(evo.get(e5, f1) == 41 and evo.get(e5, f2) == 42) - local e6 = evo.spawn({ f1, f2 }, { 41, 42, 43 }) + local e6 = evo.spawn_with({ f1, f2 }, { 41, 42, 43 }) assert(evo.has_all(e6, f1, f2)) assert(evo.get(e6, f1) == 41 and evo.get(e6, f2) == 42) end do - local e1 = evo.spawn({ f3 }) + local e1 = evo.spawn_with({ f3 }) assert(evo.has(e1, f3)) assert(evo.get(e1, f3) == nil) - local e2 = evo.spawn({ f2, f3 }) + local e2 = evo.spawn_with({ f2, f3 }) assert(evo.has_all(e2, f2, f3)) assert(evo.get(e2, f2) == 21 and evo.get(e2, f3) == nil) - local e3 = evo.spawn({ f2, f3 }, { 42 }) + local e3 = evo.spawn_with({ f2, f3 }, { 42 }) assert(evo.has_all(e3, f2, f3)) assert(evo.get(e3, f2) == 42 and evo.get(e3, f3) == nil) - local e4 = evo.spawn({ f2, f3 }, { 42, 43, 44 }) + local e4 = evo.spawn_with({ f2, f3 }, { 42, 43, 44 }) assert(evo.has_all(e4, f2, f3)) assert(evo.get(e4, f2) == 42 and evo.get(e4, f3) == nil) end @@ -3292,12 +3292,14 @@ end do local cf = evo.id() - local f1, f2 = evo.id(2) + local f1, f2, f3 = evo.id(3) evo.set(f1, cf) evo.set(f2, cf) + evo.set(f3, cf) evo.set(f2, evo.DEFAULT, 21) + evo.set(f3, evo.TAG) local set_count = 0 local insert_count = 0 @@ -3311,14 +3313,14 @@ do evo.batch_set(q, evo.ON_SET, function(e, f, c) last_set_entity = e - assert(f == f1 or f == f2) + assert(f == f1 or f == f2 or f == f3) last_set_component = c set_count = set_count + 1 end) evo.batch_set(q, evo.ON_INSERT, function(e, f, c) last_insert_entity = e - assert(f == f1 or f == f2) + assert(f == f1 or f == f2 or f == f3) last_insert_component = c insert_count = insert_count + 1 end) @@ -3331,7 +3333,7 @@ do set_count, insert_count = 0, 0 last_set_entity, last_set_component = 0, 0 last_insert_entity, last_insert_component = 0, 0 - local e = evo.spawn({ f1 }) + local e = evo.spawn_with({ f1 }) assert(set_count == 1 and insert_count == 1) assert(last_set_entity == e and last_set_component == true) assert(last_insert_entity == e and last_insert_component == true) @@ -3341,7 +3343,7 @@ do set_count, insert_count = 0, 0 last_set_entity, last_set_component = 0, 0 last_insert_entity, last_insert_component = 0, 0 - local e = evo.spawn({ f2 }) + local e = evo.spawn_with({ f2 }) assert(set_count == 1 and insert_count == 1) assert(last_set_entity == e and last_set_component == 21) assert(last_insert_entity == e and last_insert_component == 21) @@ -3351,11 +3353,31 @@ do set_count, insert_count = 0, 0 last_set_entity, last_set_component = 0, 0 last_insert_entity, last_insert_component = 0, 0 - local e = evo.spawn({ f1, f2 }) + local e = evo.spawn_with({ f1, f2 }) assert(set_count == 2 and insert_count == 2) assert(last_set_entity == e and last_set_component == 21) assert(last_insert_entity == e and last_insert_component == 21) end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local e = evo.spawn_with({ f3 }, { 33 }) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == nil) + assert(last_insert_entity == e and last_insert_component == nil) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local e = evo.spawn_with({ f3, f2 }, { 33, 22 }) + assert(set_count == 2 and insert_count == 2) + assert(last_set_entity == e and last_set_component == nil) + assert(last_insert_entity == e and last_insert_component == nil) + end end do @@ -3469,12 +3491,14 @@ end do local cf = evo.id() - local f1, f2 = evo.id(2) + local f1, f2, f3 = evo.id(3) evo.set(f1, cf) evo.set(f2, cf) + evo.set(f3, cf) evo.set(f2, evo.DEFAULT, 22) + evo.set(f3, evo.TAG) local set_count = 0 local insert_count = 0 @@ -3488,14 +3512,14 @@ do evo.batch_set(q, evo.ON_SET, function(e, f, c) last_set_entity = e - assert(f == f1 or f == f2) + assert(f == f1 or f == f2 or f == f3) last_set_component = c set_count = set_count + 1 end) evo.batch_set(q, evo.ON_INSERT, function(e, f, c) last_insert_entity = e - assert(f == f1 or f == f2) + assert(f == f1 or f == f2 or f == f3) last_insert_component = c insert_count = insert_count + 1 end) @@ -3536,4 +3560,153 @@ do assert(last_set_entity == e and last_set_component == 22) assert(last_insert_entity == e and last_insert_component == 22) end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local c = evo.chunk(f3) + local e = evo.spawn_at(c) + assert(set_count == 1 and insert_count == 1) + assert(last_set_entity == e and last_set_component == nil) + assert(last_insert_entity == e and last_insert_component == nil) + end + + do + set_count, insert_count = 0, 0 + last_set_entity, last_set_component = 0, 0 + last_insert_entity, last_insert_component = 0, 0 + local c = evo.chunk(f3, f2) + local e = evo.spawn_at(c, { f3, f2 }, { 33, 22 }) + assert(set_count == 2 and insert_count == 2) + assert(last_set_entity == e and last_set_component == nil) + assert(last_insert_entity == e and last_insert_component == nil) + end +end + +do + local f1, f2, f3, f4 = evo.id(4) + + evo.set(f3, evo.DEFAULT, 3) + evo.set(f4, evo.TAG) + + do + assert(evo.defer()) + local e, d = evo.spawn_with() + assert(evo.is_alive(e) and evo.is_empty(e)) + assert(not d) + assert(evo.commit()) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + assert(evo.defer()) + local e, d = evo.spawn_with({}) + assert(evo.is_alive(e) and evo.is_empty(e)) + assert(not d) + assert(evo.commit()) + assert(evo.is_alive(e) and evo.is_empty(e)) + end + + do + assert(evo.defer()) + local e1, d1 = evo.spawn_with({ f1 }) + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(d1) + assert(evo.commit()) + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + + assert(evo.defer()) + local e2, d2 = evo.spawn_with({ f1 }, {}) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(d2) + assert(evo.commit()) + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + + assert(evo.defer()) + local e3, d3 = evo.spawn_with({ f1 }, { 41 }) + assert(evo.is_alive(e3) and evo.is_empty(e3)) + assert(d3) + assert(evo.commit()) + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) + end + + do + assert(evo.defer()) + local e1, d1 = evo.spawn_with({ f1, f2 }) + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(d1) + assert(evo.commit()) + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f1) and evo.get(e1, f1) == true) + assert(evo.has(e1, f2) and evo.get(e1, f2) == true) + + assert(evo.defer()) + local e2, d2 = evo.spawn_with({ f1, f2 }, {}) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(d2) + assert(evo.commit()) + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == true) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + + assert(evo.defer()) + local e3, d3 = evo.spawn_with({ f1, f2 }, { 41 }) + assert(evo.is_alive(e3) and evo.is_empty(e3)) + assert(d3) + assert(evo.commit()) + assert(evo.is_alive(e3) and not evo.is_empty(e3)) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 41) + assert(evo.has(e3, f2) and evo.get(e3, f2) == true) + + assert(evo.defer()) + local e4, d4 = evo.spawn_with({ f1, f2 }, { nil, 42 }) + assert(evo.is_alive(e4) and evo.is_empty(e4)) + assert(d4) + assert(evo.commit()) + assert(evo.is_alive(e4) and not evo.is_empty(e4)) + assert(evo.has(e4, f1) and evo.get(e4, f1) == true) + assert(evo.has(e4, f2) and evo.get(e4, f2) == 42) + + assert(evo.defer()) + local e5, d5 = evo.spawn_with({ f1, f2 }, { 41, 42 }) + assert(evo.is_alive(e5) and evo.is_empty(e5)) + assert(d5) + assert(evo.commit()) + assert(evo.is_alive(e5) and not evo.is_empty(e5)) + assert(evo.has(e5, f1) and evo.get(e5, f1) == 41) + assert(evo.has(e5, f2) and evo.get(e5, f2) == 42) + + assert(evo.defer()) + local e6, d6 = evo.spawn_with({ f1, f2 }, { 41, 42, 43 }) + assert(evo.is_alive(e6) and evo.is_empty(e6)) + assert(d6) + assert(evo.commit()) + assert(evo.is_alive(e6) and not evo.is_empty(e6)) + assert(evo.has(e6, f1) and evo.get(e6, f1) == 41) + assert(evo.has(e6, f2) and evo.get(e6, f2) == 42) + end + + do + assert(evo.defer()) + local e1, d1 = evo.spawn_with({ f3, f4 }) + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(d1) + assert(evo.commit()) + assert(evo.is_alive(e1) and not evo.is_empty(e1)) + assert(evo.has(e1, f3) and evo.get(e1, f3) == 3) + assert(evo.has(e1, f4) and evo.get(e1, f4) == nil) + + assert(evo.defer()) + local e2, d2 = evo.spawn_with({ f3, f4 }, { 33, 44 }) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(d2) + assert(evo.commit()) + assert(evo.is_alive(e2) and not evo.is_empty(e2)) + assert(evo.has(e2, f3) and evo.get(e2, f3) == 33) + assert(evo.has(e2, f4) and evo.get(e2, f4) == nil) + end end diff --git a/evolved.lua b/evolved.lua index c1fa210..7e85356 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1387,6 +1387,165 @@ local function __detach_entity(entity) __structural_changes = __structural_changes + 1 end +---@param entity evolved.entity +---@param chunk evolved.chunk +---@param fragments evolved.fragment[] +---@param components evolved.component[] +local function __spawn_entity_at(entity, chunk, fragments, components) + local chunk_entities = chunk.__entities + local chunk_fragment_list = chunk.__fragment_list + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + + local place = #chunk_entities + 1 + chunk_entities[place] = entity + + for i = 1, #chunk_fragment_list do + local fragment = chunk_fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = evolved.get(fragment, evolved.DEFAULT) + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + else + local new_component = true + + component_storage[place] = new_component + end + end + end + + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then + new_component = evolved.get(fragment, evolved.DEFAULT) + end + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + else + local new_component = components[i] + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + end + end + end + + if chunk.__has_set_or_insert_hooks then + for i = 1, #chunk_fragment_list do + local fragment = chunk_fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + local new_component = component_storage[place] + + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + else + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk + __entity_places[entity_index] = place + + __structural_changes = __structural_changes + 1 +end + +---@param entity evolved.entity +---@param chunk evolved.chunk +---@param fragments evolved.fragment[] +---@param components evolved.component[] +local function __spawn_entity_with(entity, chunk, fragments, components) + local chunk_entities = chunk.__entities + local chunk_fragment_list = chunk.__fragment_list + local chunk_component_indices = chunk.__component_indices + local chunk_component_storages = chunk.__component_storages + + local place = #chunk_entities + 1 + chunk_entities[place] = entity + + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + if chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then + new_component = evolved.get(fragment, evolved.DEFAULT) + end + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + else + local new_component = components[i] + + if new_component == nil then + new_component = true + end + + component_storage[place] = new_component + end + end + end + + if chunk.__has_set_or_insert_hooks then + for i = 1, #chunk_fragment_list do + local fragment = chunk_fragment_list[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + + local new_component = component_storage[place] + + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + else + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + + local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk + __entity_places[entity_index] = place + + __structural_changes = __structural_changes + 1 +end + --- --- --- @@ -1413,6 +1572,9 @@ local __defer_op = { batch_remove = 14, batch_clear = 15, batch_destroy = 16, + + spawn_entity_at = 17, + spawn_entity_with = 18, } ---@type table @@ -1525,6 +1687,26 @@ local __defer_ops = { evolved.batch_destroy(query) return 1 end, + [__defer_op.spawn_entity_at] = function(bytes, index) + local entity = bytes[index + 0] + local chunk = bytes[index + 1] + local fragments = bytes[index + 2] + local components = bytes[index + 3] + __spawn_entity_at(entity, chunk, fragments, components) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, components) + return 4 + end, + [__defer_op.spawn_entity_with] = function(bytes, index) + local entity = bytes[index + 0] + local chunk = bytes[index + 1] + local fragments = bytes[index + 2] + local components = bytes[index + 3] + __spawn_entity_with(entity, chunk, fragments, components) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, components) + return 4 + end, } ---@return boolean started @@ -1861,6 +2043,56 @@ local function __defer_batch_destroy(query) __defer_length = length + 2 end +---@param entity evolved.entity +---@param chunk evolved.chunk +---@param fragments evolved.fragment[] +---@param components evolved.component[] +local function __defer_spawn_entity_at(entity, chunk, fragments, components) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local component_count = #components + local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_count, 0) + __table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.spawn_entity_at + bytecode[length + 2] = entity + bytecode[length + 3] = chunk + bytecode[length + 4] = fragment_list + bytecode[length + 5] = component_list + + __defer_length = length + 5 +end + +---@param entity evolved.entity +---@param chunk evolved.chunk +---@param fragments evolved.fragment[] +---@param components evolved.component[] +local function __defer_spawn_entity_with(entity, chunk, fragments, components) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local component_count = #components + local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_count, 0) + __table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.spawn_entity_with + bytecode[length + 2] = entity + bytecode[length + 3] = chunk + bytecode[length + 4] = fragment_list + bytecode[length + 5] = component_list + + __defer_length = length + 5 +end + --- --- --- @@ -3510,95 +3742,6 @@ end --- --- ----@param fragments? evolved.fragment[] ----@param components? evolved.component[] ----@return evolved.entity entity ----@return boolean is_deferred -function evolved.spawn(fragments, components) - if not fragments then - fragments = __EMPTY_FRAGMENT_LIST - end - - if not components then - components = __EMPTY_COMPONENT_LIST - end - - local entity = evolved.id() - - local chunk = __chunk_fragment_list(fragments) - - if not chunk then - return entity, false - end - - if __defer_depth > 0 then - error("spawn cannot be deferred yet") - end - - __defer() - - do - local chunk_entities = chunk.__entities - local chunk_component_indices = chunk.__component_indices - local chunk_component_storages = chunk.__component_storages - - local place = #chunk_entities + 1 - chunk_entities[place] = entity - - for i = 1, #fragments do - local fragment = fragments[i] - local component_index = chunk_component_indices[fragment] - - if component_index then - local component_storage = chunk_component_storages[component_index] - - if chunk.__has_defaults_or_constructs then - local new_component = components[i] - - if new_component == nil then - new_component = evolved.get(fragment, evolved.DEFAULT) - end - - if new_component == nil then - new_component = true - end - - component_storage[place] = new_component - - if chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) - end - else - local new_component = components[i] - - if new_component == nil then - new_component = true - end - - component_storage[place] = new_component - - if chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) - end - end - else - if chunk.__has_set_or_insert_hooks then - __fragment_call_set_and_insert_hooks(entity, fragment) - end - end - end - - local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = chunk - __entity_places[entity_index] = place - - __structural_changes = __structural_changes + 1 - end - - __defer_commit() - return entity, false -end - ---@param chunk? evolved.chunk ---@param fragments? evolved.fragment[] ---@param components? evolved.component[] @@ -3620,96 +3763,48 @@ function evolved.spawn_at(chunk, fragments, components) end if __defer_depth > 0 then - error("spawn cannot be deferred yet") + __defer_spawn_entity_at(entity, chunk, fragments, components) + return entity, true end __defer() do - local chunk_entities = chunk.__entities - local chunk_fragment_list = chunk.__fragment_list - local chunk_component_indices = chunk.__component_indices - local chunk_component_storages = chunk.__component_storages + __spawn_entity_at(entity, chunk, fragments, components) + end - local place = #chunk_entities + 1 - chunk_entities[place] = entity + __defer_commit() + return entity, false +end - for i = 1, #chunk_fragment_list do - local fragment = chunk_fragment_list[i] - local component_index = chunk_component_indices[fragment] +---@param fragments? evolved.fragment[] +---@param components? evolved.component[] +---@return evolved.entity entity +---@return boolean is_deferred +function evolved.spawn_with(fragments, components) + if not fragments then + fragments = __EMPTY_FRAGMENT_LIST + end - if component_index then - local component_storage = chunk_component_storages[component_index] + if not components then + components = __EMPTY_COMPONENT_LIST + end - if chunk.__has_defaults_or_constructs then - local new_component = evolved.get(fragment, evolved.DEFAULT) + local entity, chunk = evolved.id(), __chunk_fragment_list(fragments) - if new_component == nil then - new_component = true - end + if not chunk then + return entity, false + end - component_storage[place] = new_component - else - local new_component = true + if __defer_depth > 0 then + __defer_spawn_entity_with(entity, chunk, fragments, components) + return entity, true + end - component_storage[place] = new_component - end - end - end + __defer() - for i = 1, #fragments do - local fragment = fragments[i] - local component_index = chunk_component_indices[fragment] - - if component_index then - local component_storage = chunk_component_storages[component_index] - - if chunk.__has_defaults_or_constructs then - local new_component = components[i] - - if new_component == nil then - new_component = evolved.get(fragment, evolved.DEFAULT) - end - - if new_component == nil then - new_component = true - end - - component_storage[place] = new_component - else - local new_component = components[i] - - if new_component == nil then - new_component = true - end - - component_storage[place] = new_component - end - end - end - - if chunk.__has_set_or_insert_hooks then - for i = 1, #chunk_fragment_list do - local fragment = chunk_fragment_list[i] - local component_index = chunk_component_indices[fragment] - - if component_index then - local component_storage = chunk_component_storages[component_index] - - local new_component = component_storage[place] - - __fragment_call_set_and_insert_hooks(entity, fragment, new_component) - else - __fragment_call_set_and_insert_hooks(entity, fragment) - end - end - end - - local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = chunk - __entity_places[entity_index] = place - - __structural_changes = __structural_changes + 1 + do + __spawn_entity_with(entity, chunk, fragments, components) end __defer_commit() @@ -3784,7 +3879,7 @@ function evolved_entity_builder:build() return evolved.id() end - local entity = evolved.spawn(fragment_list, component_list) + local entity = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) @@ -3876,7 +3971,7 @@ function evolved_fragment_builder:build() return evolved.id() end - local fragment = evolved.spawn(fragment_list, component_list) + local fragment = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) @@ -3990,7 +4085,7 @@ function evolved_query_builder:build() return evolved.id() end - local query = evolved.spawn(fragment_list, component_list) + local query = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) From 511d97ae0487ff309ab10672d6d6fe46cb5d5c3f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 18 Jan 2025 01:34:14 +0700 Subject: [PATCH 22/23] build functions return deferred status now --- README.md | 6 +++--- evolved.lua | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ba66222..3317dca 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ spawn_with :: fragment[]?, component[]? -> entity, boolean ``` entity :: entity_builder entity_builder:set :: fragment, any... -> entity_builder -entity_builder:build :: entity +entity_builder:build :: entity, boolean ``` ``` @@ -96,14 +96,14 @@ fragment :: fragment_builder fragment_builder:tag :: fragment_builder fragment_builder:default :: component -> fragment_builder fragment_builder:construct :: {any... -> component} -> fragment_builder -fragment_builder:build :: fragment +fragment_builder:build :: fragment, boolean ``` ``` query :: query_builder query_builder:include :: fragment... -> query_builder query_builder:exclude :: fragment... -> query_builder -query_builder:build :: query +query_builder:build :: query, boolean ``` ## [License (MIT)](./LICENSE.md) diff --git a/evolved.lua b/evolved.lua index 7e85356..5d8f840 100644 --- a/evolved.lua +++ b/evolved.lua @@ -3866,6 +3866,7 @@ function evolved_entity_builder:set(fragment, ...) end ---@return evolved.entity entity +---@return boolean is_deferred function evolved_entity_builder:build() local fragment_list = self.__fragment_list local component_list = self.__component_list @@ -3876,15 +3877,15 @@ function evolved_entity_builder:build() self.__component_count = 0 if component_count == 0 then - return evolved.id() + return evolved.id(), false end - local entity = evolved.spawn_with(fragment_list, component_list) + local entity, is_deferred = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) - return entity + return entity, is_deferred end --- @@ -3936,6 +3937,7 @@ function evolved_fragment_builder:construct(construct) end ---@return evolved.fragment fragment +---@return boolean is_deferred function evolved_fragment_builder:build() local tag = self.__tag local default = self.__default @@ -3968,15 +3970,15 @@ function evolved_fragment_builder:build() end if component_count == 0 then - return evolved.id() + return evolved.id(), false end - local fragment = evolved.spawn_with(fragment_list, component_list) + local fragment, is_deferred = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) - return fragment + return fragment, is_deferred end --- @@ -4058,6 +4060,7 @@ function evolved_query_builder:exclude(...) end ---@return evolved.query query +---@return boolean is_deferred function evolved_query_builder:build() local include_list = self.__include_list local exclude_list = self.__exclude_list @@ -4082,15 +4085,15 @@ function evolved_query_builder:build() end if component_count == 0 then - return evolved.id() + return evolved.id(), false end - local query = evolved.spawn_with(fragment_list, component_list) + local query, is_deferred = evolved.spawn_with(fragment_list, component_list) __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_list) - return query + return query, is_deferred end --- From 684e934117e7864507d1d719cb61bf042a2aa2ac Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 18 Jan 2025 01:35:08 +0700 Subject: [PATCH 23/23] update roadmap --- ROADMAP.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 1660b8d..60603d4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,4 +10,3 @@ - add REQUIRES fragment trait - try to keep entity_chunks/places tables as arrays - set/assign/insert/remove/destroy for lists? -- add attach/detach functions?