From b8e0345e02a3c88b5109995366cc725cebad7b8e Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 7 Jul 2025 05:33:45 +0700 Subject: [PATCH] primary/secondary pair iterators --- README.md | 12 + develop/all.lua | 30 +- develop/testing/pairs_tests.lua | 204 +++++++++++++ evolved.lua | 516 ++++++++++++++++++++++++++------ 4 files changed, 649 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index bbb9079..4f23010 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - [Aliases](#aliases) - [Predefs](#predefs) - [Functions](#functions) + - [Relations](#relations) - [Classes](#classes) - [Chunk](#chunk) - [Builder](#builder) @@ -1114,6 +1115,17 @@ debug_mode :: boolean -> () collect_garbage :: () ``` +### Relations + +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 diff --git a/develop/all.lua b/develop/all.lua index 21f5475..fd21630 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -6,20 +6,20 @@ require 'develop.testing.pairs_tests' require 'develop.testing.requires_fragment_tests' require 'develop.testing.system_as_query_tests' -require 'develop.unbench' -require 'develop.usbench' +-- require 'develop.unbench' +-- require 'develop.usbench' -local basics = require 'develop.basics' +-- local basics = require 'develop.basics' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.destroy_fuzz' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.batch_destroy_fuzz' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.explicit_fuzz' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.pack_unpack_fuzz' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.requires_fuzz' -print '----------------------------------------' -basics.describe_fuzz 'develop.fuzzing.unique_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.destroy_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.batch_destroy_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.explicit_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.pack_unpack_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.requires_fuzz' +-- print '----------------------------------------' +-- basics.describe_fuzz 'develop.fuzzing.unique_fuzz' diff --git a/develop/testing/pairs_tests.lua b/develop/testing/pairs_tests.lua index 076f45a..2f06948 100644 --- a/develop/testing/pairs_tests.lua +++ b/develop/testing/pairs_tests.lua @@ -635,6 +635,210 @@ do 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, -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, -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 + -- TODO: -- How should required fragments work with pairs? -- How can we set defaults for paired fragments? diff --git a/evolved.lua b/evolved.lua index e096790..10448de 100644 --- a/evolved.lua +++ b/evolved.lua @@ -82,6 +82,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] evolved.fragment secondary_fragment +---@field package [5] integer secondary_pair_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] evolved.fragment primary_fragment +---@field package [5] integer primary_pair_index + ---@alias evolved.each_iterator fun( --- state: evolved.each_state?): --- evolved.fragment?, evolved.component? @@ -90,6 +104,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? + --- --- --- @@ -433,13 +455,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 @@ -731,90 +755,6 @@ end --- --- ----@type evolved.each_iterator -local function __each_iterator(each_state) - if not each_state then return end - - local structural_changes = each_state[1] - local entity_chunk = each_state[2] - local entity_place = each_state[3] - local chunk_fragment_index = each_state[4] - - if structural_changes ~= __structural_changes then - __error_fmt('structural changes are prohibited during iteration') - end - - local chunk_fragment_list = entity_chunk.__fragment_list - local chunk_fragment_count = entity_chunk.__fragment_count - local chunk_component_indices = entity_chunk.__component_indices - local chunk_component_storages = entity_chunk.__component_storages - - if chunk_fragment_index <= chunk_fragment_count then - each_state[4] = chunk_fragment_index + 1 - local fragment = chunk_fragment_list[chunk_fragment_index] - local component_index = chunk_component_indices[fragment] - local component_storage = chunk_component_storages[component_index] - return fragment, component_storage and component_storage[entity_place] - end - - __release_table(__table_pool_tag.each_state, each_state, true) -end - ----@type evolved.execute_iterator -local function __execute_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] - local exclude_set = execute_state[4] - - 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_child_list = chunk.__child_list - local chunk_child_count = chunk.__child_count - - for i = 1, chunk_child_count do - local chunk_child = chunk_child_list[i] - local chunk_child_fragment = chunk_child.__fragment - - local is_chunk_child_matched = - (not chunk_child.__has_explicit_major) and - (not exclude_set or not exclude_set[chunk_child_fragment]) - - if is_chunk_child_matched then - chunk_stack_size = chunk_stack_size + 1 - chunk_stack[chunk_stack_size] = chunk_child - end - end - - 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 - ---- ---- ---- ---- ---- - local __ANY = __acquire_id() local __TAG = __acquire_id() @@ -941,6 +881,15 @@ local __evolved_process local __evolved_debug_mode local __evolved_collect_garbage +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 @@ -1097,6 +1046,158 @@ end --- --- +local __iterator_fns = {} + +---@type evolved.each_iterator +function __iterator_fns.__each_iterator(each_state) + if not each_state then return end + + local structural_changes = each_state[1] + local entity_chunk = each_state[2] + local entity_place = each_state[3] + local chunk_fragment_index = each_state[4] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + local chunk_fragment_list = entity_chunk.__fragment_list + local chunk_fragment_count = entity_chunk.__fragment_count + local chunk_component_indices = entity_chunk.__component_indices + local chunk_component_storages = entity_chunk.__component_storages + + if chunk_fragment_index <= chunk_fragment_count then + each_state[4] = chunk_fragment_index + 1 + local fragment = chunk_fragment_list[chunk_fragment_index] + local component_index = chunk_component_indices[fragment] + local component_storage = chunk_component_storages[component_index] + return fragment, component_storage and component_storage[entity_place] + end + + __release_table(__table_pool_tag.each_state, each_state, true) +end + +---@type evolved.execute_iterator +function __iterator_fns.__execute_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] + local exclude_set = execute_state[4] + + 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_child_list = chunk.__child_list + local chunk_child_count = chunk.__child_count + + for i = 1, chunk_child_count do + local chunk_child = chunk_child_list[i] + local chunk_child_fragment = chunk_child.__fragment + + local is_chunk_child_matched = + (not chunk_child.__has_explicit_major) and + (not exclude_set or not exclude_set[chunk_child_fragment]) + + if is_chunk_child_matched then + chunk_stack_size = chunk_stack_size + 1 + chunk_stack[chunk_stack_size] = chunk_child + end + end + + 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_fragment = primaries_state[4] + local secondary_pair_index = primaries_state[5] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + local secondary_pairs = entity_chunk.__secondaries[secondary_fragment] + local secondary_pair_list = secondary_pairs and secondary_pairs.__item_list + local secondary_pair_count = secondary_pairs and secondary_pairs.__item_count or 0 + + if secondary_pair_index <= secondary_pair_count then + primaries_state[5] = secondary_pair_index + 1 + local secondary_pair = secondary_pair_list[secondary_pair_index] + + local primary, _ = __evolved_unpair(secondary_pair) + + local component_index = entity_chunk.__component_indices[secondary_pair] + 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_fragment = secondaries_state[4] + local primary_pair_index = secondaries_state[5] + + if structural_changes ~= __structural_changes then + __error_fmt('structural changes are prohibited during iteration') + end + + local primary_pairs = entity_chunk.__primaries[primary_fragment] + local primary_pair_list = primary_pairs and primary_pairs.__item_list + local primary_pair_count = primary_pairs and primary_pairs.__item_count or 0 + + if primary_pair_index <= primary_pair_count then + secondaries_state[5] = primary_pair_index + 1 + local primary_pair = primary_pair_list[primary_pair_index] + + local _, secondary = __evolved_unpair(primary_pair) + + local component_index = entity_chunk.__component_indices[primary_pair] + 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 + +--- +--- +--- +--- +--- + local __new_chunk local __update_chunk_tags local __update_chunk_flags @@ -1776,7 +1877,9 @@ local function __chunk_has_fragment(chunk, fragment) return false end - return chunk.__secondaries[secondary] ~= nil + local secondaries = chunk.__secondaries[secondary] + + return secondaries and secondaries.__item_count > 0 elseif secondary_index == any_index then local primary = __freelist_ids[primary_index] @@ -1785,7 +1888,9 @@ local function __chunk_has_fragment(chunk, fragment) return false end - return chunk.__primaries[primary] ~= nil + local primaries = chunk.__primaries[primary] + + return primaries and primaries.__item_count > 0 end end @@ -5421,7 +5526,7 @@ function __evolved_each(entity) local entity_index = entity % 0x100000 if __freelist_ids[entity_index] ~= entity then - return __each_iterator + return __iterator_fns.__each_iterator end local entity_chunks = __entity_chunks @@ -5431,7 +5536,7 @@ function __evolved_each(entity) local place = entity_places[entity_index] if not chunk then - return __each_iterator + return __iterator_fns.__each_iterator end ---@type evolved.each_state @@ -5442,7 +5547,7 @@ function __evolved_each(entity) each_state[3] = place each_state[4] = 1 - return __each_iterator, each_state + return __iterator_fns.__each_iterator, each_state end ---@param query evolved.query @@ -5453,7 +5558,7 @@ function __evolved_execute(query) local query_index = query % 0x100000 if __freelist_ids[query_index] ~= query then - return __execute_iterator + return __iterator_fns.__execute_iterator end ---@type evolved.chunk[] @@ -5536,7 +5641,7 @@ function __evolved_execute(query) execute_state[3] = chunk_stack_size execute_state[4] = query_exclude_set - return __execute_iterator, execute_state + return __iterator_fns.__execute_iterator, execute_state end ---@param ... evolved.system systems @@ -5631,6 +5736,212 @@ 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 + + if __is_pair(entity) then + -- pairs are always empty + return + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return + end + + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] + + local secondary_pairs = chunk and chunk.__secondaries[secondary] + local secondary_pair_list = secondary_pairs and secondary_pairs.__item_list + local secondary_pair_count = secondary_pairs and secondary_pairs.__item_count or 0 + + if index < 1 or index > secondary_pair_count then + return + end + + local secondary_pair = secondary_pair_list[index] + local primary, _ = __evolved_unpair(secondary_pair) + + local component_index = chunk.__component_indices[secondary_pair] + local component_storage = chunk.__component_storages[component_index] + + return primary, component_storage and component_storage[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 + + if __is_pair(entity) then + -- pairs are always empty + return + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return + end + + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] + + local primary_pairs = chunk and chunk.__primaries[primary] + local primary_pair_list = primary_pairs and primary_pairs.__item_list + local primary_pair_count = primary_pairs and primary_pairs.__item_count or 0 + + if index < 1 or index > primary_pair_count then + return + end + + local primary_pair = primary_pair_list[index] + local _, secondary = __evolved_unpair(primary_pair) + + local component_index = chunk.__component_indices[primary_pair] + local component_storage = chunk.__component_storages[component_index] + + return secondary, component_storage and component_storage[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) + if __is_pair(entity) then + -- pairs are always empty + return __iterator_fns.__primaries_iterator + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return __iterator_fns.__primaries_iterator + end + + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] + + local secondaries = chunk and chunk.__secondaries[secondary] + + if not secondaries 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] = chunk + primaries_state[3] = place + primaries_state[4] = secondary + 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) + if __is_pair(entity) then + -- pairs are always empty + return __iterator_fns.__secondaries_iterator + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return __iterator_fns.__secondaries_iterator + end + + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] + + local primaries = chunk and chunk.__primaries[primary] + + if not primaries 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] = chunk + secondaries_state[3] = place + secondaries_state[4] = primary + 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) + if __is_pair(entity) then + -- pairs are always empty + return 0 + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return 0 + end + + local chunk = __entity_chunks[entity_index] + + local secondary_pairs = chunk and chunk.__secondaries[secondary] + return secondary_pairs and secondary_pairs.__item_count or 0 +end + +---@param entity evolved.entity +---@param primary evolved.fragment +---@return integer +---@nodiscard +function __evolved_secondary_count(entity, primary) + if __is_pair(entity) then + -- pairs are always empty + return 0 + end + + local entity_index = entity % 2 ^ 20 + + if __freelist_ids[entity_index] ~= entity then + return 0 + end + + local chunk = __entity_chunks[entity_index] + + local primary_pairs = chunk and chunk.__primaries[primary] + return primary_pairs and primary_pairs.__item_count or 0 +end + +--- +--- +--- +--- +--- + ---@param fragment evolved.fragment ---@param ... evolved.fragment fragments ---@return evolved.chunk chunk @@ -6557,6 +6868,15 @@ evolved.batch_destroy = __evolved_batch_destroy evolved.each = __evolved_each evolved.execute = __evolved_execute +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 + evolved.process = __evolved_process evolved.debug_mode = __evolved_debug_mode