add evolved.each function

This commit is contained in:
BlackMATov
2024-12-31 02:08:38 +07:00
parent 1f787dcd65
commit d246b86104
4 changed files with 191 additions and 11 deletions

View File

@@ -52,6 +52,8 @@ batch_destroy :: query -> integer, boolean
chunk :: fragment... -> chunk?, entity[]? chunk :: fragment... -> chunk?, entity[]?
select :: chunk, fragment... -> component[]?... select :: chunk, fragment... -> component[]?...
each :: entity -> {each_state? -> fragment?, component?}, each_state?
execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state? execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state?
``` ```

View File

@@ -8,5 +8,4 @@
- optimize batch operations for cases with moving entities to empty chunks - optimize batch operations for cases with moving entities to empty chunks
- should we clear chunk's components by on_insert tag callback? - should we clear chunk's components by on_insert tag callback?
- use table.new/clear for cached tables - 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 - replace chunk_list/fragment_list pools to one generic table pool

View File

@@ -2336,3 +2336,89 @@ do
assert(entities and entities[1] == e1) assert(entities and entities[1] == e1)
end end
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

View File

@@ -17,7 +17,18 @@ local evolved = {}
---@field package __with_fragment_edges table<evolved.fragment, evolved.chunk> ---@field package __with_fragment_edges table<evolved.fragment, evolved.chunk>
---@field package __without_fragment_edges table<evolved.fragment, evolved.chunk> ---@field package __without_fragment_edges table<evolved.fragment, evolved.chunk>
---@alias evolved.execute_state [integer, evolved.chunk[], table<evolved.fragment, boolean>?] ---@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<evolved.fragment, boolean>? 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[]? ---@alias evolved.execute_iterator fun(state: evolved.execute_state?): evolved.chunk?, evolved.entity[]?
--- ---
@@ -42,6 +53,7 @@ local __entity_places = {} ---@type table<integer, integer>
local __chunk_lists = {} ---@type evolved.chunk[][] local __chunk_lists = {} ---@type evolved.chunk[][]
local __fragment_lists = {} ---@type evolved.fragment[][] local __fragment_lists = {} ---@type evolved.fragment[][]
local __each_states = {} ---@type evolved.each_state[]
local __execute_states = {} ---@type evolved.execute_state[] local __execute_states = {} ---@type evolved.execute_state[]
local __structural_changes = 0 ---@type integer 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<evolved.fragment, boolean> ---@param exclude_set? table<evolved.fragment, boolean>
---@return evolved.execute_state ---@return evolved.execute_state
---@return evolved.chunk[]
---@nodiscard ---@nodiscard
local function __acquire_execute_state(exclude_set) local function __acquire_execute_state(exclude_set)
local execute_state_count = #__execute_states local execute_state_count = #__execute_states
if execute_state_count == 0 then if execute_state_count == 0 then
local chunk_stack = __acquire_chunk_list() ---@type evolved.execute_state
return { __structural_changes, chunk_stack, exclude_set }, chunk_stack return { __structural_changes, __acquire_chunk_list(), exclude_set }
end end
local state = __execute_states[execute_state_count] local state = __execute_states[execute_state_count]
__execute_states[execute_state_count] = nil __execute_states[execute_state_count] = nil
local chunk_stack = __acquire_chunk_list() state[1], state[2], state[3] =
state[1], state[2], state[3] = __structural_changes, chunk_stack, exclude_set __structural_changes, __acquire_chunk_list(), exclude_set
return state, chunk_stack
return state
end end
---@param state evolved.execute_state ---@param state evolved.execute_state
@@ -2286,33 +2353,59 @@ function evolved.select(chunk, ...)
end end
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 ---@param query evolved.query
---@return evolved.execute_iterator ---@return evolved.execute_iterator
---@return evolved.execute_state? ---@return evolved.execute_state?
---@nodiscard ---@nodiscard
function evolved.execute(query) function evolved.execute(query)
if not __is_id_alive(query) then
return __execute_iterator
end
---@type evolved.fragment[]?, evolved.fragment[]? ---@type evolved.fragment[]?, evolved.fragment[]?
local include_list, exclude_list = evolved.get(query, local include_list, exclude_list = evolved.get(query,
__SORTED_INCLUDE_LIST, __SORTED_EXCLUDE_LIST) __SORTED_INCLUDE_LIST, __SORTED_EXCLUDE_LIST)
if not include_list or #include_list == 0 then if not include_list or #include_list == 0 then
return __execute_iterator, nil return __execute_iterator
end end
local major_fragment = include_list[#include_list] local major_fragment = include_list[#include_list]
local major_fragment_chunks = __major_chunks[major_fragment] local major_fragment_chunks = __major_chunks[major_fragment]
if not major_fragment_chunks then if not major_fragment_chunks then
return __execute_iterator, nil return __execute_iterator
end end
---@type table<evolved.fragment, boolean>? ---@type table<evolved.fragment, boolean>?
local exclude_set = evolved.get(query, __EXCLUDE_SET) 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 for _, major_fragment_chunk in ipairs(major_fragment_chunks) do
if __chunk_has_all_fragment_list(major_fragment_chunk, include_list) then 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 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 chunk_stack[#chunk_stack + 1] = major_fragment_chunk
end end
end end