From a63b0d4400d64739e379031d2bac0c24a7f3becd Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 1 Sep 2025 15:50:48 +0700 Subject: [PATCH] Revert "temp remove pairs to merge other changes to dev" This reverts commit fb6d13ca74735e7ef6ef5ab66337fb66b6054d5f. --- README.md | 143 +- develop/all.lua | 4 + develop/fuzzing/pack_unpack_fuzz.lua | 3 +- develop/fuzzing/wildcard_fuzz.lua | 275 ++++ develop/samples/relations.lua | 79 ++ develop/testing/name_tests.lua | 6 +- develop/testing/pairs_tests.lua | 1920 +++++++++++++++++++++++++ evolved.lua | 1964 +++++++++++++++++++++++--- 8 files changed, 4178 insertions(+), 216 deletions(-) create mode 100644 develop/fuzzing/wildcard_fuzz.lua create mode 100644 develop/samples/relations.lua create mode 100644 develop/testing/pairs_tests.lua diff --git a/README.md b/README.md index 07521f4..164000b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ - [Cheat Sheet](#cheat-sheet) - [Aliases](#aliases) - [Predefs](#predefs) - - [Functions](#functions) + - [Core Functions](#core-functions) + - [Relation Functions](#relation-functions) - [Classes](#classes) - [Chunk](#chunk) - [Builder](#builder) @@ -156,6 +157,7 @@ function evolved.pack(index, version) end ---@param id evolved.id ---@return integer primary ---@return integer secondary +---@return integer options ---@nodiscard function evolved.unpack(id) end ``` @@ -1024,6 +1026,7 @@ assert(not evolved.alive(entity)) ``` id :: implementation-specific +pair :: id entity :: id fragment :: id @@ -1047,14 +1050,20 @@ remove_hook :: {entity, fragment, component} each_state :: implementation-specific execute_state :: implementation-specific +primaries_state :: implementation-specific +secondaries_state :: implementation-specific each_iterator :: {each_state? -> fragment?, component?} execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?} +primaries_iterator :: {primaries_state? -> fragment?, component?} +secondaries_iterator :: {secondaries_state? -> fragment?, component?} ``` ### Predefs ``` +ANY :: fragment + TAG :: fragment NAME :: fragment @@ -1090,7 +1099,7 @@ DESTRUCTION_POLICY_DESTROY_ENTITY :: id DESTRUCTION_POLICY_REMOVE_FRAGMENT :: id ``` -### Functions +### Core Functions ``` id :: integer? -> id... @@ -1138,6 +1147,25 @@ debug_mode :: boolean -> () collect_garbage :: () ``` +### Relation Functions + +``` +pair :: id, id -> pair +unpair :: pair -> id, id + +is_pair :: id -> boolean +is_wildcard :: id -> boolean + +primary :: entity, fragment, integer? -> fragment?, component? +secondary :: entity, fragment, integer? -> fragment?, component? + +primaries :: entity, fragment -> {primaries_state? -> fragment?, component?}, primaries_state? +secondaries :: entity, fragment -> {secondaries_state? -> fragment?, component?}, secondaries_state? + +primary_count :: entity, fragment -> integer +secondary_count :: entity, fragment -> integer +``` + ### Classes #### Chunk @@ -1232,6 +1260,8 @@ builder_mt:destruction_policy :: id -> builder ## Predefs +### `evolved.ANY` + ### `evolved.TAG` ### `evolved.NAME` @@ -1280,7 +1310,7 @@ builder_mt:destruction_policy :: id -> builder ### `evolved.DESTRUCTION_POLICY_REMOVE_FRAGMENT` -## Functions +## Core Functions ### `evolved.id` @@ -1316,6 +1346,7 @@ function evolved.pack(index, version) end ---@param id evolved.id ---@return integer primary ---@return integer secondary +---@return integer options ---@nodiscard function evolved.unpack(id) end ``` @@ -1547,6 +1578,112 @@ function evolved.debug_mode(yesno) end function evolved.collect_garbage() end ``` +## Relation Functions + +### `evolved.pair` + +```lua +---@param primary evolved.id +---@param secondary evolved.id +---@return evolved.pair pair +---@nodiscard +function evolved.pair(primary, secondary) end +``` + +### `evolved.unpair` + +```lua +---@param pair evolved.pair +---@return evolved.id primary +---@return evolved.id secondary +---@nodiscard +function evolved.unpair(pair) end +``` + +### `evolved.is_pair` + +```lua +---@param id evolved.id +---@return boolean +---@nodiscard +function evolved.is_pair(id) end +``` + +### `evolved.is_wildcard` + +```lua +---@param id evolved.id +---@return boolean +---@nodiscard +function evolved.is_wildcard(id) end +``` + +### `evolved.primary` + +```lua +---@param entity evolved.entity +---@param secondary evolved.fragment +---@param index? integer +---@return evolved.fragment? primary +---@return evolved.component? component +---@nodiscard +function evolved.primary(entity, secondary, index) end +``` + +### `evolved.secondary` + +```lua +---@param entity evolved.entity +---@param primary evolved.fragment +---@param index? integer +---@return evolved.fragment? secondary +---@return evolved.component? component +---@nodiscard +function evolved.secondary(entity, primary, index) end +``` + +### `evolved.primaries` + +```lua +---@param entity evolved.entity +---@param secondary evolved.fragment +---@return evolved.primaries_iterator iterator +---@return evolved.primaries_state? iterator_state +---@nodiscard +function evolved.primaries(entity, secondary) end +``` + +### `evolved.secondaries` + +```lua +---@param entity evolved.entity +---@param primary evolved.fragment +---@return evolved.secondaries_iterator iterator +---@return evolved.secondaries_state? iterator_state +---@nodiscard +function evolved.secondaries(entity, primary) end +``` + +### `evolved.primary_count` + +```lua +---@param entity evolved.entity +---@param secondary evolved.fragment +---@return integer +---@nodiscard +function evolved.primary_count(entity, secondary) end +``` + +### `evolved.secondary_count` + +```lua +---@param entity evolved.entity +---@param primary evolved.fragment +---@return integer +---@nodiscard +function evolved.secondary_count(entity, primary) end +``` + ## Classes ### Chunk diff --git a/develop/all.lua b/develop/all.lua index bd34bf9..b5d6025 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,6 +1,8 @@ +require 'develop.samples.relations' require 'develop.samples.systems' require 'develop.testing.name_tests' +require 'develop.testing.pairs_tests' require 'develop.testing.requires_fragment_tests' require 'develop.testing.system_as_query_tests' @@ -23,3 +25,5 @@ print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.requires_fuzz' print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.unique_fuzz' +print '----------------------------------------' +basics.describe_fuzz 'develop.fuzzing.wildcard_fuzz' diff --git a/develop/fuzzing/pack_unpack_fuzz.lua b/develop/fuzzing/pack_unpack_fuzz.lua index 2693913..b1dce49 100644 --- a/develop/fuzzing/pack_unpack_fuzz.lua +++ b/develop/fuzzing/pack_unpack_fuzz.lua @@ -13,8 +13,9 @@ for _ = 1, 1000 do local initial_secondary = math.random(1, 2 ^ 20 - 1) local packed_id = evo.pack(initial_primary, initial_secondary) - local unpacked_primary, unpacked_secondary = evo.unpack(packed_id) + local unpacked_primary, unpacked_secondary, unpacked_options = evo.unpack(packed_id) assert(initial_primary == unpacked_primary) assert(initial_secondary == unpacked_secondary) + assert(0 == unpacked_options) end diff --git a/develop/fuzzing/wildcard_fuzz.lua b/develop/fuzzing/wildcard_fuzz.lua new file mode 100644 index 0000000..9a64fc6 --- /dev/null +++ b/develop/fuzzing/wildcard_fuzz.lua @@ -0,0 +1,275 @@ +local evo = require 'evolved' + +evo.debug_mode(true) + +--- +--- +--- +--- +--- + +local __table_unpack = (function() + ---@diagnostic disable-next-line: deprecated + return table.unpack or unpack +end)() + +--- +--- +--- +--- +--- + +local all_entity_list = {} ---@type evolved.entity[] +local all_fragment_list = {} ---@type evolved.fragment[] + +for i = 1, math.random(1, 5) do + local fragment_builder = evo.builder() + + if math.random(1, 3) == 1 then + fragment_builder:explicit() + end + + if math.random(1, 3) == 1 then + if math.random(1, 2) == 1 then + fragment_builder:destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + else + fragment_builder:destruction_policy(evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) + end + end + + all_fragment_list[i] = fragment_builder:spawn() +end + +for i = 1, math.random(50, 100) do + local entity_builder = evo.builder() + + for _ = 0, math.random(0, #all_fragment_list) do + if math.random(1, 2) == 1 then + local fragment = all_fragment_list[math.random(1, #all_fragment_list)] + entity_builder:set(fragment) + else + local primary = all_fragment_list[math.random(1, #all_fragment_list)] + local secondary = all_fragment_list[math.random(1, #all_fragment_list)] + entity_builder:set(evo.pair(primary, secondary)) + end + end + + all_entity_list[i] = entity_builder:spawn() +end + +--- +--- +--- +--- +--- + +for _ = 1, math.random(1, 100) do + local query_builder = evo.builder() + + local query_include_set = {} ---@type table + local query_include_list = {} ---@type evolved.entity[] + local query_include_count = 0 ---@type integer + + local query_exclude_set = {} ---@type table + local query_exclude_list = {} ---@type evolved.entity[] + local query_exclude_count = 0 ---@type integer + + for _ = 1, math.random(0, 2) do + if math.random(1, 2) == 1 then + local fragment = all_fragment_list[math.random(1, #all_fragment_list)] + + query_builder:include(fragment) + + if not query_include_set[fragment] then + query_include_count = query_include_count + 1 + query_include_set[fragment] = query_include_count + query_include_list[query_include_count] = fragment + end + else + local primary = all_fragment_list[math.random(1, #all_fragment_list)] + local secondary = all_fragment_list[math.random(1, #all_fragment_list)] + + if math.random(1, 3) == 1 then + primary = evo.ANY + end + + if math.random(1, 3) == 1 then + secondary = evo.ANY + end + + local pair = evo.pair(primary, secondary) + + query_builder:include(pair) + + if not query_include_set[pair] then + query_include_count = query_include_count + 1 + query_include_set[pair] = query_include_count + query_include_list[query_include_count] = pair + end + end + end + + for _ = 1, math.random(0, 2) do + if math.random(1, 2) == 1 then + local fragment = all_fragment_list[math.random(1, #all_fragment_list)] + + query_builder:exclude(fragment) + + if not query_exclude_set[fragment] then + query_exclude_count = query_exclude_count + 1 + query_exclude_set[fragment] = query_exclude_count + query_exclude_list[query_exclude_count] = fragment + end + else + local primary = all_fragment_list[math.random(1, #all_fragment_list)] + local secondary = all_fragment_list[math.random(1, #all_fragment_list)] + + if math.random(1, 3) == 1 then + primary = evo.ANY + end + + if math.random(1, 3) == 1 then + secondary = evo.ANY + end + + local pair = evo.pair(primary, secondary) + + query_builder:exclude(pair) + + if not query_exclude_set[pair] then + query_exclude_count = query_exclude_count + 1 + query_exclude_set[pair] = query_exclude_count + query_exclude_list[query_exclude_count] = pair + end + end + end + + local query_entity_set = {} ---@type table + local query_entity_count = 0 ---@type integer + + do + local query = query_builder:spawn() + + for chunk, entity_list, entity_count in evo.execute(query) do + if not chunk:has(evo.INTERNAL) then + for i = 1, entity_count do + local entity = entity_list[i] + assert(not query_entity_set[entity]) + query_entity_count = query_entity_count + 1 + query_entity_set[entity] = query_entity_count + end + end + end + + if query_entity_set[query] then + query_entity_set[query] = nil + query_entity_count = query_entity_count - 1 + end + + evo.destroy(query) + end + + do + local expected_entity_count = 0 + + for _, entity in ipairs(all_entity_list) do + local is_entity_expected = + not evo.empty(entity) and + evo.has_all(entity, __table_unpack(query_include_list)) and + not evo.has_any(entity, __table_unpack(query_exclude_list)) + + for fragment in evo.each(entity) do + local is_fragment_explicit = false + + if not is_fragment_explicit and evo.is_pair(fragment) then + is_fragment_explicit = evo.has(evo.unpair(fragment), evo.EXPLICIT) + end + + if not is_fragment_explicit and not evo.is_pair(fragment) then + is_fragment_explicit = evo.has(fragment, evo.EXPLICIT) + end + + if is_fragment_explicit then + local is_fragment_included = false + + if not is_fragment_included then + is_fragment_included = query_include_set[fragment] ~= nil + end + + if not is_fragment_included and evo.is_pair(fragment) then + local fragment_primary = evo.unpair(fragment) + is_fragment_included = query_include_set[evo.pair(fragment_primary, evo.ANY)] ~= nil + end + + if not is_fragment_included and not evo.is_pair(fragment) then + is_fragment_included = query_include_set[evo.pair(fragment, evo.ANY)] ~= nil + end + + if not is_fragment_included then + is_entity_expected = false + break + end + end + end + + if is_entity_expected then + assert(query_entity_set[entity]) + expected_entity_count = expected_entity_count + 1 + else + assert(not query_entity_set[entity]) + end + end + + for _, entity in ipairs(all_fragment_list) do + local is_entity_expected = + not evo.empty(entity) and + evo.has_all(entity, __table_unpack(query_include_list)) and + not evo.has_any(entity, __table_unpack(query_exclude_list)) + + for fragment in evo.each(entity) do + if evo.has(fragment, evo.EXPLICIT) then + is_entity_expected = is_entity_expected and + (query_include_set[fragment] ~= nil) or + (evo.is_pair(fragment) and query_include_set[evo.pair(fragment, evo.ANY)] ~= nil) + end + end + + if is_entity_expected then + assert(query_entity_set[entity]) + expected_entity_count = expected_entity_count + 1 + else + assert(not query_entity_set[entity]) + end + end + + assert(query_entity_count == expected_entity_count) + end +end + +--- +--- +--- +--- +--- + +if math.random(1, 2) == 1 then + evo.collect_garbage() +end + +if math.random(1, 2) == 1 then + evo.destroy(__table_unpack(all_entity_list)) + if math.random(1, 2) == 1 then + evo.collect_garbage() + end + evo.destroy(__table_unpack(all_fragment_list)) +else + evo.destroy(__table_unpack(all_fragment_list)) + if math.random(1, 2) == 1 then + evo.collect_garbage() + end + evo.destroy(__table_unpack(all_entity_list)) +end + +if math.random(1, 2) == 1 then + evo.collect_garbage() +end diff --git a/develop/samples/relations.lua b/develop/samples/relations.lua new file mode 100644 index 0000000..6311fbe --- /dev/null +++ b/develop/samples/relations.lua @@ -0,0 +1,79 @@ +---@diagnostic disable: unused-local + +local evo = require 'evolved' + +evo.debug_mode(true) + +local fragments = { + planet = evo.builder() + :name('planet') + :tag() + :spawn(), + spaceship = evo.builder() + :name('spaceship') + :tag() + :spawn(), +} + +local relations = { + docked_to = evo.builder() + :name('docked_to') + :tag() + :spawn(), +} + +local planets = { + mars = evo.builder() + :name('Mars') + :set(fragments.planet) + :spawn(), + venus = evo.builder() + :name('Venus') + :set(fragments.planet) + :spawn(), +} + +local spaceships = { + falcon = evo.builder() + :name('Millennium Falcon') + :set(fragments.spaceship) + :set(evo.pair(relations.docked_to, planets.mars)) + :spawn(), + enterprise = evo.builder() + :name('USS Enterprise') + :set(fragments.spaceship) + :set(evo.pair(relations.docked_to, planets.venus)) + :spawn(), +} + +local queries = { + all_docked_spaceships = evo.builder() + :include(fragments.spaceship) + :include(evo.pair(relations.docked_to, evo.ANY)) + :spawn(), + docked_spaceships_to_mars = evo.builder() + :include(fragments.spaceship) + :include(evo.pair(relations.docked_to, planets.mars)) + :spawn(), + +} + +print '-= | All Docked Spaceships | =-' + +for chunk, entity_list, entity_count in evo.execute(queries.all_docked_spaceships) do + for i = 1, entity_count do + local entity = entity_list[i] + local planet = evo.secondary(entity, relations.docked_to) + print(string.format('%s is docked to %s', evo.name(entity), evo.name(planet))) + end +end + +print '-= | Docked Spaceships to Mars | =-' + +for chunk, entity_list, entity_count in evo.execute(queries.docked_spaceships_to_mars) do + for i = 1, entity_count do + local entity = entity_list[i] + local planet = evo.secondary(entity, relations.docked_to) + print(string.format('%s is docked to %s', evo.name(entity), evo.name(planet))) + end +end diff --git a/develop/testing/name_tests.lua b/develop/testing/name_tests.lua index 21b2e08..8808c2d 100644 --- a/develop/testing/name_tests.lua +++ b/develop/testing/name_tests.lua @@ -3,8 +3,8 @@ local evo = require 'evolved' do local id = evo.id() - local index, version = evo.unpack(id) - assert(evo.name(id) == string.format('$%d#%d:%d', id, index, version)) + local index, version, options = evo.unpack(id) + assert(evo.name(id) == string.format('$%d#%d:%d:%d', id, index, version, options)) evo.set(id, evo.NAME, 'hello') assert(evo.name(id) == 'hello') @@ -13,7 +13,7 @@ do assert(evo.name(id) == 'world') evo.destroy(id) - assert(evo.name(id) == string.format('$%d#%d:%d', id, index, version)) + assert(evo.name(id) == string.format('$%d#%d:%d:%d', id, index, version, options)) end do diff --git a/develop/testing/pairs_tests.lua b/develop/testing/pairs_tests.lua new file mode 100644 index 0000000..86e4d22 --- /dev/null +++ b/develop/testing/pairs_tests.lua @@ -0,0 +1,1920 @@ +local evo = require 'evolved' + +do + do + local p, s = evo.unpair(evo.pair(evo.ANY, evo.ANY)) + assert(p == evo.ANY and s == evo.ANY) + end + + do + local f = evo.id() + local p, s = evo.unpair(evo.pair(evo.ANY, f)) + assert(p == evo.ANY and s == f) + end + + do + local f = evo.id() + local p, s = evo.unpair(evo.pair(f, evo.ANY)) + assert(p == f and s == evo.ANY) + end + + do + local fp, fs = evo.id(2) + local p, s = evo.unpair(evo.pair(fp, fs)) + assert(p == fp and s == fs) + end +end + +do + local p, s1, s2 = evo.id(3) + + local e1 = evo.id() + evo.set(e1, evo.pair(p, s1), 11) + + local e12 = evo.id() + evo.set(e12, evo.pair(p, s1), 21) + evo.set(e12, evo.pair(p, s2), 42) + + assert(evo.has(e1, evo.pair(p, s1))) + assert(evo.get(e1, evo.pair(p, s1)) == 11) + assert(evo.has(e12, evo.pair(p, s1))) + assert(evo.get(e12, evo.pair(p, s1)) == 21) + + assert(not evo.has(e1, evo.pair(p, s2))) + assert(evo.get(e1, evo.pair(p, s2)) == nil) + assert(evo.has(e12, evo.pair(p, s2))) + assert(evo.get(e12, evo.pair(p, s2)) == 42) + + assert(evo.has(e1, evo.pair(p, evo.ANY))) + assert(evo.has(e1, evo.pair(evo.ANY, s1))) + assert(not evo.has(e1, evo.pair(evo.ANY, s2))) + assert(evo.has(e12, evo.pair(p, evo.ANY))) + assert(evo.has(e12, evo.pair(evo.ANY, s1))) + assert(evo.has(e12, evo.pair(evo.ANY, s2))) + + assert(not evo.has_all(e1, evo.pair(evo.ANY, s1), evo.pair(evo.ANY, s2))) + assert(evo.has_any(e1, evo.pair(evo.ANY, s1), evo.pair(evo.ANY, s2))) + assert(evo.has_all(e12, evo.pair(evo.ANY, s1), evo.pair(evo.ANY, s2))) + assert(evo.has_any(e12, evo.pair(evo.ANY, s1), evo.pair(evo.ANY, s2))) +end + +do + local p1, p2, s = evo.id(3) + + local e1 = evo.id() + evo.set(e1, evo.pair(p1, s), 11) + + local e12 = evo.id() + evo.set(e12, evo.pair(p1, s), 21) + evo.set(e12, evo.pair(p2, s), 42) + + assert(evo.has(e1, evo.pair(p1, s))) + assert(evo.get(e1, evo.pair(p1, s)) == 11) + assert(evo.has(e12, evo.pair(p1, s))) + assert(evo.get(e12, evo.pair(p1, s)) == 21) + + assert(not evo.has(e1, evo.pair(p2, s))) + assert(evo.get(e1, evo.pair(p2, s)) == nil) + assert(evo.has(e12, evo.pair(p2, s))) + assert(evo.get(e12, evo.pair(p2, s)) == 42) + + assert(evo.has(e1, evo.pair(p1, evo.ANY))) + assert(not evo.has(e1, evo.pair(p2, evo.ANY))) + assert(evo.has(e1, evo.pair(evo.ANY, s))) + assert(evo.has(e12, evo.pair(p1, evo.ANY))) + assert(evo.has(e12, evo.pair(p2, evo.ANY))) + assert(evo.has(e12, evo.pair(evo.ANY, s))) + + assert(not evo.has_all(e1, evo.pair(p1, evo.ANY), evo.pair(p2, evo.ANY))) + assert(evo.has_any(e1, evo.pair(p1, evo.ANY), evo.pair(p2, evo.ANY))) + assert(evo.has_all(e12, evo.pair(p1, evo.ANY), evo.pair(p2, evo.ANY))) + assert(evo.has_any(e12, evo.pair(p1, evo.ANY), evo.pair(p2, evo.ANY))) +end + +do + local p1, s1, p2, s2 = evo.id(4) + evo.set(p1, s1) + evo.set(s1, p1) + evo.set(p2, s2) + assert(evo.empty(evo.pair(p1, s1))) + assert(evo.empty(evo.pair(p2, s2))) + assert(evo.empty_all(evo.pair(p1, s1), evo.pair(p2, s2))) + assert(evo.empty_any(evo.pair(p1, s1), evo.pair(p2, s2))) + assert(not evo.empty_all(evo.pair(p1, s1), evo.pair(p2, s2), p1)) + assert(evo.empty_any(evo.pair(p1, s1), evo.pair(p2, s2), p1)) + assert(evo.empty_all(evo.pair(p1, s1), evo.pair(p2, s2), s2)) + assert(evo.empty_any(evo.pair(p1, s1), evo.pair(p2, s2), s2)) +end + +do + local p1, s1 = evo.id(2) + evo.set(p1, s1) + evo.set(s1, p1) + assert(not evo.has(evo.pair(p1, s1), p1)) + assert(not evo.has(evo.pair(p1, s1), s1)) + assert(not evo.has_all(evo.pair(p1, s1), p1, s1)) + assert(not evo.has_any(evo.pair(p1, s1), p1, s1)) + assert(evo.get(evo.pair(p1, s1), p1) == nil) + assert(evo.get(evo.pair(p1, s1), s1) == nil) +end + +do + local p, s1, s2 = evo.id(3) + + do + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + evo.remove(e, evo.pair(p, s1)) + + assert(not evo.has(e, evo.pair(p, s1))) + assert(evo.get(e, evo.pair(p, s1)) == nil) + + assert(evo.has(e, evo.pair(p, s2))) + assert(evo.get(e, evo.pair(p, s2)) == 42) + + evo.remove(e, evo.pair(p, s2)) + + assert(not evo.has(e, evo.pair(p, s2))) + assert(evo.get(e, evo.pair(p, s2)) == nil) + + assert(not evo.has(e, evo.pair(p, s2))) + assert(evo.get(e, evo.pair(p, s2)) == nil) + end + + do + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + evo.remove(e, evo.pair(p, s2)) + + assert(evo.has(e, evo.pair(p, s1))) + assert(evo.get(e, evo.pair(p, s1)) == 21) + + assert(not evo.has(e, evo.pair(p, s2))) + assert(evo.get(e, evo.pair(p, s2)) == nil) + + evo.remove(e, evo.pair(p, s1)) + + assert(not evo.has(e, evo.pair(p, s1))) + assert(evo.get(e, evo.pair(p, s1)) == nil) + + assert(not evo.has(e, evo.pair(p, s2))) + assert(evo.get(e, evo.pair(p, s2)) == nil) + end +end + +do + local p1, p2, s1, s2 = evo.id(4) + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 11) + :set(evo.pair(p1, s2), 12) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 22) + :spawn() + + evo.remove(e, evo.pair(p1, evo.ANY)) + + assert(not evo.has(e, evo.pair(p1, s1))) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(not evo.has(e, evo.pair(p1, evo.ANY))) + + assert(evo.has(e, evo.pair(p2, s1))) + assert(evo.get(e, evo.pair(p2, s1)) == 21) + assert(evo.has(e, evo.pair(p2, s2))) + assert(evo.get(e, evo.pair(p2, s2)) == 22) + assert(evo.has(e, evo.pair(p2, evo.ANY))) + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 11) + :set(evo.pair(p1, s2), 12) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 22) + :spawn() + + evo.remove(e, evo.pair(p2, evo.ANY)) + + assert(not evo.has(e, evo.pair(p2, s1))) + assert(not evo.has(e, evo.pair(p2, s2))) + assert(not evo.has(e, evo.pair(p2, evo.ANY))) + + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == 11) + assert(evo.has(e, evo.pair(p1, s2))) + assert(evo.get(e, evo.pair(p1, s2)) == 12) + assert(evo.has(e, evo.pair(p1, evo.ANY))) + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 11) + :set(evo.pair(p1, s2), 12) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 22) + :spawn() + + evo.remove(e, evo.pair(evo.ANY, s1)) + + assert(not evo.has(e, evo.pair(p1, s1))) + assert(not evo.has(e, evo.pair(p2, s1))) + assert(not evo.has(e, evo.pair(evo.ANY, s1))) + + assert(evo.has(e, evo.pair(p1, s2))) + assert(evo.get(e, evo.pair(p1, s2)) == 12) + assert(evo.has(e, evo.pair(p2, s2))) + assert(evo.get(e, evo.pair(p2, s2)) == 22) + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 11) + :set(evo.pair(p1, s2), 12) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 22) + :spawn() + + evo.remove(e, evo.pair(evo.ANY, s2)) + + assert(not evo.has(e, evo.pair(p1, s2))) + assert(not evo.has(e, evo.pair(p2, s2))) + assert(not evo.has(e, evo.pair(evo.ANY, s2))) + + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == 11) + assert(evo.has(e, evo.pair(p2, s1))) + assert(evo.get(e, evo.pair(p2, s1)) == 21) + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 11) + :set(evo.pair(p1, s2), 12) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 22) + :set(p1, s1) + :set(p2, s2) + :spawn() + + evo.remove(e, evo.pair(evo.ANY, evo.ANY)) + + assert(not evo.has(e, evo.pair(p1, s1))) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(not evo.has(e, evo.pair(p2, s1))) + assert(not evo.has(e, evo.pair(p2, s2))) + + assert(evo.has(e, p1) and evo.get(e, p1) == s1) + assert(evo.has(e, p2) and evo.get(e, p2) == s2) + end +end + +do + local p1, s1, p2, s2 = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :spawn() + + evo.remove(e, evo.pair(p2, evo.ANY)) + evo.remove(e, evo.pair(evo.ANY, s2)) + + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == 42) + + evo.remove(e, evo.pair(p1, s1)) + + assert(not evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == nil) +end + +do + local f1, f2, f3, p1, s1, p2, s2 = evo.id(7) + evo.set(f1, evo.REQUIRES, { f2 }) + evo.set(f2, evo.DEFAULT, 84) + evo.set(f2, evo.REQUIRES, { evo.pair(p2, s2) }) + evo.set(p1, evo.REQUIRES, { f3 }) + evo.set(s1, evo.REQUIRES, { f3 }) + evo.set(p2, evo.REQUIRES, { f3 }) + evo.set(s2, evo.REQUIRES, { f3 }) + + local e = evo.builder() + :set(f1, 21) + :set(evo.pair(p1, s1), 42) + :spawn() + + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == 42) + + assert(evo.has(e, evo.pair(p2, s2))) + assert(evo.get(e, evo.pair(p2, s2)) == true) + + assert(evo.has(e, f1)) + assert(evo.get(e, f1) == 21) + + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == 84) + + assert(evo.has(e, f3)) + assert(evo.get(e, f3) == true) +end + +do + local p1, p2, s1, s2 = evo.id(4) + + do + local e1 = evo.builder() + :set(evo.pair(p1, s1)) + :set(evo.pair(p1, s2)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(p1, evo.ANY)) + evo.remove(e2, evo.pair(p1, evo.ANY)) + assert(evo.empty_all(e1, e2)) + end + + do + local e1 = evo.builder() + :set(evo.pair(p1, s1)) + :set(evo.pair(p2, s1)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(evo.ANY, s1)) + evo.remove(e2, evo.pair(evo.ANY, s1)) + assert(evo.empty_all(e1, e2)) + end + + do + local e1 = evo.builder() + :set(evo.pair(p1, s1)) + :set(evo.pair(p1, s2)) + :set(evo.pair(p2, s1)) + :set(evo.pair(p2, s2)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(evo.ANY, evo.ANY)) + evo.remove(e2, evo.pair(evo.ANY, evo.ANY)) + assert(evo.empty_all(e1, e2)) + end +end + +do + local f, p1, p2, s1, s2 = evo.id(5) + + do + local e1 = evo.builder() + :set(f, 42) + :set(evo.pair(p1, s1)) + :set(evo.pair(p1, s2)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(p1, evo.ANY)) + evo.remove(e2, evo.pair(p1, evo.ANY)) + + assert(evo.has(e1, f) and evo.has(e2, f)) + assert(not evo.has(e1, evo.pair(evo.ANY, evo.ANY))) + end + + do + local e1 = evo.builder() + :set(f, 42) + :set(evo.pair(p1, s1)) + :set(evo.pair(p2, s1)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(evo.ANY, s1)) + evo.remove(e2, evo.pair(evo.ANY, s1)) + + assert(evo.has(e1, f) and evo.has(e2, f)) + assert(not evo.has(e1, evo.pair(evo.ANY, evo.ANY))) + end + + do + local e1 = evo.builder() + :set(f, 42) + :set(evo.pair(p1, s1)) + :set(evo.pair(p1, s2)) + :set(evo.pair(p2, s1)) + :set(evo.pair(p2, s2)) + :spawn() + + local e2 = evo.clone(e1) + + evo.remove(e1, evo.pair(evo.ANY, evo.ANY)) + evo.remove(e2, evo.pair(evo.ANY, evo.ANY)) + + assert(evo.has(e1, f) and evo.has(e2, f)) + assert(not evo.has(e1, evo.pair(evo.ANY, evo.ANY))) + end +end + +do + do + local p, s = evo.id(2) + evo.set(p, evo.NAME, 'p') + evo.set(s, evo.NAME, 's') + local ps_chunk = evo.chunk(evo.pair(p, s)) + assert(tostring(ps_chunk) == '<${p,s}>') + end + do + local p, s = evo.id(2) + local ps_chunk = evo.chunk(evo.pair(p, s)) + evo.set(p, evo.NAME, 'p') + evo.set(s, evo.NAME, 's') + evo.destroy(p) + assert(tostring(ps_chunk) ~= '<${p,s}>') + end + do + local p, s = evo.id(2) + local ps_chunk = evo.chunk(evo.pair(p, s)) + evo.set(p, evo.NAME, 'p') + evo.set(s, evo.NAME, 's') + evo.destroy(s) + assert(tostring(ps_chunk) ~= '<${p,s}>') + end + do + local p, s = evo.id(2) + local ps_chunk = evo.chunk(evo.pair(p, s)) + evo.set(p, evo.NAME, 'p') + evo.set(s, evo.NAME, 's') + evo.destroy(p, s) + assert(tostring(ps_chunk) ~= '<${p,s}>') + end +end + +do + do + local p, s = evo.id(2) + local ps = evo.pair(evo.ANY, s) + local e = evo.id() + evo.set(e, p, 42) + evo.destroy(s) + evo.remove(e, ps) + end + + do + local p, s = evo.id(2) + local ps = evo.pair(p, evo.ANY) + local e = evo.id() + evo.set(e, s, 42) + evo.destroy(p) + evo.remove(e, ps) + end +end + +do + local p, s = evo.id(2) + + local e = evo.id() + assert(not evo.has(e, evo.pair(p, s))) + assert(not evo.has(e, evo.pair(p, evo.ANY))) + assert(not evo.has(e, evo.pair(evo.ANY, s))) + assert(not evo.has(e, evo.pair(evo.ANY, evo.ANY))) + + evo.set(e, p) + assert(not evo.has(e, evo.pair(p, s))) + assert(not evo.has(e, evo.pair(p, evo.ANY))) + assert(not evo.has(e, evo.pair(evo.ANY, s))) + assert(not evo.has(e, evo.pair(evo.ANY, evo.ANY))) + + evo.set(e, s) + assert(not evo.has(e, evo.pair(p, s))) + assert(not evo.has(e, evo.pair(p, evo.ANY))) + assert(not evo.has(e, evo.pair(evo.ANY, s))) + assert(not evo.has(e, evo.pair(evo.ANY, evo.ANY))) + + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, evo.pair(p, s))) + assert(evo.has(e, evo.pair(p, evo.ANY))) + assert(evo.has(e, evo.pair(evo.ANY, s))) + assert(evo.has(e, evo.pair(evo.ANY, evo.ANY))) +end + +do + local p1, s1, p2, s2 = evo.id(4) + + local e = evo.builder():set(evo.pair(p1, s1)):spawn() + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.has(e, evo.pair(p1, evo.ANY))) + assert(evo.has(e, evo.pair(evo.ANY, s1))) + assert(evo.has(e, evo.pair(evo.ANY, evo.ANY))) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(not evo.has(e, evo.pair(p2, s1))) + assert(not evo.has(e, evo.pair(p2, s2))) + assert(not evo.has(e, evo.pair(p2, evo.ANY))) + assert(not evo.has(e, evo.pair(evo.ANY, s2))) + + evo.set(e, evo.pair(p2, s2)) + + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.has(e, evo.pair(p1, evo.ANY))) + assert(evo.has(e, evo.pair(evo.ANY, s1))) + assert(evo.has(e, evo.pair(evo.ANY, evo.ANY))) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(not evo.has(e, evo.pair(p2, s1))) + assert(evo.has(e, evo.pair(p2, s2))) + assert(evo.has(e, evo.pair(p2, evo.ANY))) + assert(evo.has(e, evo.pair(evo.ANY, s2))) +end + +do + local p1, s1, s2 = evo.id(3) + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :spawn() + + evo.set(e, evo.pair(p1, s1), 84) + assert(evo.get(e, evo.pair(p1, s1)) == 84) + assert(evo.get(e, evo.pair(p1, s2)) == nil) + + evo.set(e, evo.pair(p1, s2), 42) + assert(evo.get(e, evo.pair(p1, s1)) == 84) + assert(evo.get(e, evo.pair(p1, s2)) == 42) + end +end + +do + local p1, s1, p2, s2 = evo.id(4) + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p1, s2), 84) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 63) + :spawn() + + evo.remove(e, evo.pair(p1, evo.ANY)) + assert(not evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == nil) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(evo.get(e, evo.pair(p1, s2)) == nil) + assert(evo.has(e, evo.pair(p2, s1))) + assert(evo.get(e, evo.pair(p2, s1)) == 21) + assert(evo.has(e, evo.pair(p2, s2))) + assert(evo.get(e, evo.pair(p2, s2)) == 63) + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p1, s2), 84) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 63) + :spawn() + + evo.remove(e, evo.pair(evo.ANY, s2)) + assert(evo.has(e, evo.pair(p1, s1))) + assert(evo.get(e, evo.pair(p1, s1)) == 42) + assert(not evo.has(e, evo.pair(p1, s2))) + assert(evo.get(e, evo.pair(p1, s2)) == nil) + assert(evo.has(e, evo.pair(p2, s1))) + assert(evo.get(e, evo.pair(p2, s1)) == 21) + assert(not evo.has(e, evo.pair(p2, s2))) + assert(evo.get(e, evo.pair(p2, s2)) == nil) + end +end + +do + local p1, p2, s1, s2 = evo.id(4) + + ---@param o evolved.entity + ---@param s evolved.fragment + ---@return evolved.fragment[], evolved.component[], number + local function collect_primaries(o, s) + local fragments, components, count = {}, {}, 0 + + for f, c in evo.primaries(o, s) do + count = count + 1 + + fragments[count] = f + components[count] = c + + do + local ff, cc = evo.primary(o, s, count) + assert(ff == f and cc == c) + end + end + + assert(evo.primary_count(o, s) == count) + return fragments, components, count + end + + ---@param o evolved.entity + ---@param p evolved.fragment + ---@return evolved.fragment[], evolved.component[], number + local function collect_secondaries(o, p) + local fragments, components, count = {}, {}, 0 + + for f, c in evo.secondaries(o, p) do + count = count + 1 + fragments[count] = f + components[count] = c + end + + return fragments, components, count + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :spawn() + + assert(evo.primary(e, s1) == p1) + assert(evo.primary(e, s2) == nil) + + assert(evo.secondary(e, p1) == s1) + assert(evo.secondary(e, p2) == nil) + + assert(evo.primary_count(e, s1) == 1) + assert(evo.primary_count(e, s2) == 0) + assert(evo.secondary_count(e, p1) == 1) + assert(evo.secondary_count(e, p2) == 0) + + do + local p_list, c_list, count = collect_primaries(e, s1) + assert(#p_list == 1 and #c_list == 1 and count == 1) + assert(p_list[1] == p1 and c_list[1] == 42) + end + + do + local p_list, c_list, count = collect_primaries(e, s2) + assert(#p_list == 0 and #c_list == 0 and count == 0) + end + + do + local s_list, c_list, count = collect_secondaries(e, p1) + assert(#s_list == 1 and #c_list == 1 and count == 1) + assert(s_list[1] == s1 and c_list[1] == 42) + end + + do + local s_list, c_list, count = collect_secondaries(e, p2) + assert(#s_list == 0 and #c_list == 0 and count == 0) + end + end + + do + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p1, s2), 84) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 63) + :spawn() + + do + assert(evo.primary_count(e, s1) == 2) + assert(evo.primary_count(e, s2) == 2) + assert(evo.secondary_count(e, p1) == 2) + assert(evo.secondary_count(e, p2) == 2) + end + + do + local pp, cc = evo.primary(e, s1) + assert(pp == p1 and cc == 42) + + pp, cc = evo.primary(e, s1, 1) + assert(pp == p1 and cc == 42) + + pp, cc = evo.primary(e, s1, 2) + assert(pp == p2 and cc == 21) + + pp, cc = evo.primary(e, s1, 3) + assert(pp == nil and cc == nil) + end + + do + local pp, cc = evo.primary(e, s2) + assert(pp == p1 and cc == 84) + + pp, cc = evo.primary(e, s2, 1) + assert(pp == p1 and cc == 84) + + pp, cc = evo.primary(e, s2, 2) + assert(pp == p2 and cc == 63) + + pp, cc = evo.primary(e, s2, 3) + assert(pp == nil and cc == nil) + end + + do + local pp, cc = evo.secondary(e, p1) + assert(pp == s1 and cc == 42) + + pp, cc = evo.secondary(e, p1, 1) + assert(pp == s1 and cc == 42) + + pp, cc = evo.secondary(e, p1, 2) + assert(pp == s2 and cc == 84) + + pp, cc = evo.secondary(e, p1, 3) + assert(pp == nil and cc == nil) + end + + do + local pp, cc = evo.secondary(e, p2) + assert(pp == s1 and cc == 21) + + pp, cc = evo.secondary(e, p2, 1) + assert(pp == s1 and cc == 21) + + pp, cc = evo.secondary(e, p2, 2) + assert(pp == s2 and cc == 63) + + pp, cc = evo.secondary(e, p2, 3) + assert(pp == nil and cc == nil) + end + + do + local p_list, c_list, count = collect_primaries(e, s1) + assert(#p_list == 2 and #c_list == 2 and count == 2) + assert(p_list[1] == p1 and c_list[1] == 42) + assert(p_list[2] == p2 and c_list[2] == 21) + end + + do + local p_list, c_list, count = collect_primaries(e, s2) + assert(#p_list == 2 and #c_list == 2 and count == 2) + assert(p_list[1] == p1 and c_list[1] == 84) + assert(p_list[2] == p2 and c_list[2] == 63) + end + + do + local s_list, c_list, count = collect_secondaries(e, p1) + assert(#s_list == 2 and #c_list == 2 and count == 2) + assert(s_list[1] == s1 and c_list[1] == 42) + assert(s_list[2] == s2 and c_list[2] == 84) + end + + do + local s_list, c_list, count = collect_secondaries(e, p2) + assert(#s_list == 2 and #c_list == 2 and count == 2) + assert(s_list[1] == s1 and c_list[1] == 21) + assert(s_list[2] == s2 and c_list[2] == 63) + end + end +end + +do + local p, s = evo.id(2) + + local e = evo.id() + + assert(not evo.primary(e, s)) + assert(not evo.primary(e, s, 1)) + assert(not evo.primary(e, s, 2)) + assert(not evo.primary(e, s, 0)) + assert(not evo.primary(e, s, -1)) + assert(not evo.primary(e, s, -2)) + + assert(not evo.secondary(e, p)) + assert(not evo.secondary(e, p, 1)) + assert(not evo.secondary(e, p, 2)) + assert(not evo.secondary(e, p, 0)) + assert(not evo.secondary(e, p, -1)) + assert(not evo.secondary(e, p, -2)) + + assert(evo.primary_count(e, s) == 0) + assert(evo.secondary_count(e, p) == 0) + + assert(evo.primaries(e, s)() == nil) + assert(evo.secondaries(e, p)() == nil) +end + +do + local p1, p2, s1, s2 = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p1, s2), 84) + :set(evo.pair(p2, s1), 21) + :set(evo.pair(p2, s2), 63) + :spawn() + + assert(evo.primary(e, evo.ANY) == nil) + assert(evo.primary(e, evo.ANY, 1) == nil) + assert(evo.primary(e, evo.ANY, 2) == nil) + + assert(evo.secondary(e, evo.ANY) == nil) + assert(evo.secondary(e, evo.ANY, 1) == nil) + assert(evo.secondary(e, evo.ANY, 2) == nil) + + assert(evo.primaries(e, evo.ANY)() == nil) + assert(evo.secondaries(e, evo.ANY)() == nil) + + assert(evo.primary_count(e, evo.ANY) == 0) + assert(evo.secondary_count(e, evo.ANY) == 0) +end + +do + do + local p, s = evo.id(2) + + local e = evo.builder() + :set(s) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(p) + assert(evo.alive(e)) + assert(evo.has(e, s)) + assert(not evo.has(e, evo.pair(p, s))) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local p, s = evo.id(2) + + local e = evo.builder() + :set(p) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(s) + assert(evo.alive(e)) + assert(evo.has(e, p)) + assert(not evo.has(e, evo.pair(p, s))) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local p, s = evo.id(2) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(p) + assert(not evo.alive(e)) + end + + do + local p, s = evo.id(2) + evo.set(s, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(s) + assert(not evo.alive(e)) + end +end + +do + local p1, s1 = evo.id(2) + + local e0 = evo.builder() + :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + :spawn() + + local e1 = evo.builder() + :set(evo.pair(p1, e0), 11) + :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + :spawn() + + local e2 = evo.builder() + :set(evo.pair(e1, s1), 22) + :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + :spawn() + + local e3 = evo.builder() + :set(evo.pair(e2, e2), 33) + :spawn() + + evo.destroy(e0) + assert(not evo.alive(e1)) + assert(not evo.alive(e2)) + assert(not evo.alive(e3)) +end + +do + do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(f) + assert(evo.get(e, f) == nil) + assert(evo.get(e, evo.pair(p, s)) == 42) + end + + do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(p) + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(s) + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(p, s) + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(f, p, s) + assert(evo.alive(e) and evo.empty(e)) + end +end + +do + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(p) + assert(not evo.alive(e)) + end + + do + local f, p, s = evo.id(3) + evo.set(s, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(p) + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(s) + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(s, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(s) + assert(not evo.alive(e)) + end +end + +do + local f, p, s = evo.id(3) + + local e = evo.builder():set(f, 21):set(evo.pair(p, s), 42):spawn() + + evo.destroy(evo.pair(p, s)) + evo.destroy(evo.pair(evo.ANY, s)) + evo.destroy(evo.pair(p, evo.ANY)) + evo.destroy(evo.pair(evo.ANY, evo.ANY)) + + assert(evo.get(e, f) == 21) + assert(evo.get(e, evo.pair(p, s)) == 42) +end + +do + evo.collect_garbage() + + local f, p1, s1, p2, s2 = evo.id(5) + + local e1 = evo.builder() + :set(f, 21) + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p2, s2), 84) + :spawn() + + local e2 = evo.builder() + :set(f, 21) + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p2, s2), 84) + :spawn() + + local f_chunk = evo.chunk(f) + local f_p2s2_chunk = evo.chunk(f, evo.pair(p2, s2)) + local f_p1s1_p2s2_chunk = evo.chunk(f, evo.pair(p1, s1), evo.pair(p2, s2)) + + assert(f_p1s1_p2s2_chunk:entities()[1] == e1) + assert(f_p1s1_p2s2_chunk:entities()[2] == e2) + + evo.remove(e1, evo.pair(p1, evo.ANY)) + + assert(f_p2s2_chunk:entities()[1] == e1) + assert(f_p1s1_p2s2_chunk:entities()[1] == e2) + + evo.remove(e1, evo.pair(p2, evo.ANY)) + + assert(f_chunk:entities()[1] == e1) + assert(f_p1s1_p2s2_chunk:entities()[1] == e2) + + evo.collect_garbage() + + assert(f_chunk:alive()) + assert(not f_p2s2_chunk:alive()) + assert(f_p1s1_p2s2_chunk:alive()) + + evo.remove(e2, evo.pair(p1, evo.ANY)) + + local new_f_p2s2_chunk = evo.chunk(f, evo.pair(p2, s2)) + assert(new_f_p2s2_chunk:entities()[1] == e2) +end + +do + evo.collect_garbage() + + local f, p1, p2, s1, s2 = evo.id(5) + + local e1 = evo.builder() + :set(f, 21) + :set(evo.pair(p1, s1), 42) + :set(evo.pair(p2, s2), 84) + :spawn() + + local f_p1s1_p2s2_chunk = evo.chunk(f, evo.pair(p1, s1), evo.pair(p2, s2)) + assert(f_p1s1_p2s2_chunk:entities()[1] == e1) + + evo.destroy(p2, s2) + + evo.collect_garbage() + + local f_p1s1_chunk = evo.chunk(f, evo.pair(p1, s1)) + assert(f_p1s1_chunk:entities()[1] == e1) +end + +do + local f, p, s = evo.id(3) + evo.set(p, evo.DEFAULT, 42) + + do + local e = evo.id() + evo.set(e, f) + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + + do + local e = evo.builder():set(f):set(evo.pair(p, s)):spawn() + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + + do + local e = evo.builder():set(f, 84):set(evo.pair(p, s), 21):spawn() + evo.set(e, f) + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end +end + +do + do + local f, p, s = evo.id(3) + assert(evo.empty(evo.pair(p, s))) + + evo.set(p, f) + assert(evo.empty(evo.pair(p, s))) + + evo.destroy(p) + assert(evo.empty(evo.pair(p, s))) + end + + do + local f, p, s = evo.id(3) + assert(evo.empty(evo.pair(p, s))) + + evo.set(p, f) + assert(evo.empty(evo.pair(p, s))) + + evo.destroy(s) + assert(evo.empty(evo.pair(p, s))) + + evo.destroy(p) + assert(evo.empty(evo.pair(p, s))) + end + + do + local f, p, s = evo.id(3) + assert(not evo.has(p, f)) + assert(not evo.has(evo.pair(p, s), f)) + + evo.set(p, f, 42) + assert(evo.has(p, f)) + assert(not evo.has(evo.pair(p, s), f)) + assert(not evo.has(evo.pair(s, p), f)) + assert(evo.get(p, f) == 42) + assert(evo.get(evo.pair(p, s), f) == nil) + assert(evo.get(evo.pair(s, p), f) == nil) + end +end + +do + local p, s = evo.id(3) + + local set_count = 0 + local insert_count = 0 + local remove_count = 0 + + evo.set(p, evo.ON_SET, function(e, f, nc, oc) + set_count = set_count + 1 + assert(f == p or f == evo.pair(p, s)) + assert(nc == 21 or nc == 42) + assert(oc == nil or oc == 21) + assert(evo.has(e, f)) + assert(evo.get(e, f) == nc) + end) + + evo.set(p, evo.ON_INSERT, function(e, f, nc) + insert_count = insert_count + 1 + assert(f == p or f == evo.pair(p, s)) + assert(nc == 21 or nc == 42) + assert(evo.has(e, f)) + assert(evo.get(e, f) == nc) + end) + + evo.set(p, evo.ON_REMOVE, function(e, f, oc) + remove_count = remove_count + 1 + assert(f == p or f == evo.pair(p, s)) + assert(oc == 21 or oc == 42) + assert(not evo.has(e, f)) + end) + + do + set_count, insert_count, remove_count = 0, 0, 0 + local e = evo.id() + evo.set(e, p, 21) + evo.set(e, evo.pair(p, s), 42) + assert(set_count == 2) + assert(insert_count == 2) + assert(remove_count == 0) + evo.remove(e, p) + assert(set_count == 2) + assert(insert_count == 2) + assert(remove_count == 1) + evo.remove(e, evo.pair(p, s)) + assert(set_count == 2) + assert(insert_count == 2) + assert(remove_count == 2) + end + + do + set_count, insert_count, remove_count = 0, 0, 0 + local e = evo.id() + evo.set(e, p, 21) + evo.set(e, evo.pair(p, s), 42) + assert(set_count == 2) + assert(insert_count == 2) + assert(remove_count == 0) + evo.destroy(e) + assert(set_count == 2) + assert(insert_count == 2) + assert(remove_count == 2) + end +end + +do + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(p) + + assert(not evo.alive(e) and evo.empty(e)) + assert(not evo.has(e, f) and evo.get(e, f) == nil) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(s) + + assert(evo.alive(e) and not evo.empty(e)) + assert(evo.has(e, f) and evo.get(e, f) == 21) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(p) + + assert(evo.alive(e) and not evo.empty(e)) + assert(evo.has(e, f) and evo.get(e, f) == 21) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(s) + + assert(evo.alive(e) and not evo.empty(e)) + assert(evo.has(e, f) and evo.get(e, f) == 21) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) + evo.set(s, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(p) + + assert(evo.alive(e) and not evo.empty(e)) + assert(evo.has(e, f) and evo.get(e, f) == 21) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) + evo.set(s, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) + + local e = evo.builder() + :set(f, 21) + :set(evo.pair(p, s), 42) + :spawn() + + evo.destroy(s) + + assert(not evo.alive(e) and evo.empty(e)) + assert(not evo.has(e, f) and evo.get(e, f) == nil) + assert(not evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end +end + +do + do + local p, s = evo.id(2) + evo.set(p, evo.DEFAULT, 42) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + end + + do + local p, s = evo.id(2) + evo.set(s, evo.DEFAULT, 21) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + end + + do + local p, s = evo.id(2) + evo.set(p, evo.DEFAULT, 42) + evo.set(s, evo.DEFAULT, 21) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + end + end +end + +do + do + local f, p, s = evo.id(3) + evo.set(p, evo.REQUIRES, { f }) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + end + + do + local f, p, s = evo.id(3) + evo.set(s, evo.REQUIRES, { f }) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(not evo.has(e, f) and evo.get(e, f) == nil) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(not evo.has(e, f) and evo.get(e, f) == nil) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + end + end + + do + local f, p, s = evo.id(3) + evo.set(p, evo.REQUIRES, { f }) + evo.set(f, evo.REQUIRES, { evo.pair(s, p) }) + evo.set(s, evo.REQUIRES, { p }) + + do + local e = evo.id() + evo.set(e, evo.pair(p, s)) + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + assert(evo.has(e, evo.pair(s, p)) and evo.get(e, evo.pair(s, p)) == true) + assert(evo.has(e, p) and evo.get(e, p) == true) + end + + do + local e = evo.builder():set(evo.pair(p, s)):spawn() + assert(evo.has(e, f) and evo.get(e, f) == true) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == true) + assert(evo.has(e, evo.pair(s, p)) and evo.get(e, evo.pair(s, p)) == true) + assert(evo.has(e, p) and evo.get(e, p) == true) + end + end +end + +do + do + local p, s = evo.id(2) + + local e = evo.builder():set(p, 21):set(evo.pair(p, s), 42):spawn() + assert(evo.has(e, p) and evo.get(e, p) == 21) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + + evo.set(s, evo.TAG) + assert(evo.has(e, p) and evo.get(e, p) == 21) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + + evo.set(p, evo.TAG) + assert(evo.has(e, p) and evo.get(e, p) == nil) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end + + do + local p, s = evo.id(2) + + local e = evo.builder():set(evo.pair(p, s), 42):spawn() + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + + evo.set(s, evo.TAG) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == 42) + + evo.set(p, evo.TAG) + assert(evo.has(e, evo.pair(p, s)) and evo.get(e, evo.pair(p, s)) == nil) + end +end + +do + local p1, p2, s1, s2 = evo.id(4) + + do + local b = evo.builder() + + b:set(evo.pair(p1, s1), 11) + b:set(evo.pair(p1, s2), 12) + b:set(evo.pair(p2, s1), 21) + b:set(evo.pair(p2, s2), 22) + + b:remove(evo.pair(evo.ANY, evo.ANY)) + + assert(not b:has(evo.pair(p1, s1))) + assert(not b:has(evo.pair(p1, s2))) + assert(not b:has(evo.pair(p2, s1))) + assert(not b:has(evo.pair(p2, s2))) + + assert(not b:has(evo.pair(p1, evo.ANY))) + assert(not b:has(evo.pair(p2, evo.ANY))) + assert(not b:has(evo.pair(evo.ANY, s1))) + assert(not b:has(evo.pair(evo.ANY, s2))) + + assert(not b:has(evo.pair(evo.ANY, evo.ANY))) + end + + do + local b = evo.builder() + + b:set(evo.pair(p1, s1), 11) + b:set(evo.pair(p1, s2), 12) + b:set(evo.pair(p2, s1), 21) + b:set(evo.pair(p2, s2), 22) + + b:remove(evo.pair(p2, evo.ANY)) + + assert(b:has(evo.pair(p1, s1))) + assert(b:has(evo.pair(p1, s2))) + assert(not b:has(evo.pair(p2, s1))) + assert(not b:has(evo.pair(p2, s2))) + + assert(b:has(evo.pair(p1, evo.ANY))) + assert(not b:has(evo.pair(p2, evo.ANY))) + assert(b:has(evo.pair(evo.ANY, s1))) + assert(b:has(evo.pair(evo.ANY, s2))) + + assert(b:has(evo.pair(evo.ANY, evo.ANY))) + end + + do + local b = evo.builder() + + b:set(evo.pair(p1, s1), 11) + b:set(evo.pair(p1, s2), 12) + b:set(evo.pair(p2, s1), 21) + b:set(evo.pair(p2, s2), 22) + + b:remove(evo.pair(p2, evo.ANY)) + + assert(b:has_all(evo.pair(p1, s1))) + assert(b:has_all(evo.pair(p1, s1), evo.pair(p1, s2))) + assert(not b:has_all(evo.pair(p1, s1), evo.pair(p2, s1))) + assert(not b:has_all(evo.pair(p2, s1), evo.pair(p1, s2))) + assert(not b:has_all(evo.pair(p2, s1), evo.pair(p2, s2))) + + assert(b:has_all(evo.pair(p1, evo.ANY))) + assert(b:has_all(evo.pair(p1, evo.ANY), evo.pair(evo.ANY, s1))) + assert(not b:has_all(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, s1))) + assert(not b:has_all(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, p1))) + + assert(b:has_all(evo.pair(evo.ANY, evo.ANY))) + + assert(b:has_all( + evo.pair(p1, s1), + evo.pair(p1, s2), + evo.pair(evo.ANY, s1), + evo.pair(p1, evo.ANY), + evo.pair(evo.ANY, evo.ANY))) + + assert(not b:has_all( + evo.pair(p1, s1), + evo.pair(p1, s2), + evo.pair(evo.ANY, s1), + evo.pair(p1, evo.ANY), + evo.pair(evo.ANY, evo.ANY), + evo.pair(p2, evo.ANY))) + end + + do + local b = evo.builder() + + b:set(evo.pair(p1, s1), 11) + b:set(evo.pair(p1, s2), 12) + b:set(evo.pair(p2, s1), 21) + b:set(evo.pair(p2, s2), 22) + + b:remove(evo.pair(p2, evo.ANY)) + + assert(b:has_any(evo.pair(p1, s1))) + assert(b:has_any(evo.pair(p1, s1), evo.pair(p1, s2))) + assert(b:has_any(evo.pair(p1, s1), evo.pair(p2, s1))) + assert(b:has_any(evo.pair(p2, s1), evo.pair(p1, s2))) + assert(not b:has_any(evo.pair(p2, s1), evo.pair(p2, s2))) + + assert(b:has_any(evo.pair(p1, evo.ANY))) + assert(b:has_any(evo.pair(p1, evo.ANY), evo.pair(evo.ANY, s1))) + assert(b:has_any(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, s1))) + assert(not b:has_any(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, p1))) + + assert(b:has_any(evo.pair(evo.ANY, evo.ANY))) + + assert(b:has_any( + evo.pair(p1, s1), + evo.pair(p1, s2), + evo.pair(evo.ANY, s1), + evo.pair(p1, evo.ANY), + evo.pair(evo.ANY, evo.ANY))) + + assert(not b:has_any( + evo.pair(p2, s1), + evo.pair(p2, s2), + evo.pair(p2, evo.ANY), + evo.pair(evo.ANY, p1), + evo.pair(evo.ANY, p2))) + + assert(b:has_any( + evo.pair(p2, s1), + evo.pair(p2, s2), + evo.pair(p2, evo.ANY), + evo.pair(evo.ANY, p1), + evo.pair(evo.ANY, p2), + evo.pair(p1, evo.ANY))) + end + + do + local b = evo.builder() + + b:set(evo.pair(p1, s1), 11) + b:set(evo.pair(p1, s2), 12) + b:set(evo.pair(p2, s1), 21) + b:set(evo.pair(p2, s2), 22) + + b:remove(evo.pair(p1, evo.ANY)) + b:remove(evo.pair(p1, evo.ANY)) + + b:remove(evo.pair(p2, evo.ANY)) + b:remove(evo.pair(p2, evo.ANY)) + + b:remove(evo.pair(evo.ANY, s1)) + b:remove(evo.pair(evo.ANY, s1)) + + b:remove(evo.pair(evo.ANY, s2)) + b:remove(evo.pair(evo.ANY, s2)) + + assert(not b:has(evo.pair(evo.ANY, evo.ANY))) + end +end + +do + local p, s = evo.id(2) + evo.set(p, evo.NAME, 'p') + evo.set(s, evo.NAME, 's') + assert(evo.name(evo.pair(p, s)) == '${p,s}') + assert(evo.name(evo.pair(evo.ANY, s)) == '${ANY,s}') + assert(evo.name(evo.pair(p, evo.ANY)) == '${p,ANY}') + assert(evo.name(evo.pair(evo.ANY, evo.ANY)) == '${ANY,ANY}') +end + +do + do + local p, s = evo.id(2) + assert(evo.alive(evo.pair(p, s))) + end + + do + local p, s = evo.id(2) + evo.destroy(p) + assert(not evo.alive(evo.pair(p, s))) + end + + do + local p, s = evo.id(2) + evo.destroy(s) + assert(not evo.alive(evo.pair(p, s))) + end + + do + local p, s = evo.id(2) + evo.destroy(p, s) + assert(not evo.alive(evo.pair(p, s))) + end +end + +do + do + local p1, p2, p3, s = evo.id(4) + local prefab = evo.builder() + :set(evo.pair(p1, s), 42) + :set(evo.pair(p2, s), 21) + :set(evo.pair(p3, s), 84) + :spawn() + local clone = evo.clone(prefab) + assert(evo.has(clone, evo.pair(p1, s)) and evo.get(clone, evo.pair(p1, s)) == 42) + assert(evo.has(clone, evo.pair(p2, s)) and evo.get(clone, evo.pair(p2, s)) == 21) + assert(evo.has(clone, evo.pair(p3, s)) and evo.get(clone, evo.pair(p3, s)) == 84) + end + + do + local p1, p2, p3, s = evo.id(4) + evo.set(p1, evo.UNIQUE) + evo.set(p2, evo.UNIQUE) + local prefab = evo.builder() + :set(evo.pair(p1, s), 42) + :set(evo.pair(p2, s), 21) + :set(evo.pair(p3, s), 84) + :spawn() + local clone = evo.clone(prefab) + assert(not evo.has(clone, evo.pair(p1, s)) and evo.get(clone, evo.pair(p1, s)) == nil) + assert(not evo.has(clone, evo.pair(p2, s)) and evo.get(clone, evo.pair(p2, s)) == nil) + assert(evo.has(clone, evo.pair(p3, s)) and evo.get(clone, evo.pair(p3, s)) == 84) + end +end + +do + ---@generic T1, T2 + ---@param first T1 + ---@param second T2 + ---@return T1 + ---@diagnostic disable-next-line: unused-local + local function fst(first, second) + return first + end + + ---@generic T1, T2 + ---@param first T1 + ---@param second T2 + ---@return T2 + ---@diagnostic disable-next-line: unused-local + local function snd(first, second) + return second + end + + do + local p, s1, s2, f = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + assert(fst(evo.primary(e, s1)) == p) + assert(snd(evo.primary(e, s1)) == 21) + assert(fst(evo.primary(e, s2)) == p) + assert(snd(evo.primary(e, s2)) == 42) + assert(fst(evo.primary(e, s1, 1)) == p) + assert(snd(evo.primary(e, s1, 1)) == 21) + assert(fst(evo.primary(e, s2, 1)) == p) + assert(snd(evo.primary(e, s2, 1)) == 42) + assert(fst(evo.primary(e, s1, 2)) == nil) + assert(snd(evo.primary(e, s1, 2)) == nil) + assert(fst(evo.primary(e, s2, 2)) == nil) + assert(snd(evo.primary(e, s2, 2)) == nil) + + assert(fst(evo.primary(evo.pair(e, f), s1)) == nil) + assert(fst(evo.primary(evo.pair(e, f), s2)) == nil) + assert(fst(evo.primary(evo.pair(e, f), s1, 1)) == nil) + assert(fst(evo.primary(evo.pair(e, f), s2, 1)) == nil) + assert(fst(evo.primary(evo.pair(e, f), s1, 2)) == nil) + assert(fst(evo.primary(evo.pair(e, f), s2, 2)) == nil) + + assert(fst(evo.secondary(e, p)) == s1) + assert(fst(evo.secondary(e, p, 1)) == s1) + assert(fst(evo.secondary(e, p, 2)) == s2) + assert(fst(evo.secondary(e, p, 3)) == nil) + + assert(fst(evo.secondary(evo.pair(e, f), p)) == nil) + assert(fst(evo.secondary(evo.pair(e, f), p, 1)) == nil) + assert(fst(evo.secondary(evo.pair(e, f), p, 2)) == nil) + assert(fst(evo.secondary(evo.pair(e, f), p, 3)) == nil) + + assert(fst(evo.primary(evo.pair(f, e), s1)) == nil) + assert(fst(evo.primary(evo.pair(f, e), s2)) == nil) + assert(fst(evo.secondary(evo.pair(f, e), p)) == nil) + end + + do + local p, s1, s2, f = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + assert(evo.primary_count(e, p) == 0) + assert(evo.primary_count(e, s1) == 1) + assert(evo.primary_count(e, s2) == 1) + assert(evo.secondary_count(e, p) == 2) + assert(evo.secondary_count(e, s1) == 0) + assert(evo.secondary_count(e, s2) == 0) + + assert(evo.primary_count(evo.pair(e, f), p) == 0) + assert(evo.primary_count(evo.pair(e, f), s1) == 0) + assert(evo.primary_count(evo.pair(e, f), s2) == 0) + assert(evo.secondary_count(evo.pair(e, f), p) == 0) + assert(evo.secondary_count(evo.pair(e, f), s1) == 0) + assert(evo.secondary_count(evo.pair(e, f), s2) == 0) + + assert(evo.primary_count(evo.pair(f, e), p) == 0) + assert(evo.primary_count(evo.pair(f, e), s1) == 0) + assert(evo.primary_count(evo.pair(f, e), s2) == 0) + assert(evo.secondary_count(evo.pair(f, e), p) == 0) + assert(evo.secondary_count(evo.pair(f, e), s1) == 0) + assert(evo.secondary_count(evo.pair(f, e), s2) == 0) + end + + do + local p, s1, s2, f = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + do + local iter, state = evo.primaries(e, p) + local fragment, component = iter(state) + assert(fragment == nil and component == nil) + + + iter, state = evo.primaries(evo.pair(e, f), p) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + + do + local iter, state = evo.primaries(e, s1) + local fragment, component = iter(state) + assert(fragment == p and component == 21) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + + iter, state = evo.primaries(evo.pair(e, f), s1) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + + do + local iter, state = evo.primaries(e, s2) + local fragment, component = iter(state) + assert(fragment == p and component == 42) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + + iter, state = evo.primaries(evo.pair(e, f), s2) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + end + + do + local p, s1, s2, f = evo.id(4) + + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + do + local iter, state = evo.secondaries(e, s1) + local fragment, component = iter(state) + assert(fragment == nil and component == nil) + + iter, state = evo.secondaries(evo.pair(e, f), s1) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + + do + local iter, state = evo.secondaries(e, p) + local fragment, component = iter(state) + assert(fragment == s1 and component == 21) + fragment, component = iter(state) + assert(fragment == s2 and component == 42) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + + iter, state = evo.secondaries(evo.pair(e, f), p) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + end + + do + local p, s1, s2 = evo.id(3) + + local e = evo.builder() + :set(evo.pair(p, s1), 21) + :set(evo.pair(p, s2), 42) + :spawn() + + do + local iter, state = evo.each(e) + local fragment, component = iter(state) + assert(fragment == evo.pair(p, s1) and component == 21) + fragment, component = iter(state) + assert(fragment == evo.pair(p, s2) and component == 42) + fragment, component = iter(state) + assert(fragment == nil and component == nil) + end + end +end + +do + do + local p, s1, s2 = evo.id(3) + + local e = evo.spawn { + [evo.pair(p, s1)] = 21, + [evo.pair(p, s2)] = 42, + } + + assert(evo.has(e, evo.pair(p, s1)) and evo.get(e, evo.pair(p, s1)) == 21) + assert(evo.has(e, evo.pair(p, s2)) and evo.get(e, evo.pair(p, s2)) == 42) + + evo.destroy(p) + + assert(not evo.has(e, evo.pair(p, s1)) and evo.get(e, evo.pair(p, s1)) == nil) + assert(not evo.has(e, evo.pair(p, s2)) and evo.get(e, evo.pair(p, s2)) == nil) + end + + do + local p, s1, s2 = evo.id(3) + + local e = evo.spawn { + [evo.pair(p, s1)] = 21, + [evo.pair(p, s2)] = 42, + } + + assert(evo.has(e, evo.pair(p, s1)) and evo.get(e, evo.pair(p, s1)) == 21) + assert(evo.has(e, evo.pair(p, s2)) and evo.get(e, evo.pair(p, s2)) == 42) + + evo.destroy(s1) + + assert(not evo.has(e, evo.pair(p, s1)) and evo.get(e, evo.pair(p, s1)) == nil) + assert(evo.has(e, evo.pair(p, s2)) and evo.get(e, evo.pair(p, s2)) == 42) + + evo.destroy(s2) + + assert(not evo.has(e, evo.pair(p, s1)) and evo.get(e, evo.pair(p, s1)) == nil) + assert(not evo.has(e, evo.pair(p, s2)) and evo.get(e, evo.pair(p, s2)) == nil) + end +end + +-- TODO +-- process evo.ANY as single wildcard +-- should we provide an evolved.pair type? +-- we should not add any wildcards to chunk `with` edges, lol +-- edges `without` to nil don't work because they are cannot be found diff --git a/evolved.lua b/evolved.lua index 636293a..9fc3ec7 100644 --- a/evolved.lua +++ b/evolved.lua @@ -28,6 +28,7 @@ local evolved = { } ---@class evolved.id +---@alias evolved.pair evolved.id ---@alias evolved.entity evolved.id ---@alias evolved.fragment evolved.id @@ -82,6 +83,20 @@ local evolved = { ---@field package [3] integer chunk_stack_size ---@field package [4] table? exclude_set +---@class (exact) evolved.primaries_state +---@field package [1] integer structural_changes +---@field package [2] evolved.chunk entity_chunk +---@field package [3] integer entity_place +---@field package [4] integer secondary_index +---@field package [5] integer secondary_fragment_index + +---@class (exact) evolved.secondaries_state +---@field package [1] integer structural_changes +---@field package [2] evolved.chunk entity_chunk +---@field package [3] integer entity_place +---@field package [4] integer primary_index +---@field package [5] integer primary_fragment_index + ---@alias evolved.each_iterator fun( --- state: evolved.each_state?): --- evolved.fragment?, evolved.component? @@ -90,6 +105,14 @@ local evolved = { --- state: evolved.execute_state?): --- evolved.chunk?, evolved.entity[]?, integer? +---@alias evolved.primaries_iterator fun( +--- state: evolved.primaries_state?): +--- evolved.fragment?, evolved.component? + +---@alias evolved.secondaries_iterator fun( +--- state: evolved.secondaries_state?): +--- evolved.fragment?, evolved.component? + --- --- --- @@ -98,13 +121,22 @@ local evolved = { --[=[------------------------------------------------------------------\ | |-------- OPTIONS --------|- SECONDARY -|-- PRIMARY --| - | IDENTIFIER'S | | | | - | ANATOMY | 12 bits | 20 bits | 20 bits | - | | | | | - |--------------|-------------------------|-------------|-------------| - | ID | RESERVED | version | index | + | IDENTIFIER'S | 12 bits | | | + | ANATOMY |--------|--------|-------| 20 bits | 20 bits | + | | 9 bits | 2 bits | 1 bit | | | + |--------------|--------|--------|-------|-------------|-------------| + | ID | RSVD | 00 | 0 | version | index | + | PAIR | RSVD | 00 | 1 | SEC index | PRI index | + | PRI WILDCARD | RSVD | 01 | 1 | SEC index | ANY index | + | SEC WILDCARD | RSVD | 10 | 1 | ANY index | PRI index | + | ANY WILDCARD | RSVD | 11 | 1 | ANY index | ANY index | \------------------------------------------------------------------]=] +local __PAIR_OPTIONS = 1 -- 0b001 +local __PRI_WILDCARD_OPTIONS = 3 -- 0b011 +local __SEC_WILDCARD_OPTIONS = 5 -- 0b101 +local __ANY_WILDCARD_OPTIONS = 7 -- 0b111 + --- --- --- @@ -159,6 +191,10 @@ local __group_subsystems = {} ---@type table ---@field package __component_storages evolved.storage[] ---@field package __component_fragments evolved.fragment[] +---@field package __pair_list evolved.pair[] +---@field package __pair_count integer +---@field package __primary_pairs table> +---@field package __secondary_pairs table> ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table ---@field package __unreachable_or_collected boolean @@ -166,6 +202,9 @@ local __group_subsystems = {} ---@type table +---@field package __primary_pairs? table> +---@field package __secondary_pairs? table> local __builder_mt = {} __builder_mt.__index = __builder_mt @@ -426,13 +467,15 @@ local __table_pool_tag = { system_list = 3, each_state = 4, execute_state = 5, - entity_set = 6, - entity_list = 7, - fragment_set = 8, - fragment_list = 9, - component_map = 10, - component_list = 11, - __count = 11, + primaries_state = 6, + secondaries_state = 7, + entity_set = 8, + entity_list = 9, + fragment_set = 10, + fragment_list = 11, + component_map = 12, + component_list = 13, + __count = 13, } ---@class (exact) evolved.table_pool @@ -541,6 +584,7 @@ end --- } local __assoc_list_new +local __assoc_list_dup local __assoc_list_move local __assoc_list_move_ex local __assoc_list_sort @@ -562,6 +606,31 @@ function __assoc_list_new(reserve) } end +---@generic K +---@param al evolved.assoc_list +---@return evolved.assoc_list +---@nodiscard +function __assoc_list_dup(al) + local al_item_list = al.__item_list + local al_item_count = al.__item_count + + local dup_item_set = __lua_table_new(0, al_item_count) + local dup_item_list = __lua_table_new(al_item_count, 0) + + for al_item_index = 1, al_item_count do + local al_item = al_item_list[al_item_index] + dup_item_set[al_item] = al_item_index + dup_item_list[al_item_index] = al_item + end + + ---@type evolved.assoc_list + return { + __item_set = dup_item_set, + __item_list = dup_item_list, + __item_count = al_item_count, + } +end + ---@generic K ---@param src_item_list K[] ---@param src_item_first integer @@ -711,6 +780,8 @@ end --- --- +local __ANY = __acquire_id() + local __TAG = __acquire_id() local __NAME = __acquire_id() @@ -751,6 +822,18 @@ local __DESTRUCTION_POLICY_REMOVE_FRAGMENT = __acquire_id() --- --- +local __ANY_INDEX = __ANY % 2 ^ 20 --[[@as integer]] + +local __ANY_WILDCARD = __ANY_INDEX + + __ANY_INDEX * 2 ^ 20 + + __ANY_WILDCARD_OPTIONS * 2 ^ 40 --[[@as evolved.pair]] + +--- +--- +--- +--- +--- + local __safe_tbls = { __EMPTY_FRAGMENT_SET = __lua_setmetatable({}, { __tostring = function() return 'empty fragment set' end, @@ -828,6 +911,21 @@ local __evolved_process local __evolved_debug_mode local __evolved_collect_garbage +local __evolved_pair +local __evolved_unpair + +local __evolved_is_pair +local __evolved_is_wildcard + +local __evolved_primary +local __evolved_secondary + +local __evolved_primaries +local __evolved_secondaries + +local __evolved_primary_count +local __evolved_secondary_count + local __evolved_chunk local __evolved_builder @@ -837,6 +935,14 @@ local __evolved_builder --- --- +local __primary_has +local __primary_has_all +local __primary_has_any +local __primary_get + +local __primary_wildcard +local __secondary_wildcard + local __universal_name local __component_storage @@ -865,20 +971,186 @@ local __fragment_required_fragments --- --- +---@param id evolved.id | evolved.pair +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function __primary_has(id, fragment) + local id_primary, _, id_options = __evolved_unpack(id) + + if id_options < __PAIR_OPTIONS then + if __freelist_ids[id_primary] ~= id then + return false + end + else + local id_primary_id = __freelist_ids[id_primary] --[[@as evolved.id?]] + if not id_primary_id or id_primary_id % 2 ^ 20 ~= id_primary then + return false + end + end + + local id_chunk = __entity_chunks[id_primary] + + return id_chunk and __chunk_has_fragment(id_chunk, fragment) or false +end + +---@param id evolved.id | evolved.pair +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __primary_has_all(id, ...) + local argument_count = select('#', ...) + + if argument_count == 0 then + return true + end + + local id_primary, _, id_options = __evolved_unpack(id) + + if id_options < __PAIR_OPTIONS then + if __freelist_ids[id_primary] ~= id then + return false + end + else + local id_primary_id = __freelist_ids[id_primary] --[[@as evolved.id?]] + if not id_primary_id or id_primary_id % 2 ^ 20 ~= id_primary then + return false + end + end + + local id_chunk = __entity_chunks[id_primary] + + return id_chunk and __chunk_has_all_fragments(id_chunk, ...) or false +end + +---@param id evolved.id | evolved.pair +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __primary_has_any(id, ...) + local argument_count = select('#', ...) + + if argument_count == 0 then + return false + end + + local id_primary, _, id_options = __evolved_unpack(id) + + if id_options < __PAIR_OPTIONS then + if __freelist_ids[id_primary] ~= id then + return false + end + else + local id_primary_id = __freelist_ids[id_primary] --[[@as evolved.id?]] + if not id_primary_id or id_primary_id % 2 ^ 20 ~= id_primary then + return false + end + end + + local id_chunk = __entity_chunks[id_primary] + + return id_chunk and __chunk_has_any_fragments(id_chunk, ...) or false +end + +---@param id evolved.id | evolved.pair +---@param ... evolved.fragment fragments +---@return evolved.component ... components +---@nodiscard +function __primary_get(id, ...) + local fragment_count = select('#', ...) + + if fragment_count == 0 then + return + end + + local id_primary, _, id_options = __evolved_unpack(id) + + if id_options < __PAIR_OPTIONS then + if __freelist_ids[id_primary] ~= id then + return + end + else + local id_primary_id = __freelist_ids[id_primary] --[[@as evolved.id?]] + if not id_primary_id or id_primary_id % 2 ^ 20 ~= id_primary then + return + end + end + + local id_chunk = __entity_chunks[id_primary] + + if not id_chunk then + return + end + + local id_place = __entity_places[id_primary] + return __chunk_get_components(id_chunk, id_place, ...) +end + +---@param secondary evolved.id | integer id or index +---@return evolved.pair (*, secondary) +---@nodiscard +function __primary_wildcard(secondary) + local primary_index = __ANY_INDEX + local secondary_index = secondary % 2 ^ 20 + + if secondary_index == __ANY_INDEX then + return __ANY_WILDCARD + end + + return primary_index + + secondary_index * 2 ^ 20 + + __PRI_WILDCARD_OPTIONS * 2 ^ 40 --[[@as evolved.pair]] +end + +---@param primary evolved.id | integer id or index +---@return evolved.pair (primary, *) +---@nodiscard +function __secondary_wildcard(primary) + local primary_index = primary % 2 ^ 20 + local secondary_index = __ANY_INDEX + + if primary_index == __ANY_INDEX then + return __ANY_WILDCARD + end + + return primary_index + + secondary_index * 2 ^ 20 + + __SEC_WILDCARD_OPTIONS * 2 ^ 40 --[[@as evolved.pair]] +end + ---@param id evolved.id ---@return string ---@nodiscard function __universal_name(id) - local id_primary, id_secondary = __evolved_unpack(id) + local id_primary, id_secondary, id_options = __evolved_unpack(id) - ---@type string? - local id_name = __evolved_get(id, __NAME) + if id_options < __PAIR_OPTIONS then + ---@type string? + local id_name = __evolved_get(id, __NAME) - if id_name then - return id_name + if id_name then + return id_name + end + else + ---@type string?, string? + local pair_primary_id_name, pair_secondary_id_name + + local pair_primary_id = __freelist_ids[id_primary] --[[@as evolved.id?]] + if pair_primary_id and pair_primary_id % 2 ^ 20 == id_primary then + pair_primary_id_name = __universal_name(pair_primary_id) + end + + local pair_secondary_id = __freelist_ids[id_secondary] --[[@as evolved.id?]] + if pair_secondary_id and pair_secondary_id % 2 ^ 20 == id_secondary then + pair_secondary_id_name = __universal_name(pair_secondary_id) + end + + if pair_primary_id_name and pair_secondary_id_name then + return __lua_string_format('${%s,%s}', pair_primary_id_name, pair_secondary_id_name) + end end - return __lua_string_format('$%d#%d:%d', id, id_primary, id_secondary) + return __lua_string_format('$%d#%d:%d:%d', id, id_primary, id_secondary, id_options) end ---@param fragment evolved.fragment @@ -927,7 +1199,7 @@ function __iterator_fns.__each_iterator(each_state) end ---@type evolved.execute_iterator -function __iterator_fns.__execute_iterator(execute_state) +function __iterator_fns.__execute_major_iterator(execute_state) if not execute_state then return end local structural_changes = execute_state[1] @@ -956,6 +1228,16 @@ function __iterator_fns.__execute_iterator(execute_state) (not chunk_child.__has_explicit_major) and (not exclude_set or not exclude_set[chunk_child_fragment]) + if is_chunk_child_matched and exclude_set and chunk_child.__has_pair_major then + local chunk_child_fragment_primary, chunk_child_fragment_secondary = + __evolved_unpack(chunk_child_fragment) + + is_chunk_child_matched = + not exclude_set[__ANY_WILDCARD] and + not exclude_set[__primary_wildcard(chunk_child_fragment_secondary)] and + not exclude_set[__secondary_wildcard(chunk_child_fragment_primary)] + end + if is_chunk_child_matched then chunk_stack_size = chunk_stack_size + 1 chunk_stack[chunk_stack_size] = chunk_child @@ -975,6 +1257,103 @@ function __iterator_fns.__execute_iterator(execute_state) __release_table(__table_pool_tag.execute_state, execute_state, true) end +---@type evolved.execute_iterator +function __iterator_fns.__execute_minor_iterator(execute_state) + if not execute_state then return end + + local structural_changes = execute_state[1] + local chunk_stack = execute_state[2] + local chunk_stack_size = execute_state[3] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + while chunk_stack_size > 0 do + local chunk = chunk_stack[chunk_stack_size] + + chunk_stack[chunk_stack_size] = nil + chunk_stack_size = chunk_stack_size - 1 + + local chunk_entity_list = chunk.__entity_list + local chunk_entity_count = chunk.__entity_count + + if chunk_entity_count > 0 then + execute_state[3] = chunk_stack_size + return chunk, chunk_entity_list, chunk_entity_count + end + end + + __release_table(__table_pool_tag.chunk_list, chunk_stack, true) + __release_table(__table_pool_tag.execute_state, execute_state, true) +end + +---@type evolved.primaries_iterator +function __iterator_fns.__primaries_iterator(primaries_state) + if not primaries_state then return end + + local structural_changes = primaries_state[1] + local entity_chunk = primaries_state[2] + local entity_place = primaries_state[3] + local secondary_index = primaries_state[4] + local secondary_fragment_index = primaries_state[5] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + local secondary_fragments = entity_chunk.__secondary_pairs[secondary_index] + local secondary_fragment_list = secondary_fragments and secondary_fragments.__item_list + local secondary_fragment_count = secondary_fragments and secondary_fragments.__item_count or 0 + + if secondary_fragment_index >= 1 and secondary_fragment_index <= secondary_fragment_count then + primaries_state[5] = secondary_fragment_index + 1 + + local secondary_fragment = secondary_fragment_list[secondary_fragment_index] + local primary, _ = __evolved_unpair(secondary_fragment) + + local component_index = entity_chunk.__component_indices[secondary_fragment] + local component_storage = entity_chunk.__component_storages[component_index] + + return primary, component_storage and component_storage[entity_place] + end + + __release_table(__table_pool_tag.primaries_state, primaries_state, true) +end + +---@type evolved.secondaries_iterator +function __iterator_fns.__secondaries_iterator(secondaries_state) + if not secondaries_state then return end + + local structural_changes = secondaries_state[1] + local entity_chunk = secondaries_state[2] + local entity_place = secondaries_state[3] + local primary_index = secondaries_state[4] + local primary_fragment_index = secondaries_state[5] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + local primary_fragments = entity_chunk.__primary_pairs[primary_index] + local primary_fragment_list = primary_fragments and primary_fragments.__item_list + local primary_fragment_count = primary_fragments and primary_fragments.__item_count or 0 + + if primary_fragment_index >= 1 and primary_fragment_index <= primary_fragment_count then + secondaries_state[5] = primary_fragment_index + 1 + + local primary_fragment = primary_fragment_list[primary_fragment_index] + local _, secondary = __evolved_unpair(primary_fragment) + + local component_index = entity_chunk.__component_indices[primary_fragment] + local component_storage = entity_chunk.__component_storages[component_index] + + return secondary, component_storage and component_storage[entity_place] + end + + __release_table(__table_pool_tag.secondaries_state, secondaries_state, true) +end + --- --- --- @@ -994,24 +1373,74 @@ local __update_major_chunks_trace ---@return evolved.chunk ---@nodiscard function __new_chunk(chunk_parent, chunk_fragment) - local chunk_fragment_primary, _ = __evolved_unpack(chunk_fragment) + local chunk_fragment_primary, chunk_fragment_secondary, chunk_fragment_options = + __evolved_unpack(chunk_fragment) - if __freelist_ids[chunk_fragment_primary] ~= chunk_fragment then - __error_fmt('the id (%s) is not alive and cannot be used for a new chunk', - __universal_name(chunk_fragment)) + if chunk_fragment_options < __PAIR_OPTIONS then + if chunk_fragment_primary == __ANY_INDEX then + __error_fmt('the id (%s) is a wildcard and cannot be used for a new chunk', + __universal_name(chunk_fragment)) + elseif __freelist_ids[chunk_fragment_primary] ~= chunk_fragment then + __error_fmt('the id (%s) is not alive and cannot be used for a new chunk', + __universal_name(chunk_fragment)) + end + else + if chunk_fragment_options >= __PRI_WILDCARD_OPTIONS then + __error_fmt('the pair (%s) is a wildcard and cannot be used for a new chunk', + __universal_name(chunk_fragment)) + end + + local fragment_primary_id = __freelist_ids[chunk_fragment_primary] --[[@as evolved.id?]] + if not fragment_primary_id or fragment_primary_id % 2 ^ 20 ~= chunk_fragment_primary then + __error_fmt('the pair (%s) has no alive primary id and cannot be used for a new chunk', + __universal_name(chunk_fragment)) + end + + local fragment_secondary_id = __freelist_ids[chunk_fragment_secondary] --[[@as evolved.id?]] + if not fragment_secondary_id or fragment_secondary_id % 2 ^ 20 ~= chunk_fragment_secondary then + __error_fmt('the pair (%s) has no alive secondary id and cannot be used for a new chunk', + __universal_name(chunk_fragment)) + end end local chunk_fragment_set = {} ---@type table local chunk_fragment_list = {} ---@type evolved.fragment[] local chunk_fragment_count = 0 ---@type integer - if chunk_parent then - local chunk_parent_fragment_list = chunk_parent.__fragment_list - local chunk_parent_fragment_count = chunk_parent.__fragment_count + local chunk_pair_list = {} ---@type evolved.pair[] + local chunk_pair_count = 0 ---@type integer - chunk_fragment_count = __assoc_list_move_ex( - chunk_parent_fragment_list, 1, chunk_parent_fragment_count, - chunk_fragment_set, chunk_fragment_list, chunk_fragment_count) + local chunk_primary_pairs = {} ---@type table> + local chunk_secondary_pairs = {} ---@type table> + + if chunk_parent then + do + local chunk_parent_fragment_list = chunk_parent.__fragment_list + local chunk_parent_fragment_count = chunk_parent.__fragment_count + + chunk_fragment_count = __assoc_list_move_ex( + chunk_parent_fragment_list, 1, chunk_parent_fragment_count, + chunk_fragment_set, chunk_fragment_list, chunk_fragment_count) + end + + do + local chunk_parent_pair_list = chunk_parent.__pair_list + local chunk_parent_pair_count = chunk_parent.__pair_count + + __lua_table_move( + chunk_parent_pair_list, 1, chunk_parent_pair_count, + chunk_pair_count + 1, chunk_pair_list) + + chunk_pair_count = chunk_pair_count + chunk_parent_pair_count + end + + for parent_primary_index, parent_primary_fragments in __lua_next, chunk_parent.__primary_pairs do + chunk_primary_pairs[parent_primary_index] = __assoc_list_dup(parent_primary_fragments) + end + + for parent_secondary_index, parent_secondary_fragments in __lua_next, chunk_parent.__secondary_pairs do + chunk_secondary_pairs[parent_secondary_index] = __assoc_list_dup(parent_secondary_fragments) + end end do @@ -1020,6 +1449,29 @@ function __new_chunk(chunk_parent, chunk_fragment) chunk_fragment_list[chunk_fragment_count] = chunk_fragment end + if chunk_fragment >= __PAIR_OPTIONS * 2 ^ 40 then + chunk_pair_count = chunk_pair_count + 1 + chunk_pair_list[chunk_pair_count] = chunk_fragment + + local chunk_primary_fragments = chunk_primary_pairs[chunk_fragment_primary] + local chunk_secondary_fragments = chunk_secondary_pairs[chunk_fragment_secondary] + + if not chunk_primary_fragments then + ---@type evolved.assoc_list + chunk_primary_fragments = __assoc_list_new(1) + chunk_primary_pairs[chunk_fragment_primary] = chunk_primary_fragments + end + + if not chunk_secondary_fragments then + ---@type evolved.assoc_list + chunk_secondary_fragments = __assoc_list_new(1) + chunk_secondary_pairs[chunk_fragment_secondary] = chunk_secondary_fragments + end + + __assoc_list_insert(chunk_primary_fragments, chunk_fragment) + __assoc_list_insert(chunk_secondary_fragments, chunk_fragment) + end + ---@type evolved.chunk local chunk = __lua_setmetatable({ __parent = nil, @@ -1036,6 +1488,10 @@ function __new_chunk(chunk_parent, chunk_fragment) __component_indices = {}, __component_storages = {}, __component_fragments = {}, + __pair_list = chunk_pair_list, + __pair_count = chunk_pair_count, + __primary_pairs = chunk_primary_pairs, + __secondary_pairs = chunk_secondary_pairs, __with_fragment_edges = {}, __without_fragment_edges = {}, __unreachable_or_collected = false, @@ -1043,6 +1499,9 @@ function __new_chunk(chunk_parent, chunk_fragment) __has_assign_hooks = false, __has_insert_hooks = false, __has_remove_hooks = false, + __has_pair_major = false, + __has_pair_minors = false, + __has_pair_fragments = false, __has_unique_major = false, __has_unique_minors = false, __has_unique_fragments = false, @@ -1100,6 +1559,94 @@ function __new_chunk(chunk_parent, chunk_fragment) __assoc_list_insert(minor_chunks, chunk) end + if chunk_fragment >= __PAIR_OPTIONS * 2 ^ 40 then + local major = chunk_fragment + local major_primary, major_secondary = __evolved_unpack(major) + + do + local major_wildcard = __ANY_WILDCARD + local major_wildcard_chunks = __major_chunks[major_wildcard] + + if not major_wildcard_chunks then + ---@type evolved.assoc_list + major_wildcard_chunks = __assoc_list_new(4) + __major_chunks[major_wildcard] = major_wildcard_chunks + end + + __assoc_list_insert(major_wildcard_chunks, chunk) + end + + do + local major_wildcard = __secondary_wildcard(major_primary) + local major_wildcard_chunks = __major_chunks[major_wildcard] + + if not major_wildcard_chunks then + ---@type evolved.assoc_list + major_wildcard_chunks = __assoc_list_new(4) + __major_chunks[major_wildcard] = major_wildcard_chunks + end + + __assoc_list_insert(major_wildcard_chunks, chunk) + end + + do + local major_wildcard = __primary_wildcard(major_secondary) + local major_wildcard_chunks = __major_chunks[major_wildcard] + + if not major_wildcard_chunks then + ---@type evolved.assoc_list + major_wildcard_chunks = __assoc_list_new(4) + __major_chunks[major_wildcard] = major_wildcard_chunks + end + + __assoc_list_insert(major_wildcard_chunks, chunk) + end + end + + if chunk_pair_count > 0 then + local minor_wildcard = __ANY_WILDCARD + local minor_wildcard_chunks = __minor_chunks[minor_wildcard] + + if not minor_wildcard_chunks then + ---@type evolved.assoc_list + minor_wildcard_chunks = __assoc_list_new(4) + __minor_chunks[minor_wildcard] = minor_wildcard_chunks + end + + __assoc_list_insert(minor_wildcard_chunks, chunk) + end + + for i = 1, chunk_pair_count do + local minor = chunk_pair_list[i] + local minor_primary, minor_secondary = __evolved_unpack(minor) + + do + local minor_wildcard = __secondary_wildcard(minor_primary) + local minor_wildcard_chunks = __minor_chunks[minor_wildcard] + + if not minor_wildcard_chunks then + ---@type evolved.assoc_list + minor_wildcard_chunks = __assoc_list_new(4) + __minor_chunks[minor_wildcard] = minor_wildcard_chunks + end + + __assoc_list_insert(minor_wildcard_chunks, chunk) + end + + do + local minor_wildcard = __primary_wildcard(minor_secondary) + local minor_wildcard_chunks = __minor_chunks[minor_wildcard] + + if not minor_wildcard_chunks then + ---@type evolved.assoc_list + minor_wildcard_chunks = __assoc_list_new(4) + __minor_chunks[minor_wildcard] = minor_wildcard_chunks + end + + __assoc_list_insert(minor_wildcard_chunks, chunk) + end + end + __update_chunk_tags(chunk) __update_chunk_flags(chunk) @@ -1120,7 +1667,7 @@ function __update_chunk_tags(chunk) local fragment = fragment_list[i] local component_index = component_indices[fragment] - if component_index and __evolved_has(fragment, __TAG) then + if component_index and __primary_has(fragment, __TAG) then if component_index ~= component_count then local last_component_storage = component_storages[component_count] local last_component_fragment = component_fragments[component_count] @@ -1137,7 +1684,7 @@ function __update_chunk_tags(chunk) chunk.__component_count = component_count end - if not component_index and not __evolved_has(fragment, __TAG) then + if not component_index and not __primary_has(fragment, __TAG) then component_count = component_count + 1 chunk.__component_count = component_count @@ -1150,7 +1697,7 @@ function __update_chunk_tags(chunk) ---@type evolved.default?, evolved.duplicate? local fragment_default, fragment_duplicate = - __evolved_get(fragment, __DEFAULT, __DUPLICATE) + __primary_get(fragment, __DEFAULT, __DUPLICATE) if fragment_duplicate then for place = 1, chunk.__entity_count do @@ -1176,37 +1723,45 @@ function __update_chunk_flags(chunk) local chunk_fragment = chunk.__fragment local has_setup_hooks = (chunk_parent ~= nil and chunk_parent.__has_setup_hooks) - or __evolved_has_any(chunk_fragment, __DEFAULT, __DUPLICATE) + or __primary_has_any(chunk_fragment, __DEFAULT, __DUPLICATE) local has_assign_hooks = (chunk_parent ~= nil and chunk_parent.__has_assign_hooks) - or __evolved_has_any(chunk_fragment, __ON_SET, __ON_ASSIGN) + or __primary_has_any(chunk_fragment, __ON_SET, __ON_ASSIGN) local has_insert_hooks = (chunk_parent ~= nil and chunk_parent.__has_insert_hooks) - or __evolved_has_any(chunk_fragment, __ON_SET, __ON_INSERT) + or __primary_has_any(chunk_fragment, __ON_SET, __ON_INSERT) local has_remove_hooks = (chunk_parent ~= nil and chunk_parent.__has_remove_hooks) - or __evolved_has(chunk_fragment, __ON_REMOVE) + or __primary_has(chunk_fragment, __ON_REMOVE) - local has_unique_major = __evolved_has(chunk_fragment, __UNIQUE) + local has_pair_major = chunk_fragment >= __PAIR_OPTIONS * 2 ^ 40 + local has_pair_minors = chunk_parent ~= nil and chunk_parent.__has_pair_fragments + local has_pair_fragments = has_pair_major or has_pair_minors + + local has_unique_major = __primary_has(chunk_fragment, __UNIQUE) local has_unique_minors = chunk_parent ~= nil and chunk_parent.__has_unique_fragments local has_unique_fragments = has_unique_major or has_unique_minors - local has_explicit_major = __evolved_has(chunk_fragment, __EXPLICIT) + local has_explicit_major = __primary_has(chunk_fragment, __EXPLICIT) local has_explicit_minors = chunk_parent ~= nil and chunk_parent.__has_explicit_fragments local has_explicit_fragments = has_explicit_major or has_explicit_minors - local has_internal_major = __evolved_has(chunk_fragment, __INTERNAL) + local has_internal_major = __primary_has(chunk_fragment, __INTERNAL) local has_internal_minors = chunk_parent ~= nil and chunk_parent.__has_internal_fragments local has_internal_fragments = has_internal_major or has_internal_minors local has_required_fragments = (chunk_parent ~= nil and chunk_parent.__has_required_fragments) - or __evolved_has(chunk_fragment, __REQUIRES) + or __primary_has(chunk_fragment, __REQUIRES) chunk.__has_setup_hooks = has_setup_hooks chunk.__has_assign_hooks = has_assign_hooks chunk.__has_insert_hooks = has_insert_hooks chunk.__has_remove_hooks = has_remove_hooks + chunk.__has_pair_major = has_pair_major + chunk.__has_pair_minors = has_pair_minors + chunk.__has_pair_fragments = has_pair_fragments + chunk.__has_unique_major = has_unique_major chunk.__has_unique_minors = has_unique_minors chunk.__has_unique_fragments = has_unique_fragments @@ -1226,6 +1781,10 @@ end ---@param trace fun(chunk: evolved.chunk, ...: any) ---@param ... any additional trace arguments function __trace_major_chunks(major, trace, ...) + if major >= __PAIR_OPTIONS * 2 ^ 40 then + __error_fmt('trace operations on pair fragments are not supported') + end + ---@type evolved.chunk[] local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 @@ -1244,6 +1803,34 @@ function __trace_major_chunks(major, trace, ...) end end + do + local major_chunks = __major_chunks[__primary_wildcard(major)] + local major_chunk_list = major_chunks and major_chunks.__item_list + local major_chunk_count = major_chunks and major_chunks.__item_count or 0 + + if major_chunk_count > 0 then + __lua_table_move( + major_chunk_list, 1, major_chunk_count, + chunk_stack_size + 1, chunk_stack) + + chunk_stack_size = chunk_stack_size + major_chunk_count + end + end + + do + local major_chunks = __major_chunks[__secondary_wildcard(major)] + local major_chunk_list = major_chunks and major_chunks.__item_list + local major_chunk_count = major_chunks and major_chunks.__item_count or 0 + + if major_chunk_count > 0 then + __lua_table_move( + major_chunk_list, 1, major_chunk_count, + chunk_stack_size + 1, chunk_stack) + + chunk_stack_size = chunk_stack_size + major_chunk_count + end + end + while chunk_stack_size > 0 do local chunk = chunk_stack[chunk_stack_size] @@ -1271,6 +1858,10 @@ end ---@param trace fun(chunk: evolved.chunk, ...: any) ---@param ... any additional trace arguments function __trace_minor_chunks(minor, trace, ...) + if minor >= __PAIR_OPTIONS * 2 ^ 40 then + __error_fmt('trace operations on pair fragments are not supported') + end + ---@type evolved.chunk[] local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 @@ -1289,6 +1880,34 @@ function __trace_minor_chunks(minor, trace, ...) end end + do + local minor_chunks = __minor_chunks[__primary_wildcard(minor)] + local minor_chunk_list = minor_chunks and minor_chunks.__item_list + local minor_chunk_count = minor_chunks and minor_chunks.__item_count or 0 + + if minor_chunk_count > 0 then + __lua_table_move( + minor_chunk_list, 1, minor_chunk_count, + chunk_stack_size + 1, chunk_stack) + + chunk_stack_size = chunk_stack_size + minor_chunk_count + end + end + + do + local minor_chunks = __minor_chunks[__secondary_wildcard(minor)] + local minor_chunk_list = minor_chunks and minor_chunks.__item_list + local minor_chunk_count = minor_chunks and minor_chunks.__item_count or 0 + + if minor_chunk_count > 0 then + __lua_table_move( + minor_chunk_list, 1, minor_chunk_count, + chunk_stack_size + 1, chunk_stack) + + chunk_stack_size = chunk_stack_size + minor_chunk_count + end + end + while chunk_stack_size > 0 do local chunk = chunk_stack[chunk_stack_size] @@ -1395,6 +2014,96 @@ function __chunk_without_fragment(chunk, fragment) if without_fragment_edge then return without_fragment_edge end end + if chunk.__has_pair_fragments and fragment >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 then + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) + + if fragment_options == __ANY_WILDCARD_OPTIONS then + while chunk and chunk.__has_pair_major do + chunk = chunk.__parent + end + + if not chunk or not chunk.__has_pair_fragments then + return chunk + end + + local sib_chunk = chunk.__parent + + while sib_chunk and sib_chunk.__has_pair_fragments do + sib_chunk = sib_chunk.__parent + end + + if sib_chunk then + chunk.__without_fragment_edges[fragment] = sib_chunk + sib_chunk.__with_fragment_edges[fragment] = chunk + end + + return sib_chunk + elseif fragment_options == __PRI_WILDCARD_OPTIONS then + if not chunk.__secondary_pairs[fragment_secondary] then + -- the chunk does not have such pairs + return chunk + end + + local sib_chunk = chunk.__parent + + while sib_chunk and sib_chunk.__has_pair_fragments and sib_chunk.__secondary_pairs[fragment_secondary] do + sib_chunk = sib_chunk.__parent + end + + local ini_pair_list = chunk.__pair_list + local ini_pair_count = chunk.__pair_count + + local lst_pair_index = sib_chunk and sib_chunk.__pair_count + 2 or 2 + + for ini_pair_index = lst_pair_index, ini_pair_count do + local ini_pair = ini_pair_list[ini_pair_index] + local _, ini_pair_secondary = __evolved_unpack(ini_pair) + if ini_pair_secondary ~= fragment_secondary then + sib_chunk = __chunk_with_fragment(sib_chunk, ini_pair) + end + end + + if sib_chunk then + chunk.__without_fragment_edges[fragment] = sib_chunk + sib_chunk.__with_fragment_edges[fragment] = chunk + end + + return sib_chunk + elseif fragment_options == __SEC_WILDCARD_OPTIONS then + if not chunk.__primary_pairs[fragment_primary] then + -- the chunk does not have such pairs + return chunk + end + + local sib_chunk = chunk.__parent + + while sib_chunk and sib_chunk.__has_pair_fragments and sib_chunk.__primary_pairs[fragment_primary] do + sib_chunk = sib_chunk.__parent + end + + local ini_pair_list = chunk.__pair_list + local ini_pair_count = chunk.__pair_count + + local lst_pair_index = sib_chunk and sib_chunk.__pair_count + 2 or 2 + + for ini_pair_index = lst_pair_index, ini_pair_count do + local ini_pair = ini_pair_list[ini_pair_index] + local ini_pair_primary, _ = __evolved_unpack(ini_pair) + if ini_pair_primary ~= fragment_primary then + sib_chunk = __chunk_with_fragment(sib_chunk, ini_pair) + end + end + + if sib_chunk then + chunk.__without_fragment_edges[fragment] = sib_chunk + sib_chunk.__with_fragment_edges[fragment] = chunk + end + + return sib_chunk + end + end + if fragment > chunk.__fragment or not chunk.__fragment_set[fragment] then return chunk end @@ -1474,7 +2183,7 @@ function __chunk_without_unique_fragments(chunk) for ini_fragment_index = lst_fragment_index, ini_fragment_count do local ini_fragment = ini_fragment_list[ini_fragment_index] - if not __evolved_has(ini_fragment, __UNIQUE) then + if not __primary_has(ini_fragment, __UNIQUE) then sib_chunk = __chunk_with_fragment(sib_chunk, ini_fragment) end end @@ -1542,6 +2251,21 @@ function __chunk_has_fragment(chunk, fragment) return true end + if chunk.__has_pair_fragments and fragment >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 then + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) + + if fragment_options == __ANY_WILDCARD_OPTIONS then + return true + elseif fragment_options == __PRI_WILDCARD_OPTIONS then + local secondary_fragments = chunk.__secondary_pairs[fragment_secondary] + return secondary_fragments and secondary_fragments.__item_count > 0 + elseif fragment_options == __SEC_WILDCARD_OPTIONS then + local primary_fragments = chunk.__primary_pairs[fragment_primary] + return primary_fragments and primary_fragments.__item_count > 0 + end + end + return false end @@ -1558,30 +2282,41 @@ function __chunk_has_all_fragments(chunk, ...) local fs = chunk.__fragment_set + local has_f = __chunk_has_fragment + local has_fs = __chunk_has_all_fragments + + local has_p = chunk.__has_pair_fragments + if fragment_count == 1 then local f1 = ... - return fs[f1] ~= nil + return (has_p and has_f(chunk, f1)) + or (not has_p and fs[f1] ~= nil) end if fragment_count == 2 then local f1, f2 = ... - return fs[f1] ~= nil and fs[f2] ~= nil + return (has_p and has_f(chunk, f1) and has_f(chunk, f2)) + or (not has_p and fs[f1] ~= nil and fs[f2] ~= nil) end if fragment_count == 3 then local f1, f2, f3 = ... - return fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil + return (has_p and has_f(chunk, f1) and has_f(chunk, f2) and has_f(chunk, f3)) + or (not has_p and fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil) end if fragment_count == 4 then local f1, f2, f3, f4 = ... - return fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil and fs[f4] ~= nil + return (has_p and has_f(chunk, f1) and has_f(chunk, f2) and has_f(chunk, f3) and has_f(chunk, f4)) + or (not has_p and fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil and fs[f4] ~= nil) end do local f1, f2, f3, f4 = ... - return fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil and fs[f4] ~= nil - and __chunk_has_all_fragments(chunk, __lua_select(5, ...)) + return (has_p and has_f(chunk, f1) and has_f(chunk, f2) and has_f(chunk, f3) and has_f(chunk, f4) + and has_fs(chunk, __lua_select(5, ...))) + or (not has_p and fs[f1] ~= nil and fs[f2] ~= nil and fs[f3] ~= nil and fs[f4] ~= nil + and has_fs(chunk, __lua_select(5, ...))) end end @@ -1597,10 +2332,22 @@ function __chunk_has_all_fragment_list(chunk, fragment_list, fragment_count) local fs = chunk.__fragment_set - for i = 1, fragment_count do - local f = fragment_list[i] - if fs[f] == nil then - return false + local has_f = __chunk_has_fragment + local has_p = chunk.__has_pair_fragments + + if has_p then + for i = 1, fragment_count do + local f = fragment_list[i] + if not has_f(chunk, f) then + return false + end + end + else + for i = 1, fragment_count do + local f = fragment_list[i] + if fs[f] == nil then + return false + end end end @@ -1620,30 +2367,41 @@ function __chunk_has_any_fragments(chunk, ...) local fs = chunk.__fragment_set + local has_f = __chunk_has_fragment + local has_fs = __chunk_has_any_fragments + + local has_p = chunk.__has_pair_fragments + if fragment_count == 1 then local f1 = ... - return fs[f1] ~= nil + return (has_p and has_f(chunk, f1)) + or (not has_p and fs[f1] ~= nil) end if fragment_count == 2 then local f1, f2 = ... - return fs[f1] ~= nil or fs[f2] ~= nil + return (has_p and (has_f(chunk, f1) or has_f(chunk, f2))) + or (not has_p and (fs[f1] ~= nil or fs[f2] ~= nil)) end if fragment_count == 3 then local f1, f2, f3 = ... - return fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil + return (has_p and (has_f(chunk, f1) or has_f(chunk, f2) or has_f(chunk, f3))) + or (not has_p and (fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil)) end if fragment_count == 4 then local f1, f2, f3, f4 = ... - return fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil or fs[f4] ~= nil + return (has_p and (has_f(chunk, f1) or has_f(chunk, f2) or has_f(chunk, f3) or has_f(chunk, f4))) + or (not has_p and (fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil or fs[f4] ~= nil)) end do local f1, f2, f3, f4 = ... - return fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil or fs[f4] ~= nil - or __chunk_has_any_fragments(chunk, __lua_select(5, ...)) + return (has_p and (has_f(chunk, f1) or has_f(chunk, f2) or has_f(chunk, f3) or has_f(chunk, f4) + or has_fs(chunk, __lua_select(5, ...)))) + or (not has_p and (fs[f1] ~= nil or fs[f2] ~= nil or fs[f3] ~= nil or fs[f4] ~= nil + or has_fs(chunk, __lua_select(5, ...)))) end end @@ -1659,10 +2417,22 @@ function __chunk_has_any_fragment_list(chunk, fragment_list, fragment_count) local fs = chunk.__fragment_set - for i = 1, fragment_count do - local f = fragment_list[i] - if fs[f] ~= nil then - return true + local has_f = __chunk_has_fragment + local has_p = chunk.__has_pair_fragments + + if has_p then + for i = 1, fragment_count do + local f = fragment_list[i] + if has_f(chunk, f) then + return true + end + end + else + for i = 1, fragment_count do + local f = fragment_list[i] + if fs[f] ~= nil then + return true + end end end @@ -1764,6 +2534,10 @@ function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_list, fragment_stack[fragment_stack_size] = nil fragment_stack_size = fragment_stack_size - 1 + if stack_fragment >= __PAIR_OPTIONS * 2 ^ 40 then + stack_fragment = __evolved_unpair(stack_fragment) + end + local fragment_requires = __sorted_requires[stack_fragment] local fragment_require_list = fragment_requires and fragment_requires.__item_list local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 @@ -1810,6 +2584,10 @@ function __fragment_required_fragments(fragment, req_fragment_set, req_fragment_ fragment_stack[fragment_stack_size] = nil fragment_stack_size = fragment_stack_size - 1 + if stack_fragment >= __PAIR_OPTIONS * 2 ^ 40 then + stack_fragment = __evolved_unpair(stack_fragment) + end + local fragment_requires = __sorted_requires[stack_fragment] local fragment_require_list = fragment_requires and fragment_requires.__item_list local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 @@ -1975,7 +2753,7 @@ local function __spawn_entity(entity, components) if component_index then ---@type evolved.duplicate? local fragment_duplicate = - __evolved_get(fragment, __DUPLICATE) + __primary_get(fragment, __DUPLICATE) local new_component = component @@ -2004,7 +2782,7 @@ local function __spawn_entity(entity, components) if req_component_index then ---@type evolved.default?, evolved.duplicate? local req_fragment_default, req_fragment_duplicate = - __evolved_get(req_fragment, __DEFAULT, __DUPLICATE) + __primary_get(req_fragment, __DEFAULT, __DUPLICATE) local req_component = req_fragment_default @@ -2063,7 +2841,7 @@ local function __spawn_entity(entity, components) ---@type evolved.set_hook?, evolved.insert_hook? local fragment_on_set, fragment_on_insert = - __evolved_get(fragment, __ON_SET, __ON_INSERT) + __primary_get(fragment, __ON_SET, __ON_INSERT) local component_index = chunk_component_indices[fragment] @@ -2176,7 +2954,7 @@ local function __clone_entity(entity, prefab, components) if component_index then ---@type evolved.duplicate? local fragment_duplicate = - __evolved_get(fragment, __DUPLICATE) + __primary_get(fragment, __DUPLICATE) local prefab_component_storage = prefab_component_storages[prefab_component_index] local prefab_component = prefab_component_storage[prefab_place] @@ -2226,7 +3004,7 @@ local function __clone_entity(entity, prefab, components) if component_index then ---@type evolved.duplicate? local fragment_duplicate = - __evolved_get(fragment, __DUPLICATE) + __primary_get(fragment, __DUPLICATE) local new_component = component @@ -2255,7 +3033,7 @@ local function __clone_entity(entity, prefab, components) if req_component_index then ---@type evolved.default?, evolved.duplicate? local req_fragment_default, req_fragment_duplicate = - __evolved_get(req_fragment, __DEFAULT, __DUPLICATE) + __primary_get(req_fragment, __DEFAULT, __DUPLICATE) local req_component = req_fragment_default @@ -2314,7 +3092,7 @@ local function __clone_entity(entity, prefab, components) ---@type evolved.set_hook?, evolved.insert_hook? local fragment_on_set, fragment_on_insert = - __evolved_get(fragment, __ON_SET, __ON_INSERT) + __primary_get(fragment, __ON_SET, __ON_INSERT) local component_index = chunk_component_indices[fragment] @@ -2412,6 +3190,51 @@ local function __purge_chunk(chunk) end end + if chunk.__has_pair_fragments then + local wildcard = __ANY_WILDCARD + + local major_wildcard_chunks = __major_chunks[wildcard] + local minor_wildcard_chunks = __minor_chunks[wildcard] + + if major_wildcard_chunks and __assoc_list_remove(major_wildcard_chunks, chunk) == 0 then + __major_chunks[wildcard] = nil + end + + if minor_wildcard_chunks and __assoc_list_remove(minor_wildcard_chunks, chunk) == 0 then + __minor_chunks[wildcard] = nil + end + end + + for primary_index in __lua_next, chunk.__primary_pairs do + local secondary_wildcard = __secondary_wildcard(primary_index) + + local major_wildcard_chunks = __major_chunks[secondary_wildcard] + local minor_wildcard_chunks = __minor_chunks[secondary_wildcard] + + if major_wildcard_chunks and __assoc_list_remove(major_wildcard_chunks, chunk) == 0 then + __major_chunks[secondary_wildcard] = nil + end + + if minor_wildcard_chunks and __assoc_list_remove(minor_wildcard_chunks, chunk) == 0 then + __minor_chunks[secondary_wildcard] = nil + end + end + + for secondary_index in __lua_next, chunk.__secondary_pairs do + local primary_wildcard = __primary_wildcard(secondary_index) + + local major_wildcard_chunks = __major_chunks[primary_wildcard] + local minor_wildcard_chunks = __minor_chunks[primary_wildcard] + + if major_wildcard_chunks and __assoc_list_remove(major_wildcard_chunks, chunk) == 0 then + __major_chunks[primary_wildcard] = nil + end + + if minor_wildcard_chunks and __assoc_list_remove(minor_wildcard_chunks, chunk) == 0 then + __minor_chunks[primary_wildcard] = nil + end + end + if chunk_parent then chunk.__parent, chunk_parent.__child_count = nil, __assoc_list_remove_ex( chunk_parent.__child_set, chunk_parent.__child_list, chunk_parent.__child_count, @@ -2479,7 +3302,7 @@ local function __destroy_entity_list(entity_list, entity_count) local fragment = chunk_fragment_list[chunk_fragment_index] ---@type evolved.remove_hook? - local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE) + local fragment_on_remove = __primary_get(fragment, __ON_REMOVE) if fragment_on_remove then local component_index = chunk_component_indices[fragment] @@ -2555,7 +3378,7 @@ local function __destroy_fragment_list(fragment_list, fragment_count) releasing_fragment_count = releasing_fragment_count + 1 releasing_fragment_list[releasing_fragment_count] = processing_fragment - local processing_fragment_destruction_policy = __evolved_get(processing_fragment, __DESTRUCTION_POLICY) + local processing_fragment_destruction_policy = __primary_get(processing_fragment, __DESTRUCTION_POLICY) or __DESTRUCTION_POLICY_REMOVE_FRAGMENT if processing_fragment_destruction_policy == __DESTRUCTION_POLICY_DESTROY_ENTITY then @@ -2601,7 +3424,10 @@ local function __destroy_fragment_list(fragment_list, fragment_count) for i = 1, remove_fragment_policy_fragment_count do local fragment = remove_fragment_policy_fragment_list[i] - __trace_minor_chunks(fragment, __chunk_remove, fragment) + __trace_minor_chunks(fragment, __chunk_remove, + fragment, + __primary_wildcard(fragment), + __secondary_wildcard(fragment)) end __release_table(__table_pool_tag.fragment_list, remove_fragment_policy_fragment_list) @@ -2658,7 +3484,7 @@ function __chunk_set(old_chunk, fragment, component) if old_chunk_has_setup_hooks or old_chunk_has_assign_hooks then fragment_default, fragment_duplicate, fragment_on_set, fragment_on_assign = - __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_ASSIGN) + __primary_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_ASSIGN) end if fragment_on_set or fragment_on_assign then @@ -2784,7 +3610,7 @@ function __chunk_set(old_chunk, fragment, component) if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then fragment_default, fragment_duplicate, fragment_on_set, fragment_on_insert = - __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + __primary_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end if new_entity_count == 0 then @@ -2927,7 +3753,7 @@ function __chunk_set(old_chunk, fragment, component) if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert = - __evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + __primary_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end if req_fragment_on_set or req_fragment_on_insert then @@ -3064,7 +3890,7 @@ function __chunk_remove(old_chunk, ...) if not new_fragment_set[fragment] then ---@type evolved.remove_hook? - local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE) + local fragment_on_remove = __primary_get(fragment, __ON_REMOVE) if fragment_on_remove then local old_component_index = old_component_indices[fragment] @@ -3180,7 +4006,7 @@ function __chunk_clear(chunk) local fragment = chunk_fragment_list[chunk_fragment_index] ---@type evolved.remove_hook? - local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE) + local fragment_on_remove = __primary_get(fragment, __ON_REMOVE) if fragment_on_remove then local component_index = chunk_component_indices[fragment] @@ -4033,10 +4859,12 @@ end ---@param id evolved.id ---@return integer primary ---@return integer secondary +---@return integer options ---@nodiscard function __evolved_unpack(id) return id % 2 ^ 20, - (id - id % 2 ^ 20) / 2 ^ 20 % 2 ^ 20 + (id - id % 2 ^ 20) / 2 ^ 20 % 2 ^ 20, + (id - id % 2 ^ 40) / 2 ^ 40 % 2 ^ 12 end ---@return boolean started @@ -4149,10 +4977,22 @@ end ---@return boolean ---@nodiscard function __evolved_alive(entity) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, entity_secondary, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then - return false + if entity_options < __PAIR_OPTIONS then + if __freelist_ids[entity_primary] ~= entity then + return false + end + else + local entity_primary_id = __freelist_ids[entity_primary] --[[@as evolved.id?]] + if not entity_primary_id or entity_primary_id % 2 ^ 20 ~= entity_primary then + return false + end + + local entity_secondary_id = __freelist_ids[entity_secondary] --[[@as evolved.id?]] + if not entity_secondary_id or entity_secondary_id % 2 ^ 20 ~= entity_secondary then + return false + end end return true @@ -4204,7 +5044,11 @@ end ---@return boolean ---@nodiscard function __evolved_empty(entity) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return true + end if __freelist_ids[entity_primary] ~= entity then return true @@ -4260,7 +5104,11 @@ end ---@return boolean ---@nodiscard function __evolved_has(entity, fragment) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return false + end if __freelist_ids[entity_primary] ~= entity then return false @@ -4282,7 +5130,11 @@ function __evolved_has_all(entity, ...) return true end - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return false + end if __freelist_ids[entity_primary] ~= entity then return false @@ -4304,7 +5156,11 @@ function __evolved_has_any(entity, ...) return false end - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return false + end if __freelist_ids[entity_primary] ~= entity then return false @@ -4320,7 +5176,11 @@ end ---@return evolved.component ... components ---@nodiscard function __evolved_get(entity, ...) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return + end if __freelist_ids[entity_primary] ~= entity then return @@ -4340,19 +5200,45 @@ end ---@param fragment evolved.fragment ---@param component evolved.component function __evolved_set(entity, fragment, component) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then + if entity_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be changed', + __universal_name(entity)) + elseif __freelist_ids[entity_primary] ~= entity then __error_fmt('the id (%s) is not alive and cannot be changed', __universal_name(entity)) end - local fragment_primary, _ = __evolved_unpack(fragment) + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) if __debug_mode then - if __freelist_ids[fragment_primary] ~= fragment then - __error_fmt('the id (%s) is not alive and cannot be set', - __universal_name(fragment)) + if fragment_options < __PAIR_OPTIONS then + if fragment_primary == __ANY_INDEX then + __error_fmt('the id (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + elseif __freelist_ids[fragment_primary] ~= fragment then + __error_fmt('the id (%s) is not alive and cannot be set', + __universal_name(fragment)) + end + else + if fragment_options >= __PRI_WILDCARD_OPTIONS then + __error_fmt('the pair (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + end + + local fragment_primary_id = __freelist_ids[fragment_primary] --[[@as evolved.id?]] + if not fragment_primary_id or fragment_primary_id % 2 ^ 20 ~= fragment_primary then + __error_fmt('the pair (%s) has no alive primary id and cannot be set', + __universal_name(fragment)) + end + + local fragment_secondary_id = __freelist_ids[fragment_secondary] --[[@as evolved.id?]] + if not fragment_secondary_id or fragment_secondary_id % 2 ^ 20 ~= fragment_secondary then + __error_fmt('the pair (%s) has no alive secondary id and cannot be set', + __universal_name(fragment)) + end end end @@ -4387,7 +5273,7 @@ function __evolved_set(entity, fragment, component) if old_chunk_has_setup_hooks or old_chunk_has_assign_hooks then fragment_default, fragment_duplicate, fragment_on_set, fragment_on_assign = - __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_ASSIGN) + __primary_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_ASSIGN) end local old_component_index = old_component_indices[fragment] @@ -4457,7 +5343,7 @@ function __evolved_set(entity, fragment, component) if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then fragment_default, fragment_duplicate, fragment_on_set, fragment_on_insert = - __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + __primary_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end local new_place = new_entity_count + 1 @@ -4530,7 +5416,7 @@ function __evolved_set(entity, fragment, component) if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert = - __evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + __primary_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end local req_component_index = new_component_indices[req_fragment] @@ -4590,9 +5476,12 @@ function __evolved_remove(entity, ...) return end - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then + if entity_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be changed', + __universal_name(entity)) + elseif __freelist_ids[entity_primary] ~= entity then -- the id is not alive, nothing to remove return end @@ -4631,7 +5520,7 @@ function __evolved_remove(entity, ...) if not new_fragment_set[fragment] then ---@type evolved.remove_hook? - local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE) + local fragment_on_remove = __primary_get(fragment, __ON_REMOVE) if fragment_on_remove then local old_component_index = old_component_indices[fragment] @@ -4705,9 +5594,12 @@ function __evolved_clear(...) for argument_index = 1, argument_count do ---@type evolved.entity local entity = __lua_select(argument_index, ...) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then + if entity_options >= __PAIR_OPTIONS then + __warning_fmt('the pair (%s) cannot be changed', + __universal_name(entity)) + elseif __freelist_ids[entity_primary] ~= entity then -- the id is not alive, nothing to clear else local chunk = entity_chunks[entity_primary] @@ -4723,7 +5615,7 @@ function __evolved_clear(...) local fragment = chunk_fragment_list[chunk_fragment_index] ---@type evolved.remove_hook? - local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE) + local fragment_on_remove = __primary_get(fragment, __ON_REMOVE) if fragment_on_remove then local component_index = chunk_component_indices[fragment] @@ -4781,12 +5673,18 @@ function __evolved_destroy(...) for argument_index = 1, argument_count do ---@type evolved.entity local entity = __lua_select(argument_index, ...) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then + if entity_options >= __PAIR_OPTIONS then + __warning_fmt('the pair (%s) cannot be changed', + __universal_name(entity)) + elseif __freelist_ids[entity_primary] ~= entity then -- the id is not alive, nothing to destroy else - local is_fragment = minor_chunks[entity] + local is_fragment = + minor_chunks[entity] or + minor_chunks[__primary_wildcard(entity)] or + minor_chunks[__secondary_wildcard(entity)] if not is_fragment then purging_entity_count = purging_entity_count + 1 @@ -4820,19 +5718,45 @@ end ---@param fragment evolved.fragment ---@param component evolved.component function __evolved_batch_set(query, fragment, component) - local query_primary, _ = __evolved_unpack(query) + local query_primary, _, query_options = __evolved_unpack(query) - if __freelist_ids[query_primary] ~= query then + if query_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be queried', + __universal_name(query)) + elseif __freelist_ids[query_primary] ~= query then __error_fmt('the id (%s) is not alive and cannot be queried', __universal_name(query)) end if __debug_mode then - local fragment_primary, _ = __evolved_unpack(fragment) + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) - if __freelist_ids[fragment_primary] ~= fragment then - __error_fmt('the id (%s) is not alive and cannot be set', - __universal_name(fragment)) + if fragment_options < __PAIR_OPTIONS then + if fragment_primary == __ANY_INDEX then + __error_fmt('the id (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + elseif __freelist_ids[fragment_primary] ~= fragment then + __error_fmt('the id (%s) is not alive and cannot be set', + __universal_name(fragment)) + end + else + if fragment_options >= __PRI_WILDCARD_OPTIONS then + __error_fmt('the pair (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + end + + local fragment_primary_id = __freelist_ids[fragment_primary] --[[@as evolved.id?]] + if not fragment_primary_id or fragment_primary_id % 2 ^ 20 ~= fragment_primary then + __error_fmt('the pair (%s) has no alive primary id and cannot be set', + __universal_name(fragment)) + end + + local fragment_secondary_id = __freelist_ids[fragment_secondary] --[[@as evolved.id?]] + if not fragment_secondary_id or fragment_secondary_id % 2 ^ 20 ~= fragment_secondary then + __error_fmt('the pair (%s) has no alive secondary id and cannot be set', + __universal_name(fragment)) + end end end @@ -4873,9 +5797,12 @@ function __evolved_batch_remove(query, ...) return end - local query_primary, _ = __evolved_unpack(query) + local query_primary, _, query_options = __evolved_unpack(query) - if __freelist_ids[query_primary] ~= query then + if query_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be queried', + __universal_name(query)) + elseif __freelist_ids[query_primary] ~= query then __error_fmt('the id (%s) is not alive and cannot be queried', __universal_name(query)) end @@ -4931,9 +5858,12 @@ function __evolved_batch_clear(...) for argument_index = 1, argument_count do ---@type evolved.query local query = __lua_select(argument_index, ...) - local query_primary, _ = __evolved_unpack(query) + local query_primary, _, query_options = __evolved_unpack(query) - if __freelist_ids[query_primary] ~= query then + if query_options >= __PAIR_OPTIONS then + __warning_fmt('the pair (%s) cannot be queried', + __universal_name(query)) + elseif __freelist_ids[query_primary] ~= query then __warning_fmt('the id (%s) is not alive and cannot be queried', __universal_name(query)) else @@ -4985,9 +5915,12 @@ function __evolved_batch_destroy(...) for argument_index = 1, argument_count do ---@type evolved.query local query = __lua_select(argument_index, ...) - local query_primary, _ = __evolved_unpack(query) + local query_primary, _, query_options = __evolved_unpack(query) - if __freelist_ids[query_primary] ~= query then + if query_options >= __PAIR_OPTIONS then + __warning_fmt('the pair (%s) cannot be queried', + __universal_name(query)) + elseif __freelist_ids[query_primary] ~= query then __warning_fmt('the id (%s) is not alive and cannot be queried', __universal_name(query)) else @@ -4998,7 +5931,10 @@ function __evolved_batch_destroy(...) for i = 1, entity_count do local entity = entity_list[i] - local is_fragment = minor_chunks[entity] + local is_fragment = + minor_chunks[entity] or + minor_chunks[__primary_wildcard(entity)] or + minor_chunks[__secondary_wildcard(entity)] if not is_fragment then purging_entity_count = purging_entity_count + 1 @@ -5042,9 +5978,12 @@ end ---@return evolved.each_state? iterator_state ---@nodiscard function __evolved_each(entity) - local entity_primary, _ = __evolved_unpack(entity) + local entity_primary, _, entity_options = __evolved_unpack(entity) - if __freelist_ids[entity_primary] ~= entity then + if entity_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be iterated', + __universal_name(entity)) + elseif __freelist_ids[entity_primary] ~= entity then __error_fmt('the id (%s) is not alive and cannot be iterated', __universal_name(entity)) end @@ -5071,9 +6010,12 @@ end ---@return evolved.execute_state? iterator_state ---@nodiscard function __evolved_execute(query) - local query_primary, _ = __evolved_unpack(query) + local query_primary, _, query_options = __evolved_unpack(query) - if __freelist_ids[query_primary] ~= query then + if query_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be executed', + __universal_name(query)) + elseif __freelist_ids[query_primary] ~= query then __error_fmt('the id (%s) is not alive and cannot be executed', __universal_name(query)) end @@ -5095,56 +6037,134 @@ function __evolved_execute(query) if query_include_count > 0 then local query_major = query_include_list[query_include_count] - local major_chunks = __major_chunks[query_major] - local major_chunk_list = major_chunks and major_chunks.__item_list - local major_chunk_count = major_chunks and major_chunks.__item_count or 0 + if query_major >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 then + local minor_chunks = __minor_chunks[query_major] + local minor_chunk_list = minor_chunks and minor_chunks.__item_list + local minor_chunk_count = minor_chunks and minor_chunks.__item_count or 0 - for major_chunk_index = 1, major_chunk_count do - local major_chunk = major_chunk_list[major_chunk_index] + for query_include_index = 1, query_include_count - 1 do + local query_minor = query_include_list[query_include_index] - local is_major_chunk_matched = true + local query_chunks = __minor_chunks[query_minor] + local query_chunk_list = query_chunks and query_chunks.__item_list + local query_chunk_count = query_chunks and query_chunks.__item_count or 0 - if is_major_chunk_matched and query_include_count > 1 then - is_major_chunk_matched = __chunk_has_all_fragment_list( - major_chunk, query_include_list, query_include_count - 1) - end + if query_chunk_count < minor_chunk_count then + minor_chunks, minor_chunk_list, minor_chunk_count = + query_chunks, query_chunk_list, query_chunk_count - if is_major_chunk_matched and query_exclude_count > 0 then - is_major_chunk_matched = not __chunk_has_any_fragment_list( - major_chunk, query_exclude_list, query_exclude_count) - end - - if is_major_chunk_matched and major_chunk.__has_explicit_minors then - local major_chunk_minor_list = major_chunk.__fragment_list - local major_chunk_minor_count = major_chunk.__fragment_count - 1 - - for major_chunk_fragment_index = 1, major_chunk_minor_count do - local major_chunk_minor = major_chunk_minor_list[major_chunk_fragment_index] - - local is_major_chunk_minor_included = query_include_set[major_chunk_minor] - - if not is_major_chunk_minor_included and __evolved_has(major_chunk_minor, __EXPLICIT) then - is_major_chunk_matched = false + if query_chunk_count == 0 then break end end end - if is_major_chunk_matched then - chunk_stack_size = chunk_stack_size + 1 - chunk_stack[chunk_stack_size] = major_chunk + for minor_chunk_index = 1, minor_chunk_count do + local minor_chunk = minor_chunk_list[minor_chunk_index] + + local is_minor_chunk_matched = true + + if is_minor_chunk_matched and minor_chunk.__entity_count == 0 then + is_minor_chunk_matched = false + end + + if is_minor_chunk_matched then + is_minor_chunk_matched = __chunk_has_all_fragment_list( + minor_chunk, query_include_list, query_include_count) + end + + if is_minor_chunk_matched and query_exclude_count > 0 then + is_minor_chunk_matched = not __chunk_has_any_fragment_list( + minor_chunk, query_exclude_list, query_exclude_count) + end + + if is_minor_chunk_matched and minor_chunk.__has_explicit_fragments then + local minor_chunk_fragment_list = minor_chunk.__fragment_list + local minor_chunk_fragment_count = minor_chunk.__fragment_count + + for minor_chunk_fragment_index = 1, minor_chunk_fragment_count do + local minor_chunk_fragment = minor_chunk_fragment_list[minor_chunk_fragment_index] + + local is_minor_chunk_fragment_included = + query_include_set[minor_chunk_fragment] or + query_include_set[__secondary_wildcard(__evolved_unpack(minor_chunk_fragment))] + + if not is_minor_chunk_fragment_included and __primary_has(minor_chunk_fragment, __EXPLICIT) then + is_minor_chunk_matched = false + break + end + end + end + + if is_minor_chunk_matched then + chunk_stack_size = chunk_stack_size + 1 + chunk_stack[chunk_stack_size] = minor_chunk + end end + + ---@type evolved.execute_state + local execute_state = __acquire_table(__table_pool_tag.execute_state) + + execute_state[1] = __structural_changes + execute_state[2] = chunk_stack + execute_state[3] = chunk_stack_size + execute_state[4] = query_exclude_set + + return __iterator_fns.__execute_minor_iterator, execute_state + else + local major_chunks = __major_chunks[query_major] + local major_chunk_list = major_chunks and major_chunks.__item_list + local major_chunk_count = major_chunks and major_chunks.__item_count or 0 + + for major_chunk_index = 1, major_chunk_count do + local major_chunk = major_chunk_list[major_chunk_index] + + local is_major_chunk_matched = true + + if is_major_chunk_matched and query_include_count > 1 then + is_major_chunk_matched = __chunk_has_all_fragment_list( + major_chunk, query_include_list, query_include_count - 1) + end + + if is_major_chunk_matched and query_exclude_count > 0 then + is_major_chunk_matched = not __chunk_has_any_fragment_list( + major_chunk, query_exclude_list, query_exclude_count) + end + + if is_major_chunk_matched and major_chunk.__has_explicit_minors then + local major_chunk_minor_list = major_chunk.__fragment_list + local major_chunk_minor_count = major_chunk.__fragment_count - 1 + + for major_chunk_fragment_index = 1, major_chunk_minor_count do + local major_chunk_minor = major_chunk_minor_list[major_chunk_fragment_index] + + local is_major_chunk_minor_included = + query_include_set[major_chunk_minor] or + query_include_set[__secondary_wildcard(__evolved_unpack(major_chunk_minor))] + + if not is_major_chunk_minor_included and __primary_has(major_chunk_minor, __EXPLICIT) then + is_major_chunk_matched = false + break + end + end + end + + if is_major_chunk_matched then + chunk_stack_size = chunk_stack_size + 1 + chunk_stack[chunk_stack_size] = major_chunk + end + end + + ---@type evolved.execute_state + local execute_state = __acquire_table(__table_pool_tag.execute_state) + + execute_state[1] = __structural_changes + execute_state[2] = chunk_stack + execute_state[3] = chunk_stack_size + execute_state[4] = query_exclude_set + + return __iterator_fns.__execute_major_iterator, execute_state end - - ---@type evolved.execute_state - local execute_state = __acquire_table(__table_pool_tag.execute_state) - - execute_state[1] = __structural_changes - execute_state[2] = chunk_stack - execute_state[3] = chunk_stack_size - execute_state[4] = query_exclude_set - - return __iterator_fns.__execute_iterator, execute_state else for _, root_chunk in __lua_next, __root_chunks do local is_root_chunk_matched = true @@ -5172,7 +6192,7 @@ function __evolved_execute(query) execute_state[3] = chunk_stack_size execute_state[4] = query_exclude_set - return __iterator_fns.__execute_iterator, execute_state + return __iterator_fns.__execute_major_iterator, execute_state end end @@ -5187,9 +6207,12 @@ function __evolved_process(...) for argument_index = 1, argument_count do ---@type evolved.system local system = __lua_select(argument_index, ...) - local system_primary, _ = __evolved_unpack(system) + local system_primary, _, system_options = __evolved_unpack(system) - if __freelist_ids[system_primary] ~= system then + if system_options >= __PAIR_OPTIONS then + __warning_fmt('the pair (%s) cannot be processed', + __universal_name(system)) + elseif __freelist_ids[system_primary] ~= system then __warning_fmt('the id (%s) is not alive and cannot be processed', __universal_name(system)) elseif __evolved_has(system, __DISABLED) then @@ -5271,6 +6294,340 @@ function __evolved_collect_garbage() __evolved_commit() end +---@param primary evolved.id +---@param secondary evolved.id +---@return evolved.pair pair +---@nodiscard +function __evolved_pair(primary, secondary) + local primary_index, _, primary_options = __evolved_unpack(primary) + if primary_options >= __PAIR_OPTIONS then + __error_fmt('the primary id (%s) is a pair and cannot be used as a primary id of a new pair', + __universal_name(primary)) + end + + local secondary_index, _, secondary_options = __evolved_unpack(secondary) + if secondary_options >= __PAIR_OPTIONS then + __error_fmt('the secondary id (%s) is a pair and cannot be used as a secondary id of a new pair', + __universal_name(secondary)) + end + + local pair_options = __PAIR_OPTIONS + + if primary_index == __ANY_INDEX and secondary_index == __ANY_INDEX then + pair_options = __ANY_WILDCARD_OPTIONS + elseif primary_index == __ANY_INDEX then + pair_options = __PRI_WILDCARD_OPTIONS + elseif secondary_index == __ANY_INDEX then + pair_options = __SEC_WILDCARD_OPTIONS + end + + return primary_index + secondary_index * 2 ^ 20 + pair_options * 2 ^ 40 --[[@as evolved.pair]] +end + +---@param pair evolved.pair +---@return evolved.id primary +---@return evolved.id secondary +---@nodiscard +function __evolved_unpair(pair) + local pair_primary, pair_secondary, pair_options = __evolved_unpack(pair) + if pair_options < __PAIR_OPTIONS then + __error_fmt('the id (%s) is not a pair and cannot be unpaired', + __universal_name(pair)) + end + + local pair_primary_id = __freelist_ids[pair_primary] --[[@as evolved.id?]] + if not pair_primary_id or pair_primary_id % 2 ^ 20 ~= pair_primary then + __error_fmt('the pair (%s) has not alive primary id and cannot be unpaired', + __universal_name(pair)) + else + ---@cast pair_primary_id -? + end + + local pair_secondary_id = __freelist_ids[pair_secondary] --[[@as evolved.id?]] + if not pair_secondary_id or pair_secondary_id % 2 ^ 20 ~= pair_secondary then + __error_fmt('the pair (%s) has not alive secondary id and cannot be unpaired', + __universal_name(pair)) + else + ---@cast pair_secondary_id -? + end + + return pair_primary_id, pair_secondary_id +end + +---@param id evolved.id +---@return boolean +---@nodiscard +function __evolved_is_pair(id) + return id >= __PAIR_OPTIONS * 2 ^ 40 +end + +---@param id evolved.id +---@return boolean +---@nodiscard +function __evolved_is_wildcard(id) + return id >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 +end + +---@param entity evolved.entity +---@param secondary evolved.fragment +---@param index? integer +---@return evolved.fragment? primary +---@return evolved.component? component +---@nodiscard +function __evolved_primary(entity, secondary, index) + index = index or 1 + + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return + end + + if __freelist_ids[entity_primary] ~= entity then + return + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return + end + + local secondary_index, _, secondary_options = __evolved_unpack(secondary) + + if secondary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a secondary fragment', + __universal_name(secondary)) + end + + local secondary_fragments = entity_chunk.__secondary_pairs[secondary_index] + local secondary_fragment_list = secondary_fragments and secondary_fragments.__item_list + local secondary_fragment_count = secondary_fragments and secondary_fragments.__item_count or 0 + + if index < 1 or index > secondary_fragment_count then + return + end + + local secondary_fragment = secondary_fragment_list[index] + local primary, _ = __evolved_unpair(secondary_fragment) + + local component_index = entity_chunk.__component_indices[secondary_fragment] + local component_storage = entity_chunk.__component_storages[component_index] + + local entity_place = __entity_places[entity_primary] + return primary, component_storage and component_storage[entity_place] +end + +---@param entity evolved.entity +---@param primary evolved.fragment +---@param index? integer +---@return evolved.fragment? secondary +---@return evolved.component? component +---@nodiscard +function __evolved_secondary(entity, primary, index) + index = index or 1 + + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return + end + + if __freelist_ids[entity_primary] ~= entity then + return + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return + end + + local primary_index, _, primary_options = __evolved_unpack(primary) + + if primary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a primary fragment', + __universal_name(primary)) + end + + local primary_fragments = entity_chunk.__primary_pairs[primary_index] + local primary_fragment_list = primary_fragments and primary_fragments.__item_list + local primary_fragment_count = primary_fragments and primary_fragments.__item_count or 0 + + if index < 1 or index > primary_fragment_count then + return + end + + local primary_fragment = primary_fragment_list[index] + local _, secondary = __evolved_unpair(primary_fragment) + + local component_index = entity_chunk.__component_indices[primary_fragment] + local component_storage = entity_chunk.__component_storages[component_index] + + local entity_place = __entity_places[entity_primary] + return secondary, component_storage and component_storage[entity_place] +end + +---@param entity evolved.entity +---@param secondary evolved.fragment +---@return evolved.primaries_iterator iterator +---@return evolved.primaries_state? iterator_state +---@nodiscard +function __evolved_primaries(entity, secondary) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return __iterator_fns.__primaries_iterator + end + + if __freelist_ids[entity_primary] ~= entity then + return __iterator_fns.__primaries_iterator + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return __iterator_fns.__primaries_iterator + end + + local secondary_index, _, secondary_options = __evolved_unpack(secondary) + + if secondary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a secondary fragment', + __universal_name(secondary)) + end + + local secondary_fragments = entity_chunk.__secondary_pairs[secondary_index] + + if not secondary_fragments or secondary_fragments.__item_count == 0 then + return __iterator_fns.__primaries_iterator + end + + ---@type evolved.primaries_state + local primaries_state = __acquire_table(__table_pool_tag.primaries_state) + + primaries_state[1] = __structural_changes + primaries_state[2] = entity_chunk + primaries_state[3] = __entity_places[entity_primary] + primaries_state[4] = secondary_index + primaries_state[5] = 1 + + return __iterator_fns.__primaries_iterator, primaries_state +end + +---@param entity evolved.entity +---@param primary evolved.fragment +---@return evolved.secondaries_iterator iterator +---@return evolved.secondaries_state? iterator_state +---@nodiscard +function __evolved_secondaries(entity, primary) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return __iterator_fns.__secondaries_iterator + end + + if __freelist_ids[entity_primary] ~= entity then + return __iterator_fns.__secondaries_iterator + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return __iterator_fns.__secondaries_iterator + end + + local primary_index, _, primary_options = __evolved_unpack(primary) + + if primary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a primary fragment', + __universal_name(primary)) + end + + local primary_fragments = entity_chunk.__primary_pairs[primary_index] + + if not primary_fragments or primary_fragments.__item_count == 0 then + return __iterator_fns.__secondaries_iterator + end + + ---@type evolved.secondaries_state + local secondaries_state = __acquire_table(__table_pool_tag.secondaries_state) + + secondaries_state[1] = __structural_changes + secondaries_state[2] = entity_chunk + secondaries_state[3] = __entity_places[entity_primary] + secondaries_state[4] = primary_index + secondaries_state[5] = 1 + + return __iterator_fns.__secondaries_iterator, secondaries_state +end + +---@param entity evolved.entity +---@param secondary evolved.fragment +---@return integer +---@nodiscard +function __evolved_primary_count(entity, secondary) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return 0 + end + + if __freelist_ids[entity_primary] ~= entity then + return 0 + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return 0 + end + + local secondary_index, _, secondary_options = __evolved_unpack(secondary) + + if secondary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a secondary fragment', + __universal_name(secondary)) + end + + local secondary_fragments = entity_chunk.__secondary_pairs[secondary_index] + + return secondary_fragments and secondary_fragments.__item_count or 0 +end + +---@param entity evolved.entity +---@param primary evolved.fragment +---@return integer +---@nodiscard +function __evolved_secondary_count(entity, primary) + local entity_primary, _, entity_options = __evolved_unpack(entity) + + if entity_options >= __PAIR_OPTIONS then + return 0 + end + + if __freelist_ids[entity_primary] ~= entity then + return 0 + end + + local entity_chunk = __entity_chunks[entity_primary] + + if not entity_chunk then + return 0 + end + + local primary_index, _, primary_options = __evolved_unpack(primary) + + if primary_options >= __PAIR_OPTIONS then + __error_fmt('the pair (%s) cannot be used as a primary fragment', + __universal_name(primary)) + end + + local primary_fragments = entity_chunk.__primary_pairs[primary_index] + + return primary_fragments and primary_fragments.__item_count or 0 +end + --- --- --- @@ -5459,6 +6816,30 @@ function __builder_mt:has(fragment) return true end + local primary_pairs = self.__primary_pairs + local secondary_pairs = self.__secondary_pairs + + local maybe_has_pairs = primary_pairs and secondary_pairs + + if maybe_has_pairs and fragment >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 then + ---@cast primary_pairs -? + ---@cast secondary_pairs -? + + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) + + if fragment_options == __ANY_WILDCARD_OPTIONS then + return __lua_next(primary_pairs) ~= nil + and __lua_next(secondary_pairs) ~= nil + elseif fragment_options == __PRI_WILDCARD_OPTIONS then + local secondary_fragments = secondary_pairs[fragment_secondary] + return secondary_fragments ~= nil and secondary_fragments.__item_count > 0 + elseif fragment_options == __SEC_WILDCARD_OPTIONS then + local primary_fragments = primary_pairs[fragment_primary] + return primary_fragments ~= nil and primary_fragments.__item_count > 0 + end + end + return false end @@ -5474,30 +6855,41 @@ function __builder_mt:has_all(...) local cs = self.__components + local has_f = self.has + local has_fs = self.has_all + + local m_has_p = self.__primary_pairs and self.__secondary_pairs + if fragment_count == 1 then local f1 = ... - return cs[f1] ~= nil + return (m_has_p and has_f(self, f1)) + or (not m_has_p and cs[f1] ~= nil) end if fragment_count == 2 then local f1, f2 = ... - return cs[f1] ~= nil and cs[f2] ~= nil + return (m_has_p and has_f(self, f1) and has_f(self, f2)) + or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil) end if fragment_count == 3 then local f1, f2, f3 = ... - return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil + return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3)) + or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil) end if fragment_count == 4 then local f1, f2, f3, f4 = ... - return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil + return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3) and has_f(self, f4)) + or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil) end do local f1, f2, f3, f4 = ... - return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil - and self:has_all(__lua_select(5, ...)) + return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3) and has_f(self, f4) + and has_fs(self, __lua_select(5, ...))) + or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil + and has_fs(self, __lua_select(5, ...))) end end @@ -5513,30 +6905,41 @@ function __builder_mt:has_any(...) local cs = self.__components + local has_f = self.has + local has_fs = self.has_any + + local m_has_p = self.__primary_pairs and self.__secondary_pairs + if fragment_count == 1 then local f1 = ... - return cs[f1] ~= nil + return (m_has_p and has_f(self, f1)) + or (not m_has_p and cs[f1] ~= nil) end if fragment_count == 2 then local f1, f2 = ... - return cs[f1] ~= nil or cs[f2] ~= nil + return (m_has_p and (has_f(self, f1) or has_f(self, f2))) + or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil)) end if fragment_count == 3 then local f1, f2, f3 = ... - return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil + return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3))) + or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil)) end if fragment_count == 4 then local f1, f2, f3, f4 = ... - return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil + return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3) or has_f(self, f4))) + or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil)) end do local f1, f2, f3, f4 = ... - return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil - or self:has_any(__lua_select(5, ...)) + return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3) or has_f(self, f4) + or has_fs(self, __lua_select(5, ...)))) + or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil + or has_fs(self, __lua_select(5, ...)))) end end @@ -5583,19 +6986,42 @@ end ---@param component evolved.component ---@return evolved.builder builder function __builder_mt:set(fragment, component) - local fragment_primary, _ = __evolved_unpack(fragment) + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) if __debug_mode then - if __freelist_ids[fragment_primary] ~= fragment then - __error_fmt('the id (%s) is not alive and cannot be set', - __universal_name(fragment)) + if fragment_options < __PAIR_OPTIONS then + if fragment_primary == __ANY_INDEX then + __error_fmt('the id (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + elseif __freelist_ids[fragment_primary] ~= fragment then + __error_fmt('the id (%s) is not alive and cannot be set', + __universal_name(fragment)) + end + else + if fragment_options >= __PRI_WILDCARD_OPTIONS then + __error_fmt('the pair (%s) is a wildcard and cannot be set', + __universal_name(fragment)) + end + + local fragment_primary_id = __freelist_ids[fragment_primary] --[[@as evolved.id?]] + if not fragment_primary_id or fragment_primary_id % 2 ^ 20 ~= fragment_primary then + __error_fmt('the pair (%s) has no alive primary id and cannot be set', + __universal_name(fragment)) + end + + local fragment_secondary_id = __freelist_ids[fragment_secondary] --[[@as evolved.id?]] + if not fragment_secondary_id or fragment_secondary_id % 2 ^ 20 ~= fragment_secondary then + __error_fmt('the pair (%s) has no alive secondary id and cannot be set', + __universal_name(fragment)) + end end end do ---@type evolved.default?, evolved.duplicate? local fragment_default, fragment_duplicate = - __evolved_get(fragment, __DEFAULT, __DUPLICATE) + __primary_get(fragment, __DEFAULT, __DUPLICATE) local new_component = component if new_component == nil then new_component = fragment_default end @@ -5605,6 +7031,39 @@ function __builder_mt:set(fragment, component) self.__components[fragment] = new_component end + if fragment_options >= __PAIR_OPTIONS then + local primary_pairs = self.__primary_pairs + local secondary_pairs = self.__secondary_pairs + + if not primary_pairs then + primary_pairs = {} + self.__primary_pairs = primary_pairs + end + + if not secondary_pairs then + secondary_pairs = {} + self.__secondary_pairs = secondary_pairs + end + + local primary_fragments = primary_pairs[fragment_primary] + local secondary_fragments = secondary_pairs[fragment_secondary] + + if not primary_fragments then + ---@type evolved.assoc_list + primary_fragments = __assoc_list_new(4) + primary_pairs[fragment_primary] = primary_fragments + end + + if not secondary_fragments then + ---@type evolved.assoc_list + secondary_fragments = __assoc_list_new(4) + secondary_pairs[fragment_secondary] = secondary_fragments + end + + __assoc_list_insert(primary_fragments, fragment) + __assoc_list_insert(secondary_fragments, fragment) + end + return self end @@ -5617,42 +7076,100 @@ function __builder_mt:remove(...) return self end - local cs = self.__components + local fragment = ... - if fragment_count == 1 then - local f1 = ... - cs[f1] = nil - return self + local components = self.__components + + components[fragment] = nil + + local primary_pairs = self.__primary_pairs + local secondary_pairs = self.__secondary_pairs + + local maybe_has_pairs = primary_pairs and secondary_pairs + + if maybe_has_pairs and fragment >= __PRI_WILDCARD_OPTIONS * 2 ^ 40 then + ---@cast primary_pairs -? + ---@cast secondary_pairs -? + + local fragment_primary, fragment_secondary, fragment_options = + __evolved_unpack(fragment) + + if fragment_options == __ANY_WILDCARD_OPTIONS then + for primary_index, primary_fragments in __lua_next, primary_pairs do + local primary_fragment_list = primary_fragments.__item_list + local primary_fragment_count = primary_fragments.__item_count + + for primary_fragment_index = 1, primary_fragment_count do + local primary_fragment = primary_fragment_list[primary_fragment_index] + components[primary_fragment] = nil + end + + primary_pairs[primary_index] = nil + end + + for secondary_index, secondary_fragments in __lua_next, secondary_pairs do + local secondary_fragment_list = secondary_fragments.__item_list + local secondary_fragment_count = secondary_fragments.__item_count + + for secondary_fragment_index = 1, secondary_fragment_count do + local secondary_fragment = secondary_fragment_list[secondary_fragment_index] + components[secondary_fragment] = nil + end + + secondary_pairs[secondary_index] = nil + end + elseif fragment_options == __PRI_WILDCARD_OPTIONS then + local secondary_fragments = secondary_pairs[fragment_secondary] + local secondary_fragment_list = secondary_fragments and secondary_fragments.__item_list + local secondary_fragment_count = secondary_fragments and secondary_fragments.__item_count or 0 + + for secondary_fragment_index = 1, secondary_fragment_count do + local secondary_fragment = secondary_fragment_list[secondary_fragment_index] + components[secondary_fragment] = nil + + local secondary_fragment_primary_index, _ = __evolved_unpack(secondary_fragment) + if __assoc_list_remove(primary_pairs[secondary_fragment_primary_index], secondary_fragment) == 0 then + primary_pairs[secondary_fragment_primary_index] = nil + end + end + + secondary_pairs[fragment_secondary] = nil + elseif fragment_options == __SEC_WILDCARD_OPTIONS then + local primary_fragments = primary_pairs[fragment_primary] + local primary_fragment_list = primary_fragments and primary_fragments.__item_list + local primary_fragment_count = primary_fragments and primary_fragments.__item_count or 0 + + for primary_fragment_index = 1, primary_fragment_count do + local primary_fragment = primary_fragment_list[primary_fragment_index] + components[primary_fragment] = nil + + local _, primary_fragment_secondary_index = __evolved_unpack(primary_fragment) + if __assoc_list_remove(secondary_pairs[primary_fragment_secondary_index], primary_fragment) == 0 then + secondary_pairs[primary_fragment_secondary_index] = nil + end + end + + primary_pairs[fragment_primary] = nil + end end - if fragment_count == 2 then - local f1, f2 = ... - cs[f1], cs[f2] = nil, nil - return self - end - - if fragment_count == 3 then - local f1, f2, f3 = ... - cs[f1], cs[f2], cs[f3] = nil, nil, nil - return self - end - - if fragment_count == 4 then - local f1, f2, f3, f4 = ... - cs[f1], cs[f2], cs[f3], cs[f4] = nil, nil, nil, nil - return self - end - - do - local f1, f2, f3, f4 = ... - cs[f1], cs[f2], cs[f3], cs[f4] = nil, nil, nil, nil - return self:remove(__lua_select(5, ...)) - end + return fragment_count > 1 and self:remove(__lua_select(2, ...)) or self end ---@return evolved.builder builder function __builder_mt:clear() - __lua_table_clear(self.__components) + if self.__components then + __lua_table_clear(self.__components) + end + + if self.__primary_pairs then + __lua_table_clear(self.__primary_pairs) + end + + if self.__secondary_pairs then + __lua_table_clear(self.__secondary_pairs) + end + return self end @@ -5888,6 +7405,8 @@ __evolved_set(__REQUIRES, __ON_REMOVE, __update_major_chunks_hook) --- --- +__evolved_set(__ANY, __NAME, 'ANY') + __evolved_set(__TAG, __NAME, 'TAG') __evolved_set(__NAME, __NAME, 'NAME') @@ -5928,6 +7447,8 @@ __evolved_set(__DESTRUCTION_POLICY_REMOVE_FRAGMENT, __NAME, 'DESTRUCTION_POLICY_ --- --- +__evolved_set(__ANY, __INTERNAL) + __evolved_set(__TAG, __INTERNAL) __evolved_set(__NAME, __INTERNAL) @@ -5968,6 +7489,8 @@ __evolved_set(__DESTRUCTION_POLICY_REMOVE_FRAGMENT, __INTERNAL) --- --- +__evolved_set(__ANY, __TAG) + __evolved_set(__TAG, __TAG) __evolved_set(__UNIQUE, __TAG) @@ -6136,6 +7659,8 @@ end) --- --- +evolved.ANY = __ANY + evolved.TAG = __TAG evolved.NAME = __NAME @@ -6172,7 +7697,7 @@ evolved.DESTRUCTION_POLICY_REMOVE_FRAGMENT = __DESTRUCTION_POLICY_REMOVE_FRAGMEN --- --- ---- Functions +--- Core Functions --- --- @@ -6223,6 +7748,27 @@ evolved.collect_garbage = __evolved_collect_garbage evolved.chunk = __evolved_chunk evolved.builder = __evolved_builder +--- +--- +--- Relation Functions +--- +--- + +evolved.pair = __evolved_pair +evolved.unpair = __evolved_unpair + +evolved.is_pair = __evolved_is_pair +evolved.is_wildcard = __evolved_is_wildcard + +evolved.primary = __evolved_primary +evolved.secondary = __evolved_secondary + +evolved.primaries = __evolved_primaries +evolved.secondaries = __evolved_secondaries + +evolved.primary_count = __evolved_primary_count +evolved.secondary_count = __evolved_secondary_count + --- --- ---