From 5e0170b4e834d8fe3d25a96f989cdb9b6fadd00c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 3 Jan 2026 15:42:59 +0700 Subject: [PATCH 1/8] new evolved.process_with function impl --- README.md | 64 +++++- develop/all.lua | 1 + develop/testing/process_with_tests.lua | 107 ++++++++++ evolved.d.tl | 7 +- evolved.lua | 260 ++++++++++++++++++++++--- example/main.lua | 10 +- 6 files changed, 406 insertions(+), 43 deletions(-) create mode 100644 develop/testing/process_with_tests.lua diff --git a/README.md b/README.md index ff9bac3..ddc1784 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ - [Deferred Operations](#deferred-operations) - [Batch Operations](#batch-operations) - [Systems](#systems) + - [Processing Payloads](#processing-payloads) - [Predefined Traits](#predefined-traits) - [Fragment Tags](#fragment-tags) - [Fragment Hooks](#fragment-hooks) @@ -60,6 +61,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) + - [vX.Y.Z](#vxyz) - [v1.6.0](#v160) - [v1.5.0](#v150) - [v1.4.0](#v140) @@ -880,6 +882,43 @@ The prologue and epilogue fragments do not require an explicit query. They will > [!NOTE] > And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), which means that all modifying operations inside the callback will be queued and applied after the system has processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. +#### Processing Payloads + +Additionally, systems can have a payload that will be passed to the execution, prologue, and epilogue callbacks. This is useful for passing additional data to the system without using global variables or closures. + +```lua +---@param system evolved.system +---@param ... any processing payload +function evolved.process_with(system, ...) end +``` + +The [`evolved.process_with`](#evolvedprocess_with) function is similar to the [`evolved.process`](#evolvedprocess) function, but it takes a processing payload as additional arguments. These arguments will be passed to the system's callbacks. + +```lua +local evolved = require 'evolved' + +local position_x, position_y = evolved.id(2) +local velocity_x, velocity_y = evolved.id(2) + +local physics_system = evolved.builder() + :include(position_x, position_y) + :include(velocity_x, velocity_y) + :execute(function(chunk, entity_list, entity_count, delta_time) + local px, py = chunk:components(position_x, position_y) + local vx, vy = chunk:components(velocity_x, velocity_y) + + for i = 1, entity_count do + px[i] = px[i] + vx[i] * delta_time + py[i] = py[i] + vy[i] * delta_time + end + end):build() + +local delta_time = 0.016 +evolved.process_with(physics_system, delta_time) +``` + +`delta_time` in this example is passed as a processing payload to the system's execution callback. Payloads can be of any type and can be multiple values. Also, payloads are passed to prologue and epilogue callbacks if they are defined. Every subsystem in a group will receive the same payload when the group is processed with [`evolved.process_with`](#evolvedprocess_with). + ### Predefined Traits #### Fragment Tags @@ -1125,9 +1164,9 @@ storage :: component[] default :: component duplicate :: {component -> component} -execute :: {chunk, entity[], integer} -prologue :: {} -epilogue :: {} +execute :: {chunk, entity[], integer, any...} +prologue :: {any...} +epilogue :: {any...} set_hook :: {entity, fragment, component, component} assign_hook :: {entity, fragment, component, component} @@ -1229,6 +1268,7 @@ execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_sta locate :: entity -> chunk?, integer process :: system... -> () +process_with :: system, ... -> () debug_mode :: boolean -> () collect_garbage :: () @@ -1302,16 +1342,20 @@ builder_mt:on_remove :: {entity, fragment} -> builder builder_mt:group :: system -> builder builder_mt:query :: query -> builder -builder_mt:execute :: {chunk, entity[], integer} -> builder +builder_mt:execute :: {chunk, entity[], integer, any...} -> builder -builder_mt:prologue :: {} -> builder -builder_mt:epilogue :: {} -> builder +builder_mt:prologue :: {any...} -> builder +builder_mt:epilogue :: {any...} -> builder builder_mt:destruction_policy :: id -> builder ``` ## Changelog +### vX.Y.Z + +- Added the new [`evolved.process_with`](#evolvedprocess_with) function that allows passing payloads to processing systems + ### v1.6.0 - Significant performance improvements of the [`evolved.REQUIRES`](#evolvedrequires) fragment trait @@ -1710,6 +1754,14 @@ function evolved.locate(entity) end function evolved.process(...) end ``` +### `evolved.process_with` + +```lua +---@param system evolved.system +---@param ... any processing payload +function evolved.process_with(system, ...) end +``` + ### `evolved.debug_mode` ```lua diff --git a/develop/all.lua b/develop/all.lua index ee8449d..ca2350e 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -7,6 +7,7 @@ require 'develop.testing.locate_tests' require 'develop.testing.main_tests' require 'develop.testing.multi_spawn_tests' require 'develop.testing.name_tests' +require 'develop.testing.process_with_tests' require 'develop.testing.requires_fragment_tests' require 'develop.testing.spawn_tests' require 'develop.testing.system_as_query_tests' diff --git a/develop/testing/process_with_tests.lua b/develop/testing/process_with_tests.lua new file mode 100644 index 0000000..669eb75 --- /dev/null +++ b/develop/testing/process_with_tests.lua @@ -0,0 +1,107 @@ +local evo = require 'evolved' + +do + local f = evo.id() + local e = evo.builder():set(f, 42):spawn() + + local s = evo.builder() + :include(f) + :prologue(function(payload1, payload2, payload3) + assert(payload1 == 11 and payload2 == 22 and payload3 == 33) + end) + :execute(function(chunk, entity_list, entity_count, payload1, payload2, payload3) + assert(payload1 == 11 and payload2 == 22 and payload3 == 33) + assert(chunk == evo.chunk(f) and entity_count == 1 and entity_list[1] == e) + end) + :epilogue(function(payload1, payload2, payload3) + assert(payload1 == 11 and payload2 == 22 and payload3 == 33) + end) + :spawn() + + evo.process_with(s, 11, 22, 33) +end + +do + local f = evo.id() + local e = evo.builder():set(f, 42):spawn() + + local s = evo.builder() + :include(f) + :prologue(function(payload1, payload2, payload3) + assert(payload1 == nil and payload2 == 42 and payload3 == nil) + end) + :execute(function(chunk, entity_list, entity_count, payload1, payload2, payload3) + assert(payload1 == nil and payload2 == 42 and payload3 == nil) + assert(chunk == evo.chunk(f) and entity_count == 1 and entity_list[1] == e) + end) + :epilogue(function(payload1, payload2, payload3) + assert(payload1 == nil and payload2 == 42 and payload3 == nil) + end) + :spawn() + + evo.process_with(s, nil, 42) +end + +do + local f = evo.id() + local e = evo.builder():set(f, 42):spawn() + + local s = evo.builder() + :include(f) + :prologue(function(payload1, payload2, payload3) + assert(payload1 == nil and payload2 == nil and payload3 == nil) + end) + :execute(function(chunk, entity_list, entity_count, payload1, payload2, payload3) + assert(payload1 == nil and payload2 == nil and payload3 == nil) + assert(chunk == evo.chunk(f) and entity_count == 1 and entity_list[1] == e) + end) + :epilogue(function(payload1, payload2, payload3) + assert(payload1 == nil and payload2 == nil and payload3 == nil) + end) + :spawn() + + evo.process_with(s) +end + +do + local f = evo.id() + local e = evo.builder():set(f, 42):spawn() + + local prologue_sum, execute_sum, epilogue_sum = 0, 0, 0 + + local function sum(...) + local s = 0 + for i = 1, select('#', ...) do + s = s + select(i, ...) + end + return s + end + + local function iota(n) + if n == 0 then return end + return n, iota(n - 1) + end + + local s = evo.builder() + :include(f) + :prologue(function(...) + prologue_sum = prologue_sum + sum(...) + end) + :execute(function(chunk, entity_list, entity_count, ...) + execute_sum = execute_sum + sum(...) + assert(chunk == evo.chunk(f) and entity_count == 1 and entity_list[1] == e) + end) + :epilogue(function(...) + epilogue_sum = epilogue_sum + sum(...) + end) + :spawn() + + for n = 0, 50 do + prologue_sum, execute_sum, epilogue_sum = 0, 0, 0 + evo.process_with(s, iota(n)) + local expect_sum = (n * (n + 1)) / 2 + assert(prologue_sum == expect_sum) + assert(execute_sum == expect_sum) + assert(epilogue_sum == expect_sum) + end +end diff --git a/evolved.d.tl b/evolved.d.tl index 7c75a2a..d78e4a0 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -79,10 +79,10 @@ group: function(self: Builder, group: System): Builder query: function(self: Builder, query: Query): Builder - execute: function(self: Builder, execute: function(Chunk, {Entity}, integer)): Builder + execute: function(self: Builder, execute: function(Chunk, {Entity}, integer, ...: any)): Builder - prologue: function(self: Builder, prologue: function()): Builder - epilogue: function(self: Builder, epilogue: function()): Builder + prologue: function(self: Builder, prologue: function(...: any)): Builder + epilogue: function(self: Builder, epilogue: function(...: any)): Builder destruction_policy: function(self: Builder, destruction_policy: Id): Builder end @@ -171,6 +171,7 @@ locate: function(entity: Entity): Chunk | nil, integer process: function(...: System) + process_with: function(system: System, ...: any) debug_mode: function(yesno: boolean) collect_garbage: function() diff --git a/evolved.lua b/evolved.lua index 6cf2ac4..beae86d 100644 --- a/evolved.lua +++ b/evolved.lua @@ -43,10 +43,11 @@ local evolved = { ---@alias evolved.execute fun( --- chunk: evolved.chunk, --- entity_list: evolved.entity[], ---- entity_count: integer) +--- entity_count: integer, +--- ...: any) ----@alias evolved.prologue fun() ----@alias evolved.epilogue fun() +---@alias evolved.prologue fun(...: any) +---@alias evolved.epilogue fun(...: any) ---@alias evolved.set_hook fun( --- entity: evolved.entity, @@ -207,7 +208,6 @@ local __lua_string_format = string.format local __lua_table_concat = table.concat local __lua_table_sort = table.sort local __lua_tostring = tostring -local __lua_xpcall = xpcall ---@type fun(nseq?: integer): table local __lua_table_new = (function() @@ -338,6 +338,210 @@ local __lua_debug_traceback = (function() end end)() +---@type fun(f: function, e: function, ...): boolean, ... +local __lua_xpcall = (function() + -- https://github.com/BlackMATov/xpcall.lua + + local builtin_xpcall = xpcall + + ---@diagnostic disable-next-line: redundant-parameter + if __lua_select(2, builtin_xpcall(function(a) return a end, function() end, 42)) == 42 then + -- use the built-in xpcall if it works with extra arguments as expected + return builtin_xpcall + end + + local xpcall_function + + local xpcall_argument_1, xpcall_argument_2 + local xpcall_argument_3, xpcall_argument_4 + local xpcall_argument_5, xpcall_argument_6 + local xpcall_argument_7, xpcall_argument_8 + + local xpcall_argument_tail_list = {} + local xpcall_argument_tail_count = 0 + + local function call_xpcall_function_1() + return xpcall_function( + xpcall_argument_1) + end + + local function call_xpcall_function_2() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2) + end + + local function call_xpcall_function_3() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3) + end + + local function call_xpcall_function_4() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4) + end + + local function call_xpcall_function_5() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5) + end + + local function call_xpcall_function_6() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6) + end + + local function call_xpcall_function_7() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7) + end + + local function call_xpcall_function_8() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7, xpcall_argument_8) + end + + local function call_xpcall_function_N() + return xpcall_function( + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7, xpcall_argument_8, + __lua_table_unpack(xpcall_argument_tail_list, 1, xpcall_argument_tail_count)) + end + + ---@type fun(f: function, e: function, ...): boolean, ... + return function(f, e, ...) + local argument_count = __lua_select('#', ...) + + if argument_count == 0 then + -- use the built-in xpcall without extra arguments + return builtin_xpcall(f, e) + end + + xpcall_function = f + + if argument_count <= 8 then + if argument_count <= 4 then + if argument_count <= 2 then + if argument_count <= 1 then + xpcall_argument_1 = ... + return builtin_xpcall(call_xpcall_function_1, e) + else + xpcall_argument_1, xpcall_argument_2 = ... + return builtin_xpcall(call_xpcall_function_2, e) + end + else + if argument_count <= 3 then + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3 = ... + return builtin_xpcall(call_xpcall_function_3, e) + else + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4 = ... + return builtin_xpcall(call_xpcall_function_4, e) + end + end + else + if argument_count <= 6 then + if argument_count <= 5 then + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5 = ... + return builtin_xpcall(call_xpcall_function_5, e) + else + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6 = ... + return builtin_xpcall(call_xpcall_function_6, e) + end + else + if argument_count <= 7 then + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7 = ... + return builtin_xpcall(call_xpcall_function_7, e) + else + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7, xpcall_argument_8 = ... + return builtin_xpcall(call_xpcall_function_8, e) + end + end + end + else + xpcall_argument_1, xpcall_argument_2, + xpcall_argument_3, xpcall_argument_4, + xpcall_argument_5, xpcall_argument_6, + xpcall_argument_7, xpcall_argument_8 = ... + end + + xpcall_argument_tail_count = argument_count - 8 + local argument_tail_list = xpcall_argument_tail_list + + for i = 1, argument_count - 8, 8 do + local argument_remaining = argument_count - 8 - i + 1 + + if argument_remaining <= 4 then + if argument_remaining <= 2 then + if argument_remaining <= 1 then + argument_tail_list[i] = __lua_select(i + 8, ...) + else + argument_tail_list[i], argument_tail_list[i + 1] = __lua_select(i + 8, ...) + end + else + if argument_remaining <= 3 then + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2] = __lua_select(i + 8, ...) + else + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2], argument_tail_list[i + 3] = __lua_select(i + 8, ...) + end + end + else + if argument_remaining <= 6 then + if argument_remaining <= 5 then + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2], argument_tail_list[i + 3], + argument_tail_list[i + 4] = __lua_select(i + 8, ...) + else + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2], argument_tail_list[i + 3], + argument_tail_list[i + 4], argument_tail_list[i + 5] = __lua_select(i + 8, ...) + end + else + if argument_remaining <= 7 then + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2], argument_tail_list[i + 3], + argument_tail_list[i + 4], argument_tail_list[i + 5], + argument_tail_list[i + 6] = __lua_select(i + 8, ...) + else + argument_tail_list[i], argument_tail_list[i + 1], + argument_tail_list[i + 2], argument_tail_list[i + 3], + argument_tail_list[i + 4], argument_tail_list[i + 5], + argument_tail_list[i + 6], argument_tail_list[i + 7] = __lua_select(i + 8, ...) + end + end + end + end + + return builtin_xpcall(call_xpcall_function_N, e) + end +end)() + --- --- --- @@ -840,6 +1044,7 @@ local __evolved_execute local __evolved_locate local __evolved_process +local __evolved_process_with local __evolved_debug_mode local __evolved_collect_garbage @@ -3665,32 +3870,24 @@ function __iterator_fns.__execute_iterator(execute_state) __release_table(__table_pool_tag.execute_state, execute_state, true) end ----@type { [1]: evolved.query, [2]: evolved.execute } -local __query_execute_external_arguments = {} - ----@param query? evolved.query ----@param execute? evolved.execute -local function __query_execute(query, execute) - -- we use the external arguments here to support lua 5.1 xpcall (which does not support argument passing) - -- also, we can not use upvalues directly, because the function may be called recursively in that case - -- storing the arguments in local variables makes them invulnerable to changes during recursive calls - - query = query or __query_execute_external_arguments[1] - execute = execute or __query_execute_external_arguments[2] - +---@param query evolved.query +---@param execute evolved.execute +---@param ... any processing payload +local function __query_execute(query, execute, ...) for chunk, entity_list, entity_count in __evolved_execute(query) do - execute(chunk, entity_list, entity_count) + execute(chunk, entity_list, entity_count, ...) end end ---@param system evolved.system -local function __system_process(system) +---@param ... any processing payload +local function __system_process(system, ...) ---@type evolved.query?, evolved.execute?, evolved.prologue?, evolved.epilogue? local query, execute, prologue, epilogue = __evolved_get(system, __QUERY, __EXECUTE, __PROLOGUE, __EPILOGUE) if prologue then - local success, result = __lua_xpcall(prologue, __lua_debug_traceback) + local success, result = __lua_xpcall(prologue, __lua_debug_traceback, ...) if not success then __error_fmt('system prologue failed: %s', result) @@ -3700,8 +3897,7 @@ local function __system_process(system) if execute then __evolved_defer() do - __query_execute_external_arguments[1], __query_execute_external_arguments[2] = query or system, execute - local success, result = __lua_xpcall(__query_execute, __lua_debug_traceback, query or system, execute) + local success, result = __lua_xpcall(__query_execute, __lua_debug_traceback, query or system, execute, ...) if not success then __evolved_cancel() @@ -3727,7 +3923,7 @@ local function __system_process(system) for subsystem_index = 1, group_subsystem_count do local subsystem = subsystem_list[subsystem_index] if not __evolved_has(subsystem, __DISABLED) then - __system_process(subsystem) + __system_process(subsystem, ...) end end @@ -3736,7 +3932,7 @@ local function __system_process(system) end if epilogue then - local success, result = __lua_xpcall(epilogue, __lua_debug_traceback) + local success, result = __lua_xpcall(epilogue, __lua_debug_traceback, ...) if not success then __error_fmt('system epilogue failed: %s', result) @@ -5092,14 +5288,25 @@ function __evolved_process(...) if __freelist_ids[system_primary] ~= system then __warning_fmt('the system (%s) is not alive and cannot be processed', __id_name(system)) - elseif __evolved_has(system, __DISABLED) then - -- the system is disabled, nothing to process else __system_process(system) end end end +---@param system evolved.system +---@param ... any processing payload +function __evolved_process_with(system, ...) + local system_primary = system % 2 ^ 20 + + if __freelist_ids[system_primary] ~= system then + __error_fmt('the system (%s) is not alive and cannot be processed', + __id_name(system)) + end + + __system_process(system, ...) +end + ---@param yesno boolean function __evolved_debug_mode(yesno) __debug_mode = yesno @@ -6337,6 +6544,7 @@ evolved.execute = __evolved_execute evolved.locate = __evolved_locate evolved.process = __evolved_process +evolved.process_with = __evolved_process_with evolved.debug_mode = __evolved_debug_mode evolved.collect_garbage = __evolved_collect_garbage diff --git a/example/main.lua b/example/main.lua index c5ed76b..a7ac0c6 100644 --- a/example/main.lua +++ b/example/main.lua @@ -12,10 +12,6 @@ local STAGES = { :build(), } -local UNIFORMS = { - DELTA_TIME = 1.0 / 60.0, -} - local FRAGMENTS = { POSITION_X = evolved.builder() :name('FRAGMENTS.POSITION_X') @@ -82,8 +78,7 @@ evolved.builder() :group(STAGES.ON_UPDATE) :include(FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) :include(FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y) - :execute(function(chunk, _, entity_count) - local delta_time = UNIFORMS.DELTA_TIME + :execute(function(chunk, _, entity_count, delta_time) local screen_width, screen_height = love.graphics.getDimensions() ---@type number[], number[] @@ -156,8 +151,7 @@ end ---@type love.update function love.update(dt) - UNIFORMS.DELTA_TIME = dt - evolved.process(STAGES.ON_UPDATE) + evolved.process_with(STAGES.ON_UPDATE, dt) end ---@type love.draw From a0a4a20c355730889a5dc3beb3fa7f340689a909 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 3 Jan 2026 15:52:01 +0700 Subject: [PATCH 2/8] =?UTF-8?q?Happy=20New=20Year!=20=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE.md | 2 +- evolved.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index a660e88..831ad5a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2024-2025, by Matvey Cherevko (blackmatov@gmail.com) +Copyright (C) 2024-2026, by Matvey Cherevko (blackmatov@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/evolved.lua b/evolved.lua index beae86d..e50df8a 100644 --- a/evolved.lua +++ b/evolved.lua @@ -5,7 +5,7 @@ local evolved = { __LICENSE = [[ MIT License - Copyright (C) 2024-2025, by Matvey Cherevko (blackmatov@gmail.com) + Copyright (C) 2024-2026, by Matvey Cherevko (blackmatov@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c29092c3e1cb92da281856c9a482b45d8080dd7a Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 4 Jan 2026 09:32:08 +0700 Subject: [PATCH 3/8] update embedded xpcall.lua to v1.0.1 --- evolved.lua | 204 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 119 insertions(+), 85 deletions(-) diff --git a/evolved.lua b/evolved.lua index e50df8a..c7888c1 100644 --- a/evolved.lua +++ b/evolved.lua @@ -340,157 +340,191 @@ end)() ---@type fun(f: function, e: function, ...): boolean, ... local __lua_xpcall = (function() - -- https://github.com/BlackMATov/xpcall.lua + -- https://github.com/BlackMATov/xpcall.lua/tree/v1.0.1 - local builtin_xpcall = xpcall + local __lua_xpcall = xpcall ---@diagnostic disable-next-line: redundant-parameter - if __lua_select(2, builtin_xpcall(function(a) return a end, function() end, 42)) == 42 then - -- use the built-in xpcall if it works with extra arguments as expected - return builtin_xpcall + if __lua_select(2, __lua_xpcall(function(a) return a end, function() end, 42)) == 42 then + -- use built-in xpcall if it works correctly with extra arguments + return __lua_xpcall end - local xpcall_function + local __xpcall_function - local xpcall_argument_1, xpcall_argument_2 - local xpcall_argument_3, xpcall_argument_4 - local xpcall_argument_5, xpcall_argument_6 - local xpcall_argument_7, xpcall_argument_8 + local __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 + local __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8 - local xpcall_argument_tail_list = {} - local xpcall_argument_tail_count = 0 + local __xpcall_argument_tail_list = __lua_setmetatable({}, { __mode = 'v' }) + local __xpcall_argument_tail_count = 0 + + local function ret_xpcall_function_1(...) + __xpcall_function = nil + __xpcall_argument_1 = nil + return ... + end + + local function ret_xpcall_function_2(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2 = nil, nil + return ... + end + + local function ret_xpcall_function_3(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3 = nil, nil, nil + return ... + end + + local function ret_xpcall_function_4(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = nil, nil, nil, nil + return ... + end + + local function ret_xpcall_function_5(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = nil, nil, nil, nil + __xpcall_argument_5 = nil + return ... + end + + local function ret_xpcall_function_6(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = nil, nil, nil, nil + __xpcall_argument_5, __xpcall_argument_6 = nil, nil + return ... + end + + local function ret_xpcall_function_7(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = nil, nil, nil, nil + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7 = nil, nil, nil + return ... + end + + local function ret_xpcall_function_8(...) + __xpcall_function = nil + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = nil, nil, nil, nil + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8 = nil, nil, nil, nil + return ... + end local function call_xpcall_function_1() - return xpcall_function( - xpcall_argument_1) + return ret_xpcall_function_1(__xpcall_function( + __xpcall_argument_1)) end local function call_xpcall_function_2() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2) + return ret_xpcall_function_2(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2)) end local function call_xpcall_function_3() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3) + return ret_xpcall_function_3(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3)) end local function call_xpcall_function_4() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4) + return ret_xpcall_function_4(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4)) end local function call_xpcall_function_5() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5) + return ret_xpcall_function_5(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5)) end local function call_xpcall_function_6() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6) + return ret_xpcall_function_6(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6)) end local function call_xpcall_function_7() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7) + return ret_xpcall_function_7(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7)) end local function call_xpcall_function_8() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7, xpcall_argument_8) + return ret_xpcall_function_8(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8)) end local function call_xpcall_function_N() - return xpcall_function( - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7, xpcall_argument_8, - __lua_table_unpack(xpcall_argument_tail_list, 1, xpcall_argument_tail_count)) + return ret_xpcall_function_8(__xpcall_function( + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8, + __lua_table_unpack(__xpcall_argument_tail_list, 1, __xpcall_argument_tail_count))) end - ---@type fun(f: function, e: function, ...): boolean, ... + ---@param f function + ---@param e function + ---@param ... any + ---@return boolean success + ---@return any ... results return function(f, e, ...) local argument_count = __lua_select('#', ...) if argument_count == 0 then - -- use the built-in xpcall without extra arguments - return builtin_xpcall(f, e) + -- no extra arguments, just use built-in xpcall + return __lua_xpcall(f, e) end - xpcall_function = f + __xpcall_function = f if argument_count <= 8 then if argument_count <= 4 then if argument_count <= 2 then if argument_count <= 1 then - xpcall_argument_1 = ... - return builtin_xpcall(call_xpcall_function_1, e) + __xpcall_argument_1 = ... + return __lua_xpcall(call_xpcall_function_1, e) else - xpcall_argument_1, xpcall_argument_2 = ... - return builtin_xpcall(call_xpcall_function_2, e) + __xpcall_argument_1, __xpcall_argument_2 = ... + return __lua_xpcall(call_xpcall_function_2, e) end else if argument_count <= 3 then - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3 = ... - return builtin_xpcall(call_xpcall_function_3, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3 = ... + return __lua_xpcall(call_xpcall_function_3, e) else - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4 = ... - return builtin_xpcall(call_xpcall_function_4, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4 = ... + return __lua_xpcall(call_xpcall_function_4, e) end end else if argument_count <= 6 then if argument_count <= 5 then - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5 = ... - return builtin_xpcall(call_xpcall_function_5, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5 = ... + return __lua_xpcall(call_xpcall_function_5, e) else - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6 = ... - return builtin_xpcall(call_xpcall_function_6, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6 = ... + return __lua_xpcall(call_xpcall_function_6, e) end else if argument_count <= 7 then - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7 = ... - return builtin_xpcall(call_xpcall_function_7, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7 = ... + return __lua_xpcall(call_xpcall_function_7, e) else - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7, xpcall_argument_8 = ... - return builtin_xpcall(call_xpcall_function_8, e) + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8 = ... + return __lua_xpcall(call_xpcall_function_8, e) end end end else - xpcall_argument_1, xpcall_argument_2, - xpcall_argument_3, xpcall_argument_4, - xpcall_argument_5, xpcall_argument_6, - xpcall_argument_7, xpcall_argument_8 = ... + __xpcall_argument_1, __xpcall_argument_2, __xpcall_argument_3, __xpcall_argument_4, + __xpcall_argument_5, __xpcall_argument_6, __xpcall_argument_7, __xpcall_argument_8 = ... end - xpcall_argument_tail_count = argument_count - 8 - local argument_tail_list = xpcall_argument_tail_list + local argument_tail_list = __xpcall_argument_tail_list + __xpcall_argument_tail_count = argument_count - 8 for i = 1, argument_count - 8, 8 do local argument_remaining = argument_count - 8 - i + 1 @@ -538,7 +572,7 @@ local __lua_xpcall = (function() end end - return builtin_xpcall(call_xpcall_function_N, e) + return __lua_xpcall(call_xpcall_function_N, e) end end)() From c9f4a7451809135003604e99160e3ac2ab5c430f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 6 Jan 2026 20:23:22 +0700 Subject: [PATCH 4/8] dummy eithers fragment --- evolved.lua | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/evolved.lua b/evolved.lua index c7888c1..da4c23e 100644 --- a/evolved.lua +++ b/evolved.lua @@ -133,6 +133,7 @@ local __major_queries = {} ---@type table local __entity_places = {} ---@type table +local __sorted_eithers = {} ---@type table> local __sorted_includes = {} ---@type table> local __sorted_excludes = {} ---@type table> local __sorted_requires = {} ---@type table> @@ -971,6 +972,7 @@ local __DUPLICATE = __acquire_id() local __PREFAB = __acquire_id() local __DISABLED = __acquire_id() +local __EITHERS = __acquire_id() local __INCLUDES = __acquire_id() local __EXCLUDES = __acquire_id() local __REQUIRES = __acquire_id() @@ -5990,6 +5992,31 @@ function __builder_mt:disabled() return self:set(__DISABLED) end +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function __builder_mt:either(...) + local argument_count = __lua_select('#', ...) + + if argument_count == 0 then + return self + end + + local either_list = self:get(__EITHERS) + local either_count = either_list and #either_list or 0 + + if either_count == 0 then + either_list = __list_new(argument_count) + end + + for argument_index = 1, argument_count do + ---@type evolved.fragment + local fragment = __lua_select(argument_index, ...) + either_list[either_count + argument_index] = fragment + end + + return self:set(__EITHERS, either_list) +end + ---@param ... evolved.fragment fragments ---@return evolved.builder builder function __builder_mt:include(...) @@ -6186,6 +6213,7 @@ __evolved_set(__DUPLICATE, __NAME, 'DUPLICATE') __evolved_set(__PREFAB, __NAME, 'PREFAB') __evolved_set(__DISABLED, __NAME, 'DISABLED') +__evolved_set(__EITHERS, __NAME, 'EITHERS') __evolved_set(__INCLUDES, __NAME, 'INCLUDES') __evolved_set(__EXCLUDES, __NAME, 'EXCLUDES') __evolved_set(__REQUIRES, __NAME, 'REQUIRES') @@ -6226,6 +6254,7 @@ __evolved_set(__DUPLICATE, __INTERNAL) __evolved_set(__PREFAB, __INTERNAL) __evolved_set(__DISABLED, __INTERNAL) +__evolved_set(__EITHERS, __INTERNAL) __evolved_set(__INCLUDES, __INTERNAL) __evolved_set(__EXCLUDES, __INTERNAL) __evolved_set(__REQUIRES, __INTERNAL) @@ -6271,6 +6300,9 @@ __evolved_set(__DISABLED, __TAG) __evolved_set(__DISABLED, __UNIQUE) __evolved_set(__DISABLED, __EXPLICIT) +__evolved_set(__EITHERS, __DEFAULT, __list_new()) +__evolved_set(__EITHERS, __DUPLICATE, __list_dup) + __evolved_set(__INCLUDES, __DEFAULT, __list_new()) __evolved_set(__INCLUDES, __DUPLICATE, __list_dup) @@ -6328,6 +6360,50 @@ local function __remove_query(query) __reset_query_chunks(query) end +--- +--- +--- +--- +--- + +---@param query evolved.query +---@param either_list evolved.fragment[] +__evolved_set(__EITHERS, __ON_SET, function(query, _, either_list) + __remove_query(query) + + local either_count = #either_list + + if either_count > 0 then + ---@type evolved.assoc_list + local sorted_eithers = __assoc_list_new(either_count) + + __assoc_list_move(either_list, 1, either_count, sorted_eithers) + __assoc_list_sort(sorted_eithers) + + __sorted_eithers[query] = sorted_eithers + else + __sorted_eithers[query] = nil + end + + __insert_query(query) + __update_major_chunks(query) +end) + +__evolved_set(__EITHERS, __ON_REMOVE, function(query) + __remove_query(query) + + __sorted_eithers[query] = nil + + __insert_query(query) + __update_major_chunks(query) +end) + +--- +--- +--- +--- +--- + ---@param query evolved.query ---@param include_list evolved.fragment[] __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list) @@ -6504,6 +6580,7 @@ evolved.DUPLICATE = __DUPLICATE evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED +evolved.EITHERS = __EITHERS evolved.INCLUDES = __INCLUDES evolved.EXCLUDES = __EXCLUDES evolved.REQUIRES = __REQUIRES From a5319351c14f3ef71898503b08f630e5dea62546 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 11 Jan 2026 20:33:17 +0700 Subject: [PATCH 5/8] first eithers impl --- develop/fuzzing/execute_fuzz.lua | 53 +++++++- evolved.lua | 222 +++++++++++++++++++++++-------- 2 files changed, 211 insertions(+), 64 deletions(-) diff --git a/develop/fuzzing/execute_fuzz.lua b/develop/fuzzing/execute_fuzz.lua index 11bf6c0..6894bc0 100644 --- a/develop/fuzzing/execute_fuzz.lua +++ b/develop/fuzzing/execute_fuzz.lua @@ -28,6 +28,20 @@ end ---@param query evolved.query local function generate_query(query) + local either_set = {} + local either_list = {} + local either_count = 0 + + for _ = 1, math.random(0, #all_fragment_list) do + local either = all_fragment_list[math.random(1, #all_fragment_list)] + + if not either_set[either] then + either_count = either_count + 1 + either_set[either] = either_count + either_list[either_count] = either + end + end + local include_set = {} local include_list = {} local include_count = 0 @@ -56,6 +70,10 @@ local function generate_query(query) end end + if either_count > 0 then + evo.set(query, evo.EITHERS, either_list) + end + if include_count > 0 then evo.set(query, evo.INCLUDES, include_list) end @@ -171,9 +189,19 @@ local function execute_query(query) local query_chunk_set = {} local query_entity_set = {} + local query_either_list = evo.get(query, evo.EITHERS) or {} local query_include_list = evo.get(query, evo.INCLUDES) or {} local query_exclude_list = evo.get(query, evo.EXCLUDES) or {} + local query_either_count = #query_either_list + local query_include_count = #query_include_list + local query_exclude_count = #query_exclude_list + + local query_either_set = {} + for _, either in ipairs(query_either_list) do + query_either_set[either] = true + end + local query_include_set = {} for _, include in ipairs(query_include_list) do query_include_set[include] = true @@ -189,19 +217,29 @@ local function execute_query(query) query_entity_set[entity] = true end - assert(chunk:has_all(__table_unpack(query_include_list))) - assert(not chunk:has_any(__table_unpack(query_exclude_list))) + if query_either_count > 0 then + assert(chunk:has_any(__table_unpack(query_either_list))) + end + + if query_include_count > 0 then + assert(chunk:has_all(__table_unpack(query_include_list))) + end + + if query_exclude_count > 0 then + assert(not chunk:has_any(__table_unpack(query_exclude_list))) + end end for i = 1, all_entity_count do local entity = all_entity_list[i] local is_entity_matched = - evo.has_all(entity, __table_unpack(query_include_list)) - and not evo.has_any(entity, __table_unpack(query_exclude_list)) + (query_either_count == 0 or evo.has_any(entity, __table_unpack(query_either_list))) and + (query_include_count == 0 or evo.has_all(entity, __table_unpack(query_include_list))) and + (query_exclude_count == 0 or not evo.has_any(entity, __table_unpack(query_exclude_list))) for fragment in evo.each(entity) do - if evo.has(fragment, evo.EXPLICIT) and not query_include_set[fragment] then + if evo.has(fragment, evo.EXPLICIT) and not query_either_set[fragment] and not query_include_set[fragment] then is_entity_matched = false end end @@ -236,7 +274,10 @@ for _ = 1, math.random(1, 5) do if math.random(1, 2) == 1 then generate_query(query) else - if math.random(1, 2) == 1 then + local r = math.random(1, 3) + if r == 1 then + evo.remove(query, evo.EITHERS) + elseif r == 2 then evo.remove(query, evo.INCLUDES) else evo.remove(query, evo.EXCLUDES) diff --git a/evolved.lua b/evolved.lua index da4c23e..d2c3e1f 100644 --- a/evolved.lua +++ b/evolved.lua @@ -81,7 +81,9 @@ local evolved = { ---@field package [1] integer structural_changes ---@field package [2] evolved.chunk[] chunk_stack ---@field package [3] integer chunk_stack_size ----@field package [4] table? exclude_set +---@field package [4] table? either_set +---@field package [5] table? include_set +---@field package [6] table? exclude_set ---@alias evolved.each_iterator fun( --- state: evolved.each_state?): @@ -1108,6 +1110,9 @@ local __trace_minor_chunks local __cache_query_chunks local __reset_query_chunks +local __query_major_matches +local __query_minor_matches + local __update_major_chunks local __update_major_chunks_trace @@ -1117,7 +1122,6 @@ local __chunk_without_fragment local __chunk_without_fragments local __chunk_without_unique_fragments -local __chunk_matches local __chunk_requires local __chunk_fragments local __chunk_components @@ -1399,7 +1403,7 @@ function __update_chunk_queries(chunk) local major_query_chunks = __query_chunks[major_query] if major_query_chunks then - if __chunk_matches(chunk, major_query) then + if __query_major_matches(chunk, major_query) then __assoc_list_insert(major_query_chunks, chunk) else __assoc_list_remove(major_query_chunks, chunk) @@ -1572,20 +1576,37 @@ end function __cache_query_chunks(query) __reset_query_chunks(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 - if query_include_count == 0 then - __error_fmt('the query (%s) has no include fragments and cannot be cached', - __id_name(query)) - end - ---@type evolved.assoc_list local query_chunks = __assoc_list_new(4) __query_chunks[query] = query_chunks - do + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_chunks = __major_chunks[query_either] + 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] + + if __query_major_matches(major_chunk, query) then + __assoc_list_insert(query_chunks, major_chunk) + end + end + end + end + + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_chunks = __major_chunks[query_major] @@ -1595,7 +1616,7 @@ function __cache_query_chunks(query) for major_chunk_index = 1, major_chunk_count do local major_chunk = major_chunk_list[major_chunk_index] - if __chunk_matches(major_chunk, query) then + if __query_major_matches(major_chunk, query) then __assoc_list_insert(query_chunks, major_chunk) end end @@ -1609,6 +1630,87 @@ function __reset_query_chunks(query) __query_chunks[query] = nil end +---@param chunk evolved.chunk +---@param query evolved.query +---@return boolean +---@nodiscard +function __query_major_matches(chunk, query) + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set + local query_include_count = query_includes and query_includes.__item_count or 0 + + local query_either_index = query_either_count > 0 and query_either_set[chunk.__fragment] or nil + local query_include_index = query_include_count > 0 and query_include_set[chunk.__fragment] or nil + + return ( + (query_include_index ~= nil and query_include_index == query_include_count) or + (query_either_index ~= nil and not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_index - 1)) + ) and __query_minor_matches(chunk, query) +end + +---@param chunk evolved.chunk +---@param query evolved.query +---@return boolean +---@nodiscard +function __query_minor_matches(chunk, query) + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + + if query_either_count > 0 then + if not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_count) then + return false + end + end + + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set + local query_include_list = query_includes and query_includes.__item_list + local query_include_count = query_includes and query_includes.__item_count or 0 + + if query_include_count > 0 then + if not __chunk_has_all_fragment_list(chunk, query_include_list, query_include_count) then + return false + end + end + + local query_excludes = __sorted_excludes[query] + local query_exclude_list = query_excludes and query_excludes.__item_list + local query_exclude_count = query_excludes and query_excludes.__item_count or 0 + + if query_exclude_count > 0 then + if __chunk_has_any_fragment_list(chunk, query_exclude_list, query_exclude_count) then + return false + end + end + + if chunk.__has_explicit_fragments then + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + + for chunk_fragment_index = 1, chunk_fragment_count do + local chunk_fragment = chunk_fragment_list[chunk_fragment_index] + + local is_chunk_fragment_matched = + (not __evolved_has(chunk_fragment, __EXPLICIT)) or + (query_either_count > 0 and query_either_set[chunk_fragment]) or + (query_include_count > 0 and query_include_set[chunk_fragment]) + + if not is_chunk_fragment_matched then + return false + end + end + end + + return true +end + ---@param major evolved.fragment function __update_major_chunks(major) if __defer_depth > 0 then @@ -1789,50 +1891,6 @@ function __chunk_without_unique_fragments(chunk) return sib_chunk end ----@param chunk evolved.chunk ----@param query evolved.query ----@return boolean ----@nodiscard -function __chunk_matches(chunk, query) - local query_includes = __sorted_includes[query] - local query_include_set = query_includes and query_includes.__item_set - local query_include_list = query_includes and query_includes.__item_list - local query_include_count = query_includes and query_includes.__item_count or 0 - - if query_include_count > 0 then - if not __chunk_has_all_fragment_list(chunk, query_include_list, query_include_count) then - return false - end - elseif chunk.__has_explicit_fragments then - return false - end - - local query_excludes = __sorted_excludes[query] - local query_exclude_list = query_excludes and query_excludes.__item_list - local query_exclude_count = query_excludes and query_excludes.__item_count or 0 - - if query_exclude_count > 0 then - if __chunk_has_any_fragment_list(chunk, query_exclude_list, query_exclude_count) then - return false - end - end - - if chunk.__has_explicit_fragments then - local chunk_fragment_list = chunk.__fragment_list - local chunk_fragment_count = chunk.__fragment_count - - for chunk_fragment_index = 1, chunk_fragment_count do - local chunk_fragment = chunk_fragment_list[chunk_fragment_index] - - if not query_include_set[chunk_fragment] and __evolved_has(chunk_fragment, __EXPLICIT) then - return false - end - end - end - - return true -end - ---@param chunk evolved.chunk ---@return evolved.chunk ---@nodiscard @@ -3864,7 +3922,9 @@ function __iterator_fns.__execute_iterator(execute_state) 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] + local either_set = execute_state[4] + local include_set = execute_state[5] + local exclude_set = execute_state[6] if structural_changes ~= __structural_changes then __error_fmt('structural changes are prohibited during iteration') @@ -3884,7 +3944,9 @@ function __iterator_fns.__execute_iterator(execute_state) local chunk_child_fragment = chunk_child.__fragment local is_chunk_child_matched = - (not chunk_child.__has_explicit_major) and + (not chunk_child.__has_explicit_major or ( + (either_set and either_set[chunk_child_fragment]) or + (include_set and include_set[chunk_child_fragment]))) and (not exclude_set or not exclude_set[chunk_child_fragment]) if is_chunk_child_matched then @@ -5233,14 +5295,19 @@ function __evolved_execute(query) local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set local query_include_count = query_includes and query_includes.__item_count or 0 local query_excludes = __sorted_excludes[query] local query_exclude_set = query_excludes and query_excludes.__item_set local query_exclude_count = query_excludes and query_excludes.__item_count or 0 - if query_include_count > 0 then + if query_either_count > 0 or query_include_count > 0 then local query_chunks = __query_chunks[query] or __cache_query_chunks(query) local query_chunk_list = query_chunks and query_chunks.__item_list local query_chunk_count = query_chunks and query_chunks.__item_count or 0 @@ -5281,7 +5348,9 @@ function __evolved_execute(query) execute_state[1] = __structural_changes execute_state[2] = chunk_stack execute_state[3] = chunk_stack_size - execute_state[4] = query_exclude_set + execute_state[4] = query_either_set + execute_state[5] = query_include_set + execute_state[6] = query_exclude_set return __iterator_fns.__execute_iterator, execute_state end @@ -6325,15 +6394,36 @@ __evolved_set(__ON_REMOVE, __UNIQUE) ---@param query evolved.query local function __insert_query(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_queries = __major_queries[query_either] + + if not major_queries then + ---@type evolved.assoc_list + major_queries = __assoc_list_new(4) + __major_queries[query_either] = major_queries + end + + __assoc_list_insert(major_queries, query) + end + end + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_queries = __major_queries[query_major] if not major_queries then + ---@type evolved.assoc_list major_queries = __assoc_list_new(4) __major_queries[query_major] = major_queries end @@ -6344,10 +6434,26 @@ end ---@param query evolved.query local function __remove_query(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_queries = __major_queries[query_either] + + if major_queries and __assoc_list_remove(major_queries, query) == 0 then + __major_queries[query_either] = nil + end + end + end + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_queries = __major_queries[query_major] From 52c898f9127581911ea15e63800792bc04ba20d3 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 11 Jan 2026 20:49:09 +0700 Subject: [PATCH 6/8] rename eithers to variants --- develop/fuzzing/execute_fuzz.lua | 64 +++---- evolved.lua | 306 +++++++++++++++---------------- 2 files changed, 185 insertions(+), 185 deletions(-) diff --git a/develop/fuzzing/execute_fuzz.lua b/develop/fuzzing/execute_fuzz.lua index 6894bc0..044c007 100644 --- a/develop/fuzzing/execute_fuzz.lua +++ b/develop/fuzzing/execute_fuzz.lua @@ -28,20 +28,6 @@ end ---@param query evolved.query local function generate_query(query) - local either_set = {} - local either_list = {} - local either_count = 0 - - for _ = 1, math.random(0, #all_fragment_list) do - local either = all_fragment_list[math.random(1, #all_fragment_list)] - - if not either_set[either] then - either_count = either_count + 1 - either_set[either] = either_count - either_list[either_count] = either - end - end - local include_set = {} local include_list = {} local include_count = 0 @@ -70,8 +56,18 @@ local function generate_query(query) end end - if either_count > 0 then - evo.set(query, evo.EITHERS, either_list) + local variant_set = {} + local variant_list = {} + local variant_count = 0 + + for _ = 1, math.random(0, #all_fragment_list) do + local variant = all_fragment_list[math.random(1, #all_fragment_list)] + + if not variant_set[variant] then + variant_count = variant_count + 1 + variant_set[variant] = variant_count + variant_list[variant_count] = variant + end end if include_count > 0 then @@ -81,6 +77,10 @@ local function generate_query(query) if exclude_count > 0 then evo.set(query, evo.EXCLUDES, exclude_list) end + + if variant_count > 0 then + evo.set(query, evo.VARIANTS, variant_list) + end end ---@param query_count integer @@ -189,24 +189,24 @@ local function execute_query(query) local query_chunk_set = {} local query_entity_set = {} - local query_either_list = evo.get(query, evo.EITHERS) or {} local query_include_list = evo.get(query, evo.INCLUDES) or {} local query_exclude_list = evo.get(query, evo.EXCLUDES) or {} + local query_variant_list = evo.get(query, evo.VARIANTS) or {} - local query_either_count = #query_either_list local query_include_count = #query_include_list local query_exclude_count = #query_exclude_list - - local query_either_set = {} - for _, either in ipairs(query_either_list) do - query_either_set[either] = true - end + local query_variant_count = #query_variant_list local query_include_set = {} for _, include in ipairs(query_include_list) do query_include_set[include] = true end + local query_variant_set = {} + for _, variant in ipairs(query_variant_list) do + query_variant_set[variant] = true + end + for chunk, entity_list, entity_count in evo.execute(query) do assert(not query_chunk_set[chunk]) query_chunk_set[chunk] = true @@ -217,10 +217,6 @@ local function execute_query(query) query_entity_set[entity] = true end - if query_either_count > 0 then - assert(chunk:has_any(__table_unpack(query_either_list))) - end - if query_include_count > 0 then assert(chunk:has_all(__table_unpack(query_include_list))) end @@ -228,18 +224,22 @@ local function execute_query(query) if query_exclude_count > 0 then assert(not chunk:has_any(__table_unpack(query_exclude_list))) end + + if query_variant_count > 0 then + assert(chunk:has_any(__table_unpack(query_variant_list))) + end end for i = 1, all_entity_count do local entity = all_entity_list[i] local is_entity_matched = - (query_either_count == 0 or evo.has_any(entity, __table_unpack(query_either_list))) and + (query_variant_count == 0 or evo.has_any(entity, __table_unpack(query_variant_list))) and (query_include_count == 0 or evo.has_all(entity, __table_unpack(query_include_list))) and (query_exclude_count == 0 or not evo.has_any(entity, __table_unpack(query_exclude_list))) for fragment in evo.each(entity) do - if evo.has(fragment, evo.EXPLICIT) and not query_either_set[fragment] and not query_include_set[fragment] then + if evo.has(fragment, evo.EXPLICIT) and not query_variant_set[fragment] and not query_include_set[fragment] then is_entity_matched = false end end @@ -276,11 +276,11 @@ for _ = 1, math.random(1, 5) do else local r = math.random(1, 3) if r == 1 then - evo.remove(query, evo.EITHERS) - elseif r == 2 then evo.remove(query, evo.INCLUDES) - else + elseif r == 2 then evo.remove(query, evo.EXCLUDES) + else + evo.remove(query, evo.VARIANTS) end end end diff --git a/evolved.lua b/evolved.lua index d2c3e1f..535df2a 100644 --- a/evolved.lua +++ b/evolved.lua @@ -81,9 +81,9 @@ local evolved = { ---@field package [1] integer structural_changes ---@field package [2] evolved.chunk[] chunk_stack ---@field package [3] integer chunk_stack_size ----@field package [4] table? either_set ----@field package [5] table? include_set ----@field package [6] table? exclude_set +---@field package [4] table? include_set +---@field package [5] table? exclude_set +---@field package [6] table? variant_set ---@alias evolved.each_iterator fun( --- state: evolved.each_state?): @@ -135,9 +135,9 @@ local __major_queries = {} ---@type table local __entity_places = {} ---@type table -local __sorted_eithers = {} ---@type table> local __sorted_includes = {} ---@type table> local __sorted_excludes = {} ---@type table> +local __sorted_variants = {} ---@type table> local __sorted_requires = {} ---@type table> local __subsystem_groups = {} ---@type table @@ -974,9 +974,9 @@ local __DUPLICATE = __acquire_id() local __PREFAB = __acquire_id() local __DISABLED = __acquire_id() -local __EITHERS = __acquire_id() local __INCLUDES = __acquire_id() local __EXCLUDES = __acquire_id() +local __VARIANTS = __acquire_id() local __REQUIRES = __acquire_id() local __ON_SET = __acquire_id() @@ -1576,36 +1576,18 @@ end function __cache_query_chunks(query) __reset_query_chunks(query) - local query_eithers = __sorted_eithers[query] - local query_either_list = query_eithers and query_eithers.__item_list - local query_either_count = query_eithers and query_eithers.__item_count or 0 - local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 + local query_variants = __sorted_variants[query] + local query_variant_list = query_variants and query_variants.__item_list + local query_variant_count = query_variants and query_variants.__item_count or 0 + ---@type evolved.assoc_list local query_chunks = __assoc_list_new(4) __query_chunks[query] = query_chunks - for query_either_index = 1, query_either_count do - local query_either = query_either_list[query_either_index] - - if query_include_count == 0 or query_either > query_include_list[query_include_count] then - local major_chunks = __major_chunks[query_either] - 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] - - if __query_major_matches(major_chunk, query) then - __assoc_list_insert(query_chunks, major_chunk) - end - end - end - end - if query_include_count > 0 then local query_major = query_include_list[query_include_count] @@ -1622,6 +1604,24 @@ function __cache_query_chunks(query) end end + for query_variant_index = 1, query_variant_count do + local query_variant = query_variant_list[query_variant_index] + + if query_include_count == 0 or query_variant > query_include_list[query_include_count] then + local major_chunks = __major_chunks[query_variant] + 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] + + if __query_major_matches(major_chunk, query) then + __assoc_list_insert(query_chunks, major_chunk) + end + end + end + end + return query_chunks end @@ -1635,21 +1635,21 @@ end ---@return boolean ---@nodiscard function __query_major_matches(chunk, query) - local query_eithers = __sorted_eithers[query] - local query_either_set = query_eithers and query_eithers.__item_set - local query_either_list = query_eithers and query_eithers.__item_list - local query_either_count = query_eithers and query_eithers.__item_count or 0 - local query_includes = __sorted_includes[query] local query_include_set = query_includes and query_includes.__item_set local query_include_count = query_includes and query_includes.__item_count or 0 - local query_either_index = query_either_count > 0 and query_either_set[chunk.__fragment] or nil + local query_variants = __sorted_variants[query] + local query_variant_set = query_variants and query_variants.__item_set + local query_variant_list = query_variants and query_variants.__item_list + local query_variant_count = query_variants and query_variants.__item_count or 0 + local query_include_index = query_include_count > 0 and query_include_set[chunk.__fragment] or nil + local query_variant_index = query_variant_count > 0 and query_variant_set[chunk.__fragment] or nil return ( (query_include_index ~= nil and query_include_index == query_include_count) or - (query_either_index ~= nil and not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_index - 1)) + (query_variant_index ~= nil and not __chunk_has_any_fragment_list(chunk, query_variant_list, query_variant_index - 1)) ) and __query_minor_matches(chunk, query) end @@ -1658,17 +1658,6 @@ end ---@return boolean ---@nodiscard function __query_minor_matches(chunk, query) - local query_eithers = __sorted_eithers[query] - local query_either_set = query_eithers and query_eithers.__item_set - local query_either_list = query_eithers and query_eithers.__item_list - local query_either_count = query_eithers and query_eithers.__item_count or 0 - - if query_either_count > 0 then - if not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_count) then - return false - end - end - local query_includes = __sorted_includes[query] local query_include_set = query_includes and query_includes.__item_set local query_include_list = query_includes and query_includes.__item_list @@ -1690,6 +1679,17 @@ function __query_minor_matches(chunk, query) end end + local query_variants = __sorted_variants[query] + local query_variant_set = query_variants and query_variants.__item_set + local query_variant_list = query_variants and query_variants.__item_list + local query_variant_count = query_variants and query_variants.__item_count or 0 + + if query_variant_count > 0 then + if not __chunk_has_any_fragment_list(chunk, query_variant_list, query_variant_count) then + return false + end + end + if chunk.__has_explicit_fragments then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -1699,7 +1699,7 @@ function __query_minor_matches(chunk, query) local is_chunk_fragment_matched = (not __evolved_has(chunk_fragment, __EXPLICIT)) or - (query_either_count > 0 and query_either_set[chunk_fragment]) or + (query_variant_count > 0 and query_variant_set[chunk_fragment]) or (query_include_count > 0 and query_include_set[chunk_fragment]) if not is_chunk_fragment_matched then @@ -3922,9 +3922,9 @@ function __iterator_fns.__execute_iterator(execute_state) local structural_changes = execute_state[1] local chunk_stack = execute_state[2] local chunk_stack_size = execute_state[3] - local either_set = execute_state[4] - local include_set = execute_state[5] - local exclude_set = execute_state[6] + local include_set = execute_state[4] + local exclude_set = execute_state[5] + local variant_set = execute_state[6] if structural_changes ~= __structural_changes then __error_fmt('structural changes are prohibited during iteration') @@ -3945,8 +3945,8 @@ function __iterator_fns.__execute_iterator(execute_state) local is_chunk_child_matched = (not chunk_child.__has_explicit_major or ( - (either_set and either_set[chunk_child_fragment]) or - (include_set and include_set[chunk_child_fragment]))) and + (include_set and include_set[chunk_child_fragment]) or + (variant_set and variant_set[chunk_child_fragment]))) and (not exclude_set or not exclude_set[chunk_child_fragment]) if is_chunk_child_matched then @@ -5295,10 +5295,6 @@ function __evolved_execute(query) local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 - local query_eithers = __sorted_eithers[query] - local query_either_set = query_eithers and query_eithers.__item_set - local query_either_count = query_eithers and query_eithers.__item_count or 0 - local query_includes = __sorted_includes[query] local query_include_set = query_includes and query_includes.__item_set local query_include_count = query_includes and query_includes.__item_count or 0 @@ -5307,7 +5303,11 @@ function __evolved_execute(query) local query_exclude_set = query_excludes and query_excludes.__item_set local query_exclude_count = query_excludes and query_excludes.__item_count or 0 - if query_either_count > 0 or query_include_count > 0 then + local query_variants = __sorted_variants[query] + local query_variant_set = query_variants and query_variants.__item_set + local query_variant_count = query_variants and query_variants.__item_count or 0 + + if query_include_count > 0 or query_variant_count > 0 then local query_chunks = __query_chunks[query] or __cache_query_chunks(query) local query_chunk_list = query_chunks and query_chunks.__item_list local query_chunk_count = query_chunks and query_chunks.__item_count or 0 @@ -5348,9 +5348,9 @@ function __evolved_execute(query) execute_state[1] = __structural_changes execute_state[2] = chunk_stack execute_state[3] = chunk_stack_size - execute_state[4] = query_either_set - execute_state[5] = query_include_set - execute_state[6] = query_exclude_set + execute_state[4] = query_include_set + execute_state[5] = query_exclude_set + execute_state[6] = query_variant_set return __iterator_fns.__execute_iterator, execute_state end @@ -6063,77 +6063,77 @@ end ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:either(...) +function __builder_mt:include(...) local argument_count = __lua_select('#', ...) if argument_count == 0 then return self end - local either_list = self:get(__EITHERS) - local either_count = either_list and #either_list or 0 + local include_list = self:get(__INCLUDES) + local include_count = include_list and #include_list or 0 - if either_count == 0 then - either_list = __list_new(argument_count) + if include_count == 0 then + include_list = __list_new(argument_count) end for argument_index = 1, argument_count do ---@type evolved.fragment local fragment = __lua_select(argument_index, ...) - either_list[either_count + argument_index] = fragment + include_list[include_count + argument_index] = fragment end - return self:set(__EITHERS, either_list) + return self:set(__INCLUDES, include_list) end ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:include(...) +function __builder_mt:exclude(...) local argument_count = __lua_select('#', ...) if argument_count == 0 then return self end - local include_list = self:get(__INCLUDES) - local include_count = include_list and #include_list or 0 + local exclude_list = self:get(__EXCLUDES) + local exclude_count = exclude_list and #exclude_list or 0 - if include_count == 0 then - include_list = __list_new(argument_count) + if exclude_count == 0 then + exclude_list = __list_new(argument_count) end for argument_index = 1, argument_count do ---@type evolved.fragment local fragment = __lua_select(argument_index, ...) - include_list[include_count + argument_index] = fragment + exclude_list[exclude_count + argument_index] = fragment end - return self:set(__INCLUDES, include_list) + return self:set(__EXCLUDES, exclude_list) end ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:exclude(...) +function __builder_mt:variant(...) local argument_count = __lua_select('#', ...) if argument_count == 0 then return self end - local exclude_list = self:get(__EXCLUDES) - local exclude_count = exclude_list and #exclude_list or 0 + local variant_list = self:get(__VARIANTS) + local variant_count = variant_list and #variant_list or 0 - if exclude_count == 0 then - exclude_list = __list_new(argument_count) + if variant_count == 0 then + variant_list = __list_new(argument_count) end for argument_index = 1, argument_count do ---@type evolved.fragment local fragment = __lua_select(argument_index, ...) - exclude_list[exclude_count + argument_index] = fragment + variant_list[variant_count + argument_index] = fragment end - return self:set(__EXCLUDES, exclude_list) + return self:set(__VARIANTS, variant_list) end ---@param ... evolved.fragment fragments @@ -6282,9 +6282,9 @@ __evolved_set(__DUPLICATE, __NAME, 'DUPLICATE') __evolved_set(__PREFAB, __NAME, 'PREFAB') __evolved_set(__DISABLED, __NAME, 'DISABLED') -__evolved_set(__EITHERS, __NAME, 'EITHERS') __evolved_set(__INCLUDES, __NAME, 'INCLUDES') __evolved_set(__EXCLUDES, __NAME, 'EXCLUDES') +__evolved_set(__VARIANTS, __NAME, 'VARIANTS') __evolved_set(__REQUIRES, __NAME, 'REQUIRES') __evolved_set(__ON_SET, __NAME, 'ON_SET') @@ -6323,9 +6323,9 @@ __evolved_set(__DUPLICATE, __INTERNAL) __evolved_set(__PREFAB, __INTERNAL) __evolved_set(__DISABLED, __INTERNAL) -__evolved_set(__EITHERS, __INTERNAL) __evolved_set(__INCLUDES, __INTERNAL) __evolved_set(__EXCLUDES, __INTERNAL) +__evolved_set(__VARIANTS, __INTERNAL) __evolved_set(__REQUIRES, __INTERNAL) __evolved_set(__ON_SET, __INTERNAL) @@ -6369,15 +6369,15 @@ __evolved_set(__DISABLED, __TAG) __evolved_set(__DISABLED, __UNIQUE) __evolved_set(__DISABLED, __EXPLICIT) -__evolved_set(__EITHERS, __DEFAULT, __list_new()) -__evolved_set(__EITHERS, __DUPLICATE, __list_dup) - __evolved_set(__INCLUDES, __DEFAULT, __list_new()) __evolved_set(__INCLUDES, __DUPLICATE, __list_dup) __evolved_set(__EXCLUDES, __DEFAULT, __list_new()) __evolved_set(__EXCLUDES, __DUPLICATE, __list_dup) +__evolved_set(__VARIANTS, __DEFAULT, __list_new()) +__evolved_set(__VARIANTS, __DUPLICATE, __list_dup) + __evolved_set(__REQUIRES, __DEFAULT, __list_new()) __evolved_set(__REQUIRES, __DUPLICATE, __list_dup) @@ -6394,29 +6394,13 @@ __evolved_set(__ON_REMOVE, __UNIQUE) ---@param query evolved.query local function __insert_query(query) - local query_eithers = __sorted_eithers[query] - local query_either_list = query_eithers and query_eithers.__item_list - local query_either_count = query_eithers and query_eithers.__item_count or 0 - local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 - for query_either_index = 1, query_either_count do - local query_either = query_either_list[query_either_index] - - if query_include_count == 0 or query_either > query_include_list[query_include_count] then - local major_queries = __major_queries[query_either] - - if not major_queries then - ---@type evolved.assoc_list - major_queries = __assoc_list_new(4) - __major_queries[query_either] = major_queries - end - - __assoc_list_insert(major_queries, query) - end - end + local query_variants = __sorted_variants[query] + local query_variant_list = query_variants and query_variants.__item_list + local query_variant_count = query_variants and query_variants.__item_count or 0 if query_include_count > 0 then local query_major = query_include_list[query_include_count] @@ -6430,29 +6414,33 @@ local function __insert_query(query) __assoc_list_insert(major_queries, query) end + + for query_variant_index = 1, query_variant_count do + local query_variant = query_variant_list[query_variant_index] + + if query_include_count == 0 or query_variant > query_include_list[query_include_count] then + local major_queries = __major_queries[query_variant] + + if not major_queries then + ---@type evolved.assoc_list + major_queries = __assoc_list_new(4) + __major_queries[query_variant] = major_queries + end + + __assoc_list_insert(major_queries, query) + end + end end ---@param query evolved.query local function __remove_query(query) - local query_eithers = __sorted_eithers[query] - local query_either_list = query_eithers and query_eithers.__item_list - local query_either_count = query_eithers and query_eithers.__item_count or 0 - local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 - for query_either_index = 1, query_either_count do - local query_either = query_either_list[query_either_index] - - if query_include_count == 0 or query_either > query_include_list[query_include_count] then - local major_queries = __major_queries[query_either] - - if major_queries and __assoc_list_remove(major_queries, query) == 0 then - __major_queries[query_either] = nil - end - end - end + local query_variants = __sorted_variants[query] + local query_variant_list = query_variants and query_variants.__item_list + local query_variant_count = query_variants and query_variants.__item_count or 0 if query_include_count > 0 then local query_major = query_include_list[query_include_count] @@ -6463,6 +6451,18 @@ local function __remove_query(query) end end + for query_variant_index = 1, query_variant_count do + local query_variant = query_variant_list[query_variant_index] + + if query_include_count == 0 or query_variant > query_include_list[query_include_count] then + local major_queries = __major_queries[query_variant] + + if major_queries and __assoc_list_remove(major_queries, query) == 0 then + __major_queries[query_variant] = nil + end + end + end + __reset_query_chunks(query) end @@ -6473,32 +6473,32 @@ end --- ---@param query evolved.query ----@param either_list evolved.fragment[] -__evolved_set(__EITHERS, __ON_SET, function(query, _, either_list) +---@param include_list evolved.fragment[] +__evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list) __remove_query(query) - local either_count = #either_list + local include_count = #include_list - if either_count > 0 then + if include_count > 0 then ---@type evolved.assoc_list - local sorted_eithers = __assoc_list_new(either_count) + local sorted_includes = __assoc_list_new(include_count) - __assoc_list_move(either_list, 1, either_count, sorted_eithers) - __assoc_list_sort(sorted_eithers) + __assoc_list_move(include_list, 1, include_count, sorted_includes) + __assoc_list_sort(sorted_includes) - __sorted_eithers[query] = sorted_eithers + __sorted_includes[query] = sorted_includes else - __sorted_eithers[query] = nil + __sorted_includes[query] = nil end __insert_query(query) __update_major_chunks(query) end) -__evolved_set(__EITHERS, __ON_REMOVE, function(query) +__evolved_set(__INCLUDES, __ON_REMOVE, function(query) __remove_query(query) - __sorted_eithers[query] = nil + __sorted_includes[query] = nil __insert_query(query) __update_major_chunks(query) @@ -6511,32 +6511,32 @@ end) --- ---@param query evolved.query ----@param include_list evolved.fragment[] -__evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list) +---@param exclude_list evolved.fragment[] +__evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list) __remove_query(query) - local include_count = #include_list + local exclude_count = #exclude_list - if include_count > 0 then + if exclude_count > 0 then ---@type evolved.assoc_list - local sorted_includes = __assoc_list_new(include_count) + local sorted_excludes = __assoc_list_new(exclude_count) - __assoc_list_move(include_list, 1, include_count, sorted_includes) - __assoc_list_sort(sorted_includes) + __assoc_list_move(exclude_list, 1, exclude_count, sorted_excludes) + __assoc_list_sort(sorted_excludes) - __sorted_includes[query] = sorted_includes + __sorted_excludes[query] = sorted_excludes else - __sorted_includes[query] = nil + __sorted_excludes[query] = nil end __insert_query(query) __update_major_chunks(query) end) -__evolved_set(__INCLUDES, __ON_REMOVE, function(query) +__evolved_set(__EXCLUDES, __ON_REMOVE, function(query) __remove_query(query) - __sorted_includes[query] = nil + __sorted_excludes[query] = nil __insert_query(query) __update_major_chunks(query) @@ -6549,32 +6549,32 @@ end) --- ---@param query evolved.query ----@param exclude_list evolved.fragment[] -__evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list) +---@param variant_list evolved.fragment[] +__evolved_set(__VARIANTS, __ON_SET, function(query, _, variant_list) __remove_query(query) - local exclude_count = #exclude_list + local variant_count = #variant_list - if exclude_count > 0 then + if variant_count > 0 then ---@type evolved.assoc_list - local sorted_excludes = __assoc_list_new(exclude_count) + local sorted_variants = __assoc_list_new(variant_count) - __assoc_list_move(exclude_list, 1, exclude_count, sorted_excludes) - __assoc_list_sort(sorted_excludes) + __assoc_list_move(variant_list, 1, variant_count, sorted_variants) + __assoc_list_sort(sorted_variants) - __sorted_excludes[query] = sorted_excludes + __sorted_variants[query] = sorted_variants else - __sorted_excludes[query] = nil + __sorted_variants[query] = nil end __insert_query(query) __update_major_chunks(query) end) -__evolved_set(__EXCLUDES, __ON_REMOVE, function(query) +__evolved_set(__VARIANTS, __ON_REMOVE, function(query) __remove_query(query) - __sorted_excludes[query] = nil + __sorted_variants[query] = nil __insert_query(query) __update_major_chunks(query) @@ -6686,9 +6686,9 @@ evolved.DUPLICATE = __DUPLICATE evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED -evolved.EITHERS = __EITHERS evolved.INCLUDES = __INCLUDES evolved.EXCLUDES = __EXCLUDES +evolved.VARIANTS = __VARIANTS evolved.REQUIRES = __REQUIRES evolved.ON_SET = __ON_SET From e396c320ee826a79abb3d477235d3eb75947b7e5 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 11 Jan 2026 21:08:22 +0700 Subject: [PATCH 7/8] update readme and teal defs (VARIANTS fragment) --- README.md | 26 +++++++++++++++++++++++--- evolved.d.tl | 2 ++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ddc1784..5e1f109 100644 --- a/README.md +++ b/README.md @@ -588,16 +588,22 @@ evolved.set(entity, fragment, 42) One of the most important features of any ECS library is the ability to process entities by filters or queries. `evolved.lua` provides a simple and efficient way to do this. -First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes). These fragments expect a list of fragments as their components. +First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: [`evolved.INCLUDES`](#evolvedincludes), [`evolved.EXCLUDES`](#evolvedexcludes), and [`evolved.VARIANTS`](#evolvedvariants). These fragments expect a list of fragments as their components. + +- [`evolved.INCLUDES`](#evolvedincludes) is used to specify fragments that must be present in the entity; +- [`evolved.EXCLUDES`](#evolvedexcludes) is used to specify fragments that must not be present in the entity; +- [`evolved.VARIANTS`](#evolvedvariants) is used to specify fragments where at least one must be present in the entity. ```lua local evolved = require 'evolved' local health, poisoned, resistant = evolved.id(3) +local alive, undead = evolved.id(2) local query = evolved.id() evolved.set(query, evolved.INCLUDES, { health, poisoned }) evolved.set(query, evolved.EXCLUDES, { resistant }) +evolved.set(query, evolved.VARIANTS, { alive, undead }) ``` The builder interface can be used to create queries too. It is more convenient to use, because the builder has special methods for including and excluding fragments. Here is a simple example of this: @@ -606,10 +612,11 @@ The builder interface can be used to create queries too. It is more convenient t local query = evolved.builder() :include(health, poisoned) :exclude(resistant) + :variant(alive, undead) :build() ``` -We don't have to set both [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes) fragments, we can even do it without filters at all, then the query will match all chunks in the world. +We don't have to set all of [`evolved.INCLUDES`](#evolvedincludes), [`evolved.EXCLUDES`](#evolvedexcludes), and [`evolved.VARIANTS`](#evolvedvariants) fragments, we can even do it without filters at all, then the query will match all chunks in the world. After the query is created, we are ready to process our filtered by this query entities. You can do this by using the [`evolved.execute`](#evolvedexecute) function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. @@ -788,7 +795,7 @@ The [`evolved.process`](#evolvedprocess) function is used to process systems. It function evolved.process(...) end ``` -If you don't specify a query for the system, the system itself will be treated as a query. This means the system can contain `evolved.INCLUDES` and `evolved.EXCLUDES` fragments, and it will be processed according to them. This is useful for creating systems with unique queries that don't need to be reused in other systems. +If you don't specify a query for the system, the system itself will be treated as a query. This means the system can contain `evolved.INCLUDES`, `evolved.EXCLUDES`, and `evolved.VARIANTS` fragments, and it will be processed according to them. This is useful for creating systems with unique queries that don't need to be reused in other systems. ```lua local evolved = require 'evolved' @@ -1198,6 +1205,7 @@ DISABLED :: fragment INCLUDES :: fragment EXCLUDES :: fragment +VARIANTS :: fragment REQUIRES :: fragment ON_SET :: fragment @@ -1332,6 +1340,7 @@ builder_mt:disabled :: builder builder_mt:include :: fragment... -> builder builder_mt:exclude :: fragment... -> builder +builder_mt:variant :: fragment... -> builder builder_mt:require :: fragment... -> builder builder_mt:on_set :: {entity, fragment, component, component} -> builder @@ -1354,6 +1363,7 @@ builder_mt:destruction_policy :: id -> builder ### vX.Y.Z +- Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries - Added the new [`evolved.process_with`](#evolvedprocess_with) function that allows passing payloads to processing systems ### v1.6.0 @@ -1428,6 +1438,8 @@ builder_mt:destruction_policy :: id -> builder ### `evolved.EXCLUDES` +### `evolved.VARIANTS` + ### `evolved.REQUIRES` ### `evolved.ON_SET` @@ -2065,6 +2077,14 @@ function evolved.builder_mt:include(...) end function evolved.builder_mt:exclude(...) end ``` +#### `evolved.builder_mt:variant` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function evolved.builder_mt:variant(...) end +``` + ### `evolved.builder_mt:require` ```lua diff --git a/evolved.d.tl b/evolved.d.tl index d78e4a0..8e390b0 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -69,6 +69,7 @@ include: function(self: Builder, ...: Fragment): Builder exclude: function(self: Builder, ...: Fragment): Builder + variant: function(self: Builder, ...: Fragment): Builder require: function(self: Builder, ...: Fragment): Builder on_set: function(self: Builder, on_set: function(Entity, Fragment, ? Component, ? Component)): Builder @@ -102,6 +103,7 @@ INCLUDES: Fragment EXCLUDES: Fragment + VARIANTS: Fragment REQUIRES: Fragment ON_SET: Fragment From d9c9b4cf85345d56400c21c3b59e838158f6a55e Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 11 Jan 2026 21:16:36 +0700 Subject: [PATCH 8/8] v1.7.0 --- README.md | 4 +-- evolved.lua | 2 +- rockspecs/evolved.lua-1.7.0-0.rockspec | 34 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 rockspecs/evolved.lua-1.7.0-0.rockspec diff --git a/README.md b/README.md index 5e1f109..ba1ade2 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) - - [vX.Y.Z](#vxyz) + - [v1.7.0](#v170) - [v1.6.0](#v160) - [v1.5.0](#v150) - [v1.4.0](#v140) @@ -1361,7 +1361,7 @@ builder_mt:destruction_policy :: id -> builder ## Changelog -### vX.Y.Z +### v1.7.0 - Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries - Added the new [`evolved.process_with`](#evolvedprocess_with) function that allows passing payloads to processing systems diff --git a/evolved.lua b/evolved.lua index 535df2a..2e90401 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1,7 +1,7 @@ local evolved = { __HOMEPAGE = 'https://github.com/BlackMATov/evolved.lua', __DESCRIPTION = 'Evolved ECS (Entity-Component-System) for Lua', - __VERSION = '1.6.0', + __VERSION = '1.7.0', __LICENSE = [[ MIT License diff --git a/rockspecs/evolved.lua-1.7.0-0.rockspec b/rockspecs/evolved.lua-1.7.0-0.rockspec new file mode 100644 index 0000000..c741649 --- /dev/null +++ b/rockspecs/evolved.lua-1.7.0-0.rockspec @@ -0,0 +1,34 @@ +rockspec_format = "3.0" +package = "evolved.lua" +version = "1.7.0-0" +source = { + url = "git://github.com/BlackMATov/evolved.lua", + tag = "v1.7.0", +} +description = { + homepage = "https://github.com/BlackMATov/evolved.lua", + summary = "Evolved ECS (Entity-Component-System) for Lua", + detailed = [[ + `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. + It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. + ]], + license = "MIT", + labels = { + "ecs", + "entity", + "entities", + "component", + "components", + "entity-component", + "entity-component-system", + }, +} +dependencies = { + "lua >= 5.1", +} +build = { + type = "builtin", + modules = { + evolved = "evolved.lua", + } +}