diff --git a/README.md b/README.md index 48b8591..31b712e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ clear :: entity -> boolean, boolean alive :: entity -> boolean destroy :: entity -> boolean, boolean + +execute :: query -> {execution_state? -> chunk?}, execution_state? ``` ## [License (MIT)](./LICENSE.md) diff --git a/develop/untests.lua b/develop/untests.lua index c942bdf..18b6a93 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -1,5 +1,18 @@ local evo = require 'evolved' +---@generic V +---@param list V[] +---@return boolean +---@nodiscard +local function __is_sorted(list) + for i = 1, #list - 1 do + if list[i] > list[i + 1] then + return false + end + end + return true +end + do local e1, e2 = evo.id(), evo.id() assert(e1 ~= e2) @@ -663,3 +676,74 @@ do assert(evo.get(e, f2) == nil) end end + +do + local f1, f2, f3, f4 = evo.id(4) + + do + local q = evo.id() + evo.set(q, evo.INCLUDE_LIST) + assert(evo.has_all(q, evo.INCLUDE_LIST, evo.EXCLUDE_LIST)) + + local include_list, exclude_list = evo.get(q, evo.INCLUDE_LIST, evo.EXCLUDE_LIST) + assert(type(include_list) == "table" and next(include_list) == nil) + assert(type(exclude_list) == "table" and next(exclude_list) == nil) + end + + do + local q = evo.id() + evo.set(q, evo.EXCLUDE_LIST) + assert(evo.has_all(q, evo.EXCLUDE_LIST, evo.INCLUDE_LIST)) + + local include_list, exclude_list = evo.get(q, evo.INCLUDE_LIST, evo.EXCLUDE_LIST) + assert(type(include_list) == "table" and next(include_list) == nil) + assert(type(exclude_list) == "table" and next(exclude_list) == nil) + end + + do + local q = evo.id() + + evo.set(q, evo.INCLUDE_LIST, { f2, f1 }) + assert(__is_sorted(evo.get(q, evo.INCLUDE_LIST))) + + evo.set(q, evo.EXCLUDE_LIST, { f4, f3 }) + assert(__is_sorted(evo.get(q, evo.EXCLUDE_LIST))) + end +end + +do + local f1, f2, f3, f4 = evo.id(4) + + local q = evo.id() + evo.set(q, evo.INCLUDE_LIST, { f2, f1 }) + evo.set(q, evo.EXCLUDE_LIST, { f3 }) + + do + local e1 = evo.id() + assert(evo.insert(e1, f1, 41)) + + local e2 = evo.id() + assert(evo.insert(e2, f1, 41)) + assert(evo.insert(e2, f2, 42)) + + local e3 = evo.id() + assert(evo.insert(e3, f1, 43)) + assert(evo.insert(e3, f2, 44)) + assert(evo.insert(e3, f3, 45)) + + local e4 = evo.id() + assert(evo.insert(e4, f1, 45)) + assert(evo.insert(e4, f2, 46)) + assert(evo.insert(e4, f3, 47)) + assert(evo.insert(e4, f4, 48)) + + local e5 = evo.id() + assert(evo.insert(e5, f1, 49)) + assert(evo.insert(e5, f2, 50)) + assert(evo.insert(e5, f4, 51)) + + for chunk in evo.execute(q) do + print(chunk) + end + end +end diff --git a/evolved.lua b/evolved.lua index e94000a..64838e1 100644 --- a/evolved.lua +++ b/evolved.lua @@ -2,6 +2,7 @@ local evolved = {} ---@alias evolved.id integer +---@alias evolved.query evolved.id ---@alias evolved.entity evolved.id ---@alias evolved.fragment evolved.id ---@alias evolved.component any @@ -16,6 +17,10 @@ local evolved = {} ---@field __with_fragment_edges table ---@field __without_fragment_edges table +---@alias evolved.execution_stack evolved.chunk[] +---@alias evolved.execution_state [integer, table, evolved.execution_stack] +---@alias evolved.execution_iterator fun(state: evolved.execution_state?): evolved.chunk? + --- --- --- @@ -35,6 +40,9 @@ local __major_chunks = {} ---@type table local __entity_chunks = {} ---@type table local __entity_places = {} ---@type table +local __execution_stacks = {} ---@type evolved.execution_stack[] +local __execution_states = {} ---@type evolved.execution_state[] + local __structural_changes = 0 ---@type integer --- @@ -142,6 +150,9 @@ evolved.ON_ASSIGN = __acquire_id() evolved.ON_INSERT = __acquire_id() evolved.ON_REMOVE = __acquire_id() +evolved.INCLUDE_LIST = __acquire_id() +evolved.EXCLUDE_LIST = __acquire_id() + --- --- --- @@ -1152,4 +1163,191 @@ end --- --- +local __INCLUDE_SET = __acquire_id() +local __EXCLUDE_SET = __acquire_id() + +---@param in_list? evolved.fragment[] +assert(evolved.insert(evolved.INCLUDE_LIST, evolved.CONSTRUCT, function(_, in_list) + if not in_list then + return {} + end + + local out_list = {} + + for i = 1, #in_list do + out_list[i] = in_list[i] + end + + table.sort(out_list) + return out_list +end)) + +---@param query evolved.query +---@param include_list evolved.entity[] +assert(evolved.insert(evolved.INCLUDE_LIST, evolved.ON_SET, function(query, _, include_list) + ---@type table + local include_set = {} + + for i = 1, #include_list do + include_set[include_list[i]] = true + end + + evolved.set(query, __INCLUDE_SET, include_set) + evolved.insert(query, evolved.EXCLUDE_LIST) +end)) + +---@param in_list? evolved.fragment[] +assert(evolved.insert(evolved.EXCLUDE_LIST, evolved.CONSTRUCT, function(_, in_list) + if not in_list then + return {} + end + + local out_list = {} + + for i = 1, #in_list do + out_list[i] = in_list[i] + end + + table.sort(out_list) + return out_list +end)) + +---@param query evolved.query +---@param exclude_list evolved.entity[] +assert(evolved.insert(evolved.EXCLUDE_LIST, evolved.ON_SET, function(query, _, exclude_list) + ---@type table + local exclude_set = {} + + for i = 1, #exclude_list do + exclude_set[exclude_list[i]] = true + end + + evolved.set(query, __EXCLUDE_SET, exclude_set) + evolved.insert(query, evolved.INCLUDE_LIST) +end)) + +---@return evolved.execution_stack +---@nodiscard +local function __acquire_execution_stack() + local execution_stacks = __execution_stacks + + if #execution_stacks == 0 then + return {} + end + + local stack = execution_stacks[#execution_stacks] + execution_stacks[#execution_stacks] = nil + + return stack +end + +---@param stack evolved.execution_stack +local function __release_execution_stack(stack) + for i = #stack, 1, -1 do stack[i] = nil end + __execution_stacks[#__execution_stacks + 1] = stack +end + +---@param exclude_set table +---@return evolved.execution_state +---@return evolved.execution_stack +---@nodiscard +local function __acquire_execution_state(exclude_set) + local execution_states = __execution_states + + if #execution_states == 0 then + local stack = __acquire_execution_stack() + return { __structural_changes, exclude_set, stack }, stack + end + + local state = execution_states[#execution_states] + execution_states[#execution_states] = nil + + local stack = __acquire_execution_stack() + state[1], state[2], state[3] = __structural_changes, exclude_set, stack + return state, stack +end + +---@param state evolved.execution_state +local function __release_execution_state(state) + __release_execution_stack(state[3]); state[3] = nil + __execution_states[#__execution_states + 1] = state +end + +---@type evolved.execution_iterator +local function __execution_iterator(execution_state) + if not execution_state then return end + + local structural_changes, exclude_set, execution_stack = + execution_state[1], execution_state[2], execution_state[3] + + if structural_changes ~= __structural_changes then + error('structural changes are prohibited during execution', 2) + end + + while #execution_stack > 0 do + local matched_chunk = execution_stack[#execution_stack] + execution_stack[#execution_stack] = nil + + for _, matched_chunk_child in ipairs(matched_chunk.__children) do + if not exclude_set[matched_chunk_child.__fragment] then + execution_stack[#execution_stack + 1] = matched_chunk_child + end + end + + if #matched_chunk.__entities > 0 then + return matched_chunk + end + end + + __release_execution_state(execution_state) +end + +--- +--- +--- +--- +--- + +---@param query evolved.query +---@return evolved.execution_iterator +---@return evolved.execution_state? +---@nodiscard +function evolved.execute(query) + local include_list = + evolved.get(query, evolved.INCLUDE_LIST) + + if not include_list or #include_list == 0 then + return __execution_iterator, nil + end + + local exclude_set, exclude_list = + evolved.get(query, __EXCLUDE_SET, evolved.EXCLUDE_LIST) + + local major_fragment = include_list[#include_list] + local major_fragment_chunks = __major_chunks[major_fragment] + + if not major_fragment_chunks then + return __execution_iterator, nil + end + + local execution_state, execution_stack = + __acquire_execution_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 __chunk_has_any_fragment_list(major_fragment_chunk, exclude_list) then + execution_stack[#execution_stack + 1] = major_fragment_chunk + end + end + end + + return __execution_iterator, execution_state +end + +--- +--- +--- +--- +--- + return evolved