From d246b861045f965ffbc1c53dfd102bb2463e9f96 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 31 Dec 2024 02:08:38 +0700 Subject: [PATCH] add evolved.each function --- README.md | 2 + ROADMAP.md | 1 - develop/untests.lua | 86 +++++++++++++++++++++++++++++++++ evolved.lua | 113 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 191 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c7c5ab2..557f1ef 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ batch_destroy :: query -> integer, boolean chunk :: fragment... -> chunk?, entity[]? select :: chunk, fragment... -> component[]?... + +each :: entity -> {each_state? -> fragment?, component?}, each_state? execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state? ``` diff --git a/ROADMAP.md b/ROADMAP.md index cf9ae2b..dc6d60e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,5 +8,4 @@ - optimize batch operations for cases with moving entities to empty chunks - should we clear chunk's components by on_insert tag callback? - use table.new/clear for cached tables -- add `each :: entity -> {each_state? -> fragment?, component?}, each_state?` function - replace chunk_list/fragment_list pools to one generic table pool diff --git a/develop/untests.lua b/develop/untests.lua index 33fc922..d8eb678 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -2336,3 +2336,89 @@ do assert(entities and entities[1] == e1) end end + +do + local f1, f2 = evo.id(3) + + do + local e = evo.id() + + local iter, state = evo.each(e) + local fragment, component = iter(state) + assert(not fragment and not component) + end + + do + local e = evo.id() + assert(evo.insert(e, f1, 41)) + + local iter, state = evo.each(e) + local fragment, component = iter(state) + assert(fragment == f1 and component == 41) + + fragment, component = iter(state) + assert(not fragment and not component) + end + + do + local e = evo.id() + assert(evo.insert(e, f1, 41)) + assert(evo.insert(e, f2, 42)) + + do + local iter, state = evo.each(e) + local fragment, component = iter(state) + assert(fragment == f1 or fragment == f2) + assert((fragment == f1 and component == 41) or (fragment == f2 and component == 42)) + + fragment, component = iter(state) + assert(fragment == f1 or fragment == f2) + assert((fragment == f1 and component == 41) or (fragment == f2 and component == 42)) + + fragment, component = iter(state) + assert(not fragment and not component) + end + + do + local fragment_sum = 0 + local component_sum = 0 + for f, c in evo.each(e) do + fragment_sum = fragment_sum + f + component_sum = component_sum + c + end + assert(fragment_sum == f1 + f2) + assert(component_sum == 41 + 42) + end + end + + do + local s = evo.id() + evo.set(s, evo.TAG) + + local e = evo.id() + assert(evo.insert(e, f1)) + assert(evo.insert(e, s)) + + do + local iter, state = evo.each(e) + local fragment, component = iter(state) + assert(fragment == f1 or fragment == s) + if fragment == f1 then + assert(component == true) + elseif fragment == s then + assert(component == nil) + end + + fragment, component = iter(state) + assert(fragment == f1 or fragment == s) + if fragment == f1 then + assert(component == true) + elseif fragment == s then + assert(component == nil) + end + + fragment, component = iter(state) + assert(not fragment and not component) + end + end +end diff --git a/evolved.lua b/evolved.lua index cd85237..2b1f532 100644 --- a/evolved.lua +++ b/evolved.lua @@ -17,7 +17,18 @@ local evolved = {} ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table ----@alias evolved.execute_state [integer, evolved.chunk[], table?] +---@class (exact) evolved.each_state +---@field [1] integer structural_changes +---@field [2] evolved.chunk entity_chunk +---@field [3] integer entity_place +---@field [4] evolved.fragment? fragments_index + +---@class (exact) evolved.execute_state +---@field [1] integer structural_changes +---@field [2] evolved.chunk[] chunk_stack +---@field [3] table? exclude_set + +---@alias evolved.each_iterator fun(state: evolved.each_state?): evolved.fragment?, evolved.component? ---@alias evolved.execute_iterator fun(state: evolved.execute_state?): evolved.chunk?, evolved.entity[]? --- @@ -42,6 +53,7 @@ local __entity_places = {} ---@type table local __chunk_lists = {} ---@type evolved.chunk[][] local __fragment_lists = {} ---@type evolved.fragment[][] +local __each_states = {} ---@type evolved.each_state[] local __execute_states = {} ---@type evolved.execute_state[] local __structural_changes = 0 ---@type integer @@ -219,24 +231,79 @@ end --- --- +---@param chunk evolved.chunk +---@param place integer +---@return evolved.each_state +---@nodiscard +local function __acquire_each_state(chunk, place) + local each_state_count = #__each_states + + if each_state_count == 0 then + ---@type evolved.each_state + return { __structural_changes, chunk, place } + end + + local state = __each_states[each_state_count] + __each_states[each_state_count] = nil + + state[1], state[2], state[3] = + __structural_changes, chunk, place + + return state +end + +---@param state evolved.each_state +local function __release_each_state(state) + for i = #state, 1, -1 do state[i] = nil end + __each_states[#__each_states + 1] = state +end + +---@type evolved.each_iterator +local function __each_iterator(state) + if not state then return end + + local structural_changes, chunk, place, fragment = + state[1], state[2], state[3], state[4] + + if structural_changes ~= __structural_changes then + error('structural changes are prohibited during iteration', 2) + end + + fragment = next(chunk.__fragments, fragment) + + if fragment then + state[4] = fragment + local fragment_components = chunk.__components[fragment] + return fragment, fragment_components and fragment_components[place] + end + + __release_each_state(state) +end + +--- +--- +--- +--- +--- + ---@param exclude_set? table ---@return evolved.execute_state ----@return evolved.chunk[] ---@nodiscard local function __acquire_execute_state(exclude_set) local execute_state_count = #__execute_states if execute_state_count == 0 then - local chunk_stack = __acquire_chunk_list() - return { __structural_changes, chunk_stack, exclude_set }, chunk_stack + ---@type evolved.execute_state + return { __structural_changes, __acquire_chunk_list(), exclude_set } end local state = __execute_states[execute_state_count] __execute_states[execute_state_count] = nil - local chunk_stack = __acquire_chunk_list() - state[1], state[2], state[3] = __structural_changes, chunk_stack, exclude_set - return state, chunk_stack + state[1], state[2], state[3] = + __structural_changes, __acquire_chunk_list(), exclude_set + + return state end ---@param state evolved.execute_state @@ -2286,33 +2353,59 @@ function evolved.select(chunk, ...) end end +---@param entity evolved.entity +---@return evolved.each_iterator +---@return evolved.each_state? +---@nodiscard +function evolved.each(entity) + if not __is_id_alive(entity) then + return __each_iterator + end + + local index = __unpack_id(entity) + + local chunk = __entity_chunks[index] + local place = __entity_places[index] + + if not chunk then + return __each_iterator + end + + return __each_iterator, __acquire_each_state(chunk, place) +end + ---@param query evolved.query ---@return evolved.execute_iterator ---@return evolved.execute_state? ---@nodiscard function evolved.execute(query) + if not __is_id_alive(query) then + return __execute_iterator + end + ---@type evolved.fragment[]?, evolved.fragment[]? local include_list, exclude_list = evolved.get(query, __SORTED_INCLUDE_LIST, __SORTED_EXCLUDE_LIST) if not include_list or #include_list == 0 then - return __execute_iterator, nil + return __execute_iterator end local major_fragment = include_list[#include_list] local major_fragment_chunks = __major_chunks[major_fragment] if not major_fragment_chunks then - return __execute_iterator, nil + return __execute_iterator end ---@type table? local exclude_set = evolved.get(query, __EXCLUDE_SET) - local execute_state, chunk_stack = __acquire_execute_state(exclude_set) + local execute_state = __acquire_execute_state(exclude_set) for _, major_fragment_chunk in ipairs(major_fragment_chunks) do if __chunk_has_all_fragment_list(major_fragment_chunk, include_list) then if not exclude_list or not __chunk_has_any_fragment_list(major_fragment_chunk, exclude_list) then + local chunk_stack = execute_state[2] chunk_stack[#chunk_stack + 1] = major_fragment_chunk end end