From e78c4e8d6b6d1c3c8c1313d0d18ae4037b5e0ed7 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 23 May 2025 22:05:09 +0700 Subject: [PATCH 01/15] update README --- .vscode/settings.json | 1 + GUIDES.md | 11 +++++++++++ README.md | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 GUIDES.md diff --git a/.vscode/settings.json b/.vscode/settings.json index b8d220a..36600ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "markdown.extension.toc.levels": "2..6", "markdown.extension.toc.omittedFromToc": { "README.md": [ + "# Changelog", "# API Reference" ] } diff --git a/GUIDES.md b/GUIDES.md new file mode 100644 index 0000000..3763684 --- /dev/null +++ b/GUIDES.md @@ -0,0 +1,11 @@ +# Guidelines + +## Checklists + +### Adding a New Top-Level Function +1. Insert the new function into the `evolved` table in `evolved.lua`. +2. Create tests for the function in `develop/testing/function_name_tests.lua`. +3. Add the new test to `develop/all.lua`. +4. Document the function in the **Cheat Sheet** and **API Reference** sections of `README.md`. +5. Provide a description in the **Overview** section of `README.md`. +6. Describe the update in the **Changelog** section of `README.md`. diff --git a/README.md b/README.md index 71a435e..4b61406 100644 --- a/README.md +++ b/README.md @@ -1123,6 +1123,12 @@ builder_mt:destruction_policy :: id -> builder `evolved.lua` is licensed under the [MIT License][license]. For more details, see the [LICENSE.md](./LICENSE.md) file in the repository. +# Changelog + +## v1.0.0 + +- Initial release + # API Reference ## Predefs From 5ae0e77f20e40fa8f5c9a88016980f22dfa315bc Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 25 May 2025 22:30:45 +0700 Subject: [PATCH 02/15] update GUIDES --- GUIDES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/GUIDES.md b/GUIDES.md index 3763684..1f59314 100644 --- a/GUIDES.md +++ b/GUIDES.md @@ -2,6 +2,17 @@ ## Checklists +### New Version Releasing + +1. Ensure all tests pass on CI. +2. Update the version number in `evolved.lua`. +3. Update the **Changelog** section in `README.md`. +4. Create a new rockspec file in `rockspecs`. +5. Commit the changes with a message like `vX.Y.Z`. +6. Push and merge the changes to the `main` branch. +7. Create the release on GitHub. +8. Upload the new package to LuaRocks. + ### Adding a New Top-Level Function 1. Insert the new function into the `evolved` table in `evolved.lua`. 2. Create tests for the function in `develop/testing/function_name_tests.lua`. From 3a64bac8e25475b4e8bf606176bf97cd76d932d3 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 26 May 2025 17:37:40 +0700 Subject: [PATCH 03/15] systems can be queries themselves --- README.md | 27 +++++ develop/all.lua | 5 +- develop/testing/system_as_query_tests.lua | 128 ++++++++++++++++++++++ evolved.lua | 15 ++- 4 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 develop/testing/system_as_query_tests.lua diff --git a/README.md b/README.md index 4b61406..a8b404a 100644 --- a/README.md +++ b/README.md @@ -671,6 +671,29 @@ 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. + +```lua +local evolved = require 'evolved' + +local health = evolved.id() + +local system = evolved.builder() + :include(health) + :execute(function(chunk, entity_list, entity_count) + local health_components = chunk:components(health) + + for i = 1, entity_count do + print(i) + health_components[i] = math.max( + health_components[i] - 1, + 0) + end + end):spawn() + +evolved.process(system) +``` + To group systems together, you can use the [`evolved.GROUP`](#evolvedgroup) fragment. Systems with a specified group will be processed when you call the [`evolved.process`](#evolvedprocess) function with this group. For example, you can group all physics systems together and process them in one [`evolved.process`](#evolvedprocess) call. ```lua @@ -1125,6 +1148,10 @@ builder_mt:destruction_policy :: id -> builder # Changelog +## vX.X.X-dev + +- Systems can be queries themselves + ## v1.0.0 - Initial release diff --git a/develop/all.lua b/develop/all.lua index 1a518e5..f6a56bc 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,6 +1,9 @@ require 'develop.example' -require 'develop.unbench' require 'develop.untests' + +require 'develop.testing.system_as_query_tests' + +require 'develop.unbench' require 'develop.usbench' local basics = require 'develop.basics' diff --git a/develop/testing/system_as_query_tests.lua b/develop/testing/system_as_query_tests.lua new file mode 100644 index 0000000..42effca --- /dev/null +++ b/develop/testing/system_as_query_tests.lua @@ -0,0 +1,128 @@ +local evo = require 'evolved' + +do + local f1, f2, f3 = evo.id(3) + + local q1e3 = evo.builder():include(f1):exclude(f3):spawn() + local q2e3 = evo.builder():include(f2):exclude(f3):spawn() + + local e1 = evo.builder():set(f1, 1):spawn() + local e12 = evo.builder():set(f1, 11):set(f2, 12):spawn() + local e2 = evo.builder():set(f2, 2):spawn() + local e23 = evo.builder():set(f2, 23):set(f3, 3):spawn() + + local c1 = evo.chunk(f1) + local c12 = evo.chunk(f1, f2) + local c2 = evo.chunk(f2) + + do + local _, entity_list, entity_count = evo.chunk(f2, f3) + assert(entity_count == 1 and entity_list[1] == e23) + end + + do + local entity_sum = 0 + + local s = evo.builder() + :query(q1e3) + :execute(function(chunk, entity_list, entity_count) + for i = 1, entity_count do + entity_sum = entity_sum + entity_list[i] + end + + if chunk == c1 then + assert(entity_count == 1) + assert(entity_list[1] == e1) + elseif chunk == c12 then + assert(entity_count == 1) + assert(entity_list[1] == e12) + else + assert(false, "Unexpected chunk: " .. tostring(chunk)) + end + end):spawn() + + evo.process(s) + + assert(entity_sum == e1 + e12) + end + + do + local entity_sum = 0 + + local s = evo.builder() + :query(q2e3) + :execute(function(chunk, entity_list, entity_count) + for i = 1, entity_count do + entity_sum = entity_sum + entity_list[i] + end + + if chunk == c12 then + assert(entity_count == 1) + assert(entity_list[1] == e12) + elseif chunk == c2 then + assert(entity_count == 1) + assert(entity_list[1] == e2) + else + assert(false, "Unexpected chunk: " .. tostring(chunk)) + end + end):spawn() + + evo.process(s) + + assert(entity_sum == e12 + e2) + end + + do + local entity_sum = 0 + + local s = evo.builder() + :include(f1) + :exclude(f3) + :execute(function(chunk, entity_list, entity_count) + for i = 1, entity_count do + entity_sum = entity_sum + entity_list[i] + end + + if chunk == c1 then + assert(entity_count == 1) + assert(entity_list[1] == e1) + elseif chunk == c12 then + assert(entity_count == 1) + assert(entity_list[1] == e12) + else + assert(false, "Unexpected chunk: " .. tostring(chunk)) + end + end):spawn() + + evo.process(s) + + assert(entity_sum == e1 + e12) + end + + do + local entity_sum = 0 + + local s = evo.builder() + :include(f2) + :exclude(f3) + :execute(function(chunk, entity_list, entity_count) + for i = 1, entity_count do + entity_sum = entity_sum + entity_list[i] + end + + if chunk == c12 then + assert(entity_count == 1) + assert(entity_list[1] == e12) + elseif chunk == c2 then + assert(entity_count == 1) + assert(entity_list[1] == e2) + else + assert(false, "Unexpected chunk: " .. tostring(chunk)) + end + end):spawn() + + evo.process(s) + + assert(entity_sum == e12 + e2) + end +end diff --git a/evolved.lua b/evolved.lua index 74e3460..74a70a6 100644 --- a/evolved.lua +++ b/evolved.lua @@ -2727,6 +2727,7 @@ end ---@param system evolved.system 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) @@ -2738,16 +2739,14 @@ local function __system_process(system) end end - if query and execute then + if execute then __evolved_defer() - do - for chunk, entity_list, entity_count in __evolved_execute(query) do - local success, result = __lua_pcall(execute, chunk, entity_list, entity_count) + for chunk, entity_list, entity_count in __evolved_execute(query or system) do + local success, result = __lua_pcall(execute, chunk, entity_list, entity_count) - if not success then - __evolved_commit() - __error_fmt('system execution failed: %s', result) - end + if not success then + __evolved_commit() + __error_fmt('system execution failed: %s', result) end end __evolved_commit() From 1399820d7128a93f429dc845fdfe220752a357f7 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 27 May 2025 17:13:52 +0700 Subject: [PATCH 04/15] new REQUIRES dummy fragment --- README.md | 12 +++ develop/all.lua | 1 + develop/testing/requires_fragment_tests.lua | 24 ++++++ evolved.lua | 85 ++++++++++++++++++--- 4 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 develop/testing/requires_fragment_tests.lua diff --git a/README.md b/README.md index a8b404a..51497bc 100644 --- a/README.md +++ b/README.md @@ -1008,6 +1008,7 @@ DISABLED :: fragment INCLUDES :: fragment EXCLUDES :: fragment +REQUIRES :: fragment ON_SET :: fragment ON_ASSIGN :: fragment @@ -1125,6 +1126,7 @@ builder_mt:disabled :: builder builder_mt:include :: fragment... -> builder builder_mt:exclude :: fragment... -> builder +builder_mt:require :: fragment... -> builder builder_mt:on_set :: {entity, fragment, component, component?} -> builder builder_mt:on_assign :: {entity, fragment, component, component} -> builder @@ -1180,6 +1182,8 @@ builder_mt:destruction_policy :: id -> builder ### `evolved.EXCLUDES` +### `evolved.REQUIRES` + ### `evolved.ON_SET` ### `evolved.ON_ASSIGN` @@ -1708,6 +1712,14 @@ function evolved.builder_mt:include(...) end function evolved.builder_mt:exclude(...) end ``` +### `evolved.builder_mt:require` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function evolved.builder_mt:require(...) end +``` + #### `evolved.builder_mt:on_set` ```lua diff --git a/develop/all.lua b/develop/all.lua index f6a56bc..af0ffc6 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,6 +1,7 @@ require 'develop.example' require 'develop.untests' +require 'develop.testing.requires_fragment_tests' require 'develop.testing.system_as_query_tests' require 'develop.unbench' diff --git a/develop/testing/requires_fragment_tests.lua b/develop/testing/requires_fragment_tests.lua new file mode 100644 index 0000000..b341122 --- /dev/null +++ b/develop/testing/requires_fragment_tests.lua @@ -0,0 +1,24 @@ +local evo = require 'evolved' + +do + local f1, f2 = evo.id(2) + evo.set(f1, evo.REQUIRES) + evo.set(f2, evo.REQUIRES, evo.get(f1, evo.REQUIRES)) + local f1_rs = evo.get(f1, evo.REQUIRES) + local f2_rs = evo.get(f2, evo.REQUIRES) + assert(f1_rs and f2_rs and #f1_rs == 0 and #f2_rs == 0 and f1_rs ~= f2_rs) +end + +do + local f1, f2 = evo.id(2) + local f3 = evo.builder():require(f1):require(f2):spawn() + local f3_rs = evo.get(f3, evo.REQUIRES) + assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2) +end + +do + local f1, f2 = evo.id(2) + local f3 = evo.builder():require(f1, f2):spawn() + local f3_rs = evo.get(f3, evo.REQUIRES) + assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2) +end diff --git a/evolved.lua b/evolved.lua index 74a70a6..f59f3e9 100644 --- a/evolved.lua +++ b/evolved.lua @@ -117,10 +117,11 @@ local __entity_places = {} ---@type table local __structural_changes = 0 ---@type integer -local __group_subsystems = {} ---@type table +local __sorted_includes = {} ---@type table +local __sorted_excludes = {} ---@type table +local __sorted_requires = {} ---@type table -local __query_sorted_includes = {} ---@type table -local __query_sorted_excludes = {} ---@type table +local __group_subsystems = {} ---@type table --- --- @@ -701,6 +702,7 @@ local __DISABLED = __acquire_id() local __INCLUDES = __acquire_id() local __EXCLUDES = __acquire_id() +local __REQUIRES = __acquire_id() local __ON_SET = __acquire_id() local __ON_ASSIGN = __acquire_id() @@ -4437,12 +4439,12 @@ function __evolved_execute(query) local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 - local query_includes = __query_sorted_includes[query] + local query_includes = __sorted_includes[query] local query_include_set = query_includes and query_includes.__item_set --[[@as table]] local query_include_list = query_includes and query_includes.__item_list --[=[@as evolved.fragment[]]=] local query_include_count = query_includes and query_includes.__item_count or 0 --[[@as integer]] - local query_excludes = __query_sorted_excludes[query] + local query_excludes = __sorted_excludes[query] local query_exclude_set = query_excludes and query_excludes.__item_set --[[@as table]] local query_exclude_list = query_excludes and query_excludes.__item_list --[=[@as evolved.fragment[]]=] local query_exclude_count = query_excludes and query_excludes.__item_count or 0 --[[@as integer]] @@ -5115,6 +5117,31 @@ function __builder_mt:exclude(...) return self:set(__EXCLUDES, exclude_list) end +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function __builder_mt:require(...) + local argument_count = __lua_select('#', ...) + + if argument_count == 0 then + return self + end + + local require_list = self:get(__REQUIRES) + local require_count = require_list and #require_list or 0 + + if require_count == 0 then + require_list = __lua_table_new(argument_count, 0) + end + + for i = 1, argument_count do + ---@type evolved.fragment + local fragment = __lua_select(i, ...) + require_list[require_count + i] = fragment + end + + return self:set(__REQUIRES, require_list) +end + ---@param on_set evolved.set_hook ---@return evolved.builder builder function __builder_mt:on_set(on_set) @@ -5232,6 +5259,7 @@ __evolved_set(__DISABLED, __NAME, 'DISABLED') __evolved_set(__INCLUDES, __NAME, 'INCLUDES') __evolved_set(__EXCLUDES, __NAME, 'EXCLUDES') +__evolved_set(__REQUIRES, __NAME, 'REQUIRES') __evolved_set(__ON_SET, __NAME, 'ON_SET') __evolved_set(__ON_ASSIGN, __NAME, 'ON_ASSIGN') @@ -5276,6 +5304,9 @@ __evolved_set(__INCLUDES, __DUPLICATE, __list_copy) __evolved_set(__EXCLUDES, __DEFAULT, {}) __evolved_set(__EXCLUDES, __DUPLICATE, __list_copy) +__evolved_set(__REQUIRES, __DEFAULT, {}) +__evolved_set(__REQUIRES, __DUPLICATE, __list_copy) + __evolved_set(__ON_SET, __UNIQUE) __evolved_set(__ON_ASSIGN, __UNIQUE) __evolved_set(__ON_INSERT, __UNIQUE) @@ -5293,7 +5324,7 @@ __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list) local include_count = #include_list if include_count == 0 then - __query_sorted_includes[query] = nil + __sorted_includes[query] = nil return end @@ -5305,11 +5336,11 @@ __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list) end __assoc_list_sort(sorted_includes) - __query_sorted_includes[query] = sorted_includes + __sorted_includes[query] = sorted_includes end) __evolved_set(__INCLUDES, __ON_REMOVE, function(query) - __query_sorted_includes[query] = nil + __sorted_includes[query] = nil end) --- @@ -5324,7 +5355,7 @@ __evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list) local exclude_count = #exclude_list if exclude_count == 0 then - __query_sorted_excludes[query] = nil + __sorted_excludes[query] = nil return end @@ -5336,11 +5367,42 @@ __evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list) end __assoc_list_sort(sorted_excludes) - __query_sorted_excludes[query] = sorted_excludes + __sorted_excludes[query] = sorted_excludes end) __evolved_set(__EXCLUDES, __ON_REMOVE, function(query) - __query_sorted_excludes[query] = nil + __sorted_excludes[query] = nil +end) + +--- +--- +--- +--- +--- + +---@param fragment evolved.fragment +---@param require_list evolved.fragment[] +__evolved_set(__REQUIRES, __ON_SET, function(fragment, _, require_list) + local require_count = #require_list + + if require_count == 0 then + __sorted_requires[fragment] = nil + return + end + + local sorted_requires = __assoc_list_new(require_count) + + for require_index = 1, require_count do + local require = require_list[require_index] + __assoc_list_insert(sorted_requires, require) + end + + __assoc_list_sort(sorted_requires) + __sorted_requires[fragment] = sorted_requires +end) + +__evolved_set(__REQUIRES, __ON_REMOVE, function(fragment) + __sorted_requires[fragment] = nil end) --- @@ -5411,6 +5473,7 @@ evolved.DUPLICATE = __DUPLICATE evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED +evolved.REQUIRES = __REQUIRES evolved.INCLUDES = __INCLUDES evolved.EXCLUDES = __EXCLUDES From 8038031b51a6ffe9a4d6bcdbafea4a8366fc39d2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 28 May 2025 22:44:18 +0700 Subject: [PATCH 05/15] has_required_fragments chunk flag --- evolved.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/evolved.lua b/evolved.lua index f59f3e9..2d1049b 100644 --- a/evolved.lua +++ b/evolved.lua @@ -157,6 +157,7 @@ local __group_subsystems = {} ---@type table ---@field package __has_explicit_major boolean ---@field package __has_explicit_minors boolean ---@field package __has_explicit_fragments boolean +---@field package __has_required_fragments boolean local __chunk_mt = {} __chunk_mt.__index = __chunk_mt @@ -1021,6 +1022,7 @@ function __new_chunk(chunk_parent, chunk_fragment) __has_explicit_major = false, __has_explicit_minors = false, __has_explicit_fragments = false, + __has_required_fragments = false, }, __chunk_mt) if chunk_parent then @@ -1158,6 +1160,9 @@ function __update_chunk_flags(chunk) local has_explicit_minors = chunk_parent ~= nil and chunk_parent.__has_explicit_fragments local has_explicit_fragments = has_explicit_major or has_explicit_minors + local has_required_fragments = (chunk_parent ~= nil and chunk_parent.__has_required_fragments) + or __evolved_has(chunk_fragment, __REQUIRES) + chunk.__has_setup_hooks = has_setup_hooks chunk.__has_assign_hooks = has_assign_hooks chunk.__has_insert_hooks = has_insert_hooks @@ -1170,6 +1175,8 @@ function __update_chunk_flags(chunk) chunk.__has_explicit_major = has_explicit_major chunk.__has_explicit_minors = has_explicit_minors chunk.__has_explicit_fragments = has_explicit_fragments + + chunk.__has_required_fragments = has_required_fragments end ---@param major evolved.fragment @@ -5239,6 +5246,9 @@ __evolved_set(__DEFAULT, __ON_REMOVE, __update_major_chunks_hook) __evolved_set(__DUPLICATE, __ON_INSERT, __update_major_chunks_hook) __evolved_set(__DUPLICATE, __ON_REMOVE, __update_major_chunks_hook) +__evolved_set(__REQUIRES, __ON_INSERT, __update_major_chunks_hook) +__evolved_set(__REQUIRES, __ON_REMOVE, __update_major_chunks_hook) + --- --- --- @@ -5473,9 +5483,9 @@ evolved.DUPLICATE = __DUPLICATE evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED -evolved.REQUIRES = __REQUIRES evolved.INCLUDES = __INCLUDES evolved.EXCLUDES = __EXCLUDES +evolved.REQUIRES = __REQUIRES evolved.ON_SET = __ON_SET evolved.ON_ASSIGN = __ON_ASSIGN From c323131d1e29ecdbec5c38ef3f9967469ade15ae Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 28 May 2025 23:33:57 +0700 Subject: [PATCH 06/15] add pack/unpack fuzz test --- develop/all.lua | 2 ++ develop/fuzzing/pack_unpack_fuzz.lua | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 develop/fuzzing/pack_unpack_fuzz.lua diff --git a/develop/all.lua b/develop/all.lua index af0ffc6..0b67fbf 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -16,4 +16,6 @@ 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.unique_fuzz' diff --git a/develop/fuzzing/pack_unpack_fuzz.lua b/develop/fuzzing/pack_unpack_fuzz.lua new file mode 100644 index 0000000..295ef9b --- /dev/null +++ b/develop/fuzzing/pack_unpack_fuzz.lua @@ -0,0 +1,20 @@ +local evo = require 'evolved' + +evo.debug_mode(true) + +--- +--- +--- +--- +--- + +for _ = 1, 1000 do + local initial_index = math.random(1, 0xFFFFF) + local initial_version = math.random(1, 0xFFFFF) + + local packed_id = evo.pack(initial_index, initial_version) + local unpacked_index, unpacked_version = evo.unpack(packed_id) + + assert(initial_index == unpacked_index) + assert(initial_version == unpacked_version) +end From 8eccd461fdf47e0de8061641e369f15d51fe68b5 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 28 May 2025 23:33:57 +0700 Subject: [PATCH 07/15] add pack/unpack fuzz test --- develop/all.lua | 2 ++ develop/fuzzing/pack_unpack_fuzz.lua | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 develop/fuzzing/pack_unpack_fuzz.lua diff --git a/develop/all.lua b/develop/all.lua index f6a56bc..8869010 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -15,4 +15,6 @@ 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.unique_fuzz' diff --git a/develop/fuzzing/pack_unpack_fuzz.lua b/develop/fuzzing/pack_unpack_fuzz.lua new file mode 100644 index 0000000..295ef9b --- /dev/null +++ b/develop/fuzzing/pack_unpack_fuzz.lua @@ -0,0 +1,20 @@ +local evo = require 'evolved' + +evo.debug_mode(true) + +--- +--- +--- +--- +--- + +for _ = 1, 1000 do + local initial_index = math.random(1, 0xFFFFF) + local initial_version = math.random(1, 0xFFFFF) + + local packed_id = evo.pack(initial_index, initial_version) + local unpacked_index, unpacked_version = evo.unpack(packed_id) + + assert(initial_index == unpacked_index) + assert(initial_version == unpacked_version) +end From 50afb722d182503e8b5da4c20241258102aa6ad5 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 29 May 2025 07:19:50 +0700 Subject: [PATCH 08/15] proof of concept REQUIRES fragment impl --- develop/testing/requires_fragment_tests.lua | 173 ++++++++ evolved.lua | 423 ++++++++++++++++++++ 2 files changed, 596 insertions(+) diff --git a/develop/testing/requires_fragment_tests.lua b/develop/testing/requires_fragment_tests.lua index b341122..529817a 100644 --- a/develop/testing/requires_fragment_tests.lua +++ b/develop/testing/requires_fragment_tests.lua @@ -22,3 +22,176 @@ do local f3_rs = evo.get(f3, evo.REQUIRES) assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2) end + +do + local f1, f2 = evo.id(2) + evo.set(f1, evo.REQUIRES, { f2 }) + + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + end + + do + local e = evo.builder():set(f1):spawn() + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + end + + do + local e = evo.spawn { [f1] = true } + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + + evo.remove(e, f2) + assert(not evo.has(e, f2)) + + local e2 = evo.clone(e) + assert(evo.has(e2, f2)) + assert(evo.get(e2, f2) == true) + end + + do + local f0 = evo.id() + local q0 = evo.builder():include(f0):spawn() + + local e1 = evo.builder():set(f0):spawn() + local e2 = evo.builder():set(f0):spawn() + local e3 = evo.builder():set(f0):set(f2, false):spawn() + + evo.batch_set(q0, f1) + + assert(evo.has(e1, f2) and evo.get(e1, f2) == true) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + assert(evo.has(e3, f2) and evo.get(e3, f2) == false) + end +end + +do + local f1, f2 = evo.id(2) + evo.set(f1, evo.REQUIRES, { f2 }) + evo.set(f2, evo.DEFAULT, 42) + + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == 42) + end + + do + local e = evo.builder():set(f1):spawn() + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == 42) + end + + do + local e = evo.spawn { [f1] = true, } + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == 42) + + evo.remove(e, f2) + assert(not evo.has(e, f2)) + + local e2 = evo.clone(e) + assert(evo.has(e2, f2)) + assert(evo.get(e2, f2) == 42) + end + + do + local f0 = evo.id() + local q0 = evo.builder():include(f0):spawn() + + local e1 = evo.builder():set(f0):spawn() + local e2 = evo.builder():set(f0):spawn() + local e3 = evo.builder():set(f0):set(f2, 21):spawn() + + evo.batch_set(q0, f1) + + assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + assert(evo.has(e3, f2) and evo.get(e3, f2) == 21) + end +end + +do + local f1, f2, f3 = evo.id(3) + evo.set(f1, evo.REQUIRES, { f2 }) + evo.set(f2, evo.REQUIRES, { f3 }) + evo.set(f3, evo.DEFAULT, 42) + + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + assert(evo.has(e, f3)) + assert(evo.get(e, f3) == 42) + end + + do + local e = evo.builder():set(f1):spawn() + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + assert(evo.has(e, f3)) + assert(evo.get(e, f3) == 42) + end + + do + local e = evo.spawn { [f1] = true } + assert(evo.has(e, f2)) + assert(evo.get(e, f2) == true) + assert(evo.has(e, f3)) + assert(evo.get(e, f3) == 42) + + evo.remove(e, f2, f3) + assert(not evo.has(e, f2)) + assert(not evo.has(e, f3)) + + local e2 = evo.clone(e) + assert(evo.has(e2, f2)) + assert(evo.get(e2, f2) == true) + assert(evo.has(e2, f3)) + assert(evo.get(e2, f3) == 42) + end +end + +do + local f1, f2, f3 = evo.id(3) + evo.set(f1, evo.REQUIRES, { f2 }) + evo.set(f2, evo.REQUIRES, { f3 }) + evo.set(f3, evo.REQUIRES, { f1, f2, f3 }) + + do + local e = evo.id() + evo.set(e, f1, 42) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == true) + end + + do + local e = evo.builder():set(f1, 42):spawn() + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == true) + end + + do + local e = evo.spawn { [f1] = 42 } + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == true) + + evo.remove(e, f2, f3) + assert(not evo.has(e, f2)) + assert(not evo.has(e, f3)) + + local e2 = evo.clone(e) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + assert(evo.has(e2, f3) and evo.get(e2, f3) == true) + end +end diff --git a/evolved.lua b/evolved.lua index 2d1049b..a8d6b61 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1376,6 +1376,63 @@ local function __chunk_without_unique_fragments(chunk) return new_chunk end +---@param chunk evolved.chunk +---@param req_fragment_set table +---@param req_fragment_list evolved.fragment[] +---@param req_fragment_count integer +---@return integer +---@nodiscard +local function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_list, req_fragment_count) + if not chunk.__has_required_fragments then + return req_fragment_count + end + + ---@type evolved.fragment[] + local fragment_stack = __acquire_table(__table_pool_tag.fragment_list) + local fragment_stack_size = 0 + + do + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + + __lua_table_move( + chunk_fragment_list, 1, chunk_fragment_count, + 1, fragment_stack) + + fragment_stack_size = fragment_stack_size + chunk_fragment_count + end + + while fragment_stack_size > 0 do + local fragment = fragment_stack[fragment_stack_size] + + fragment_stack[fragment_stack_size] = nil + fragment_stack_size = fragment_stack_size - 1 + + local fragment_requires = __sorted_requires[fragment] + local fragment_require_list = fragment_requires and fragment_requires.__item_list + local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 + + for fragment_require_index = 1, fragment_require_count do + ---@cast fragment_require_list -? + local fragment_require = fragment_require_list[fragment_require_index] + + if req_fragment_set[fragment_require] then + -- this fragment has already been gathered + else + req_fragment_count = req_fragment_count + 1 + req_fragment_set[fragment_require] = req_fragment_count + req_fragment_list[req_fragment_count] = fragment_require + + fragment_stack_size = fragment_stack_size + 1 + fragment_stack[fragment_stack_size] = fragment_require + end + end + end + + __release_table(__table_pool_tag.fragment_list, fragment_stack, true) + return req_fragment_count +end + --- --- --- @@ -1703,6 +1760,29 @@ local function __spawn_entity(entity, components) return end + local req_fragment_set + local req_fragment_list + local req_fragment_count = 0 + + local ini_chunk = chunk + local ini_fragment_set = ini_chunk.__fragment_set + + if chunk.__has_required_fragments then + ---@type table + req_fragment_set = __acquire_table(__table_pool_tag.fragment_set) + + ---@type evolved.fragment[] + req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) + + req_fragment_count = __chunk_required_fragments(ini_chunk, + req_fragment_set, req_fragment_list, req_fragment_count) + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + chunk = __chunk_with_fragment(chunk, req_fragment) + end + end + local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count @@ -1747,6 +1827,36 @@ local function __spawn_entity(entity, components) component_storage[place] = new_component end end + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + local req_component_index = chunk_component_indices[req_fragment] + + if req_component_index then + ---@type evolved.default?, evolved.duplicate? + local req_fragment_default, req_fragment_duplicate = + __evolved_get(req_fragment, __DEFAULT, __DUPLICATE) + + local req_component = req_fragment_default + + if req_component ~= nil and req_fragment_duplicate then + req_component = req_fragment_duplicate(req_component) + end + + if req_component == nil then + req_component = true + end + + local req_component_storage = chunk_component_storages[req_component_index] + + req_component_storage[place] = req_component + end + end + end else for fragment, component in __lua_next, components do local component_index = chunk_component_indices[fragment] @@ -1759,6 +1869,24 @@ local function __spawn_entity(entity, components) component_storage[place] = new_component end end + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + local req_component_index = chunk_component_indices[req_fragment] + + if req_component_index then + local req_component = true + + local req_component_storage = chunk_component_storages[req_component_index] + + req_component_storage[place] = req_component + end + end + end end if chunk.__has_insert_hooks then @@ -1797,6 +1925,14 @@ local function __spawn_entity(entity, components) end end end + + if req_fragment_set then + __release_table(__table_pool_tag.fragment_set, req_fragment_set) + end + + if req_fragment_list then + __release_table(__table_pool_tag.fragment_list, req_fragment_list) + end end ---@param entity evolved.entity @@ -1819,6 +1955,29 @@ local function __clone_entity(entity, prefab, components) return end + local req_fragment_set + local req_fragment_list + local req_fragment_count = 0 + + local ini_chunk = chunk + local ini_fragment_set = ini_chunk.__fragment_set + + if chunk.__has_required_fragments then + ---@type table + req_fragment_set = __acquire_table(__table_pool_tag.fragment_set) + + ---@type evolved.fragment[] + req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) + + req_fragment_count = __chunk_required_fragments(ini_chunk, + req_fragment_set, req_fragment_list, req_fragment_count) + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + chunk = __chunk_with_fragment(chunk, req_fragment) + end + end + local chunk_entity_list = chunk.__entity_list local chunk_entity_count = chunk.__entity_count @@ -1919,6 +2078,36 @@ local function __clone_entity(entity, prefab, components) component_storage[place] = new_component end end + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + local req_component_index = chunk_component_indices[req_fragment] + + if req_component_index then + ---@type evolved.default?, evolved.duplicate? + local req_fragment_default, req_fragment_duplicate = + __evolved_get(req_fragment, __DEFAULT, __DUPLICATE) + + local req_component = req_fragment_default + + if req_component ~= nil and req_fragment_duplicate then + req_component = req_fragment_duplicate(req_component) + end + + if req_component == nil then + req_component = true + end + + local req_component_storage = chunk_component_storages[req_component_index] + + req_component_storage[place] = req_component + end + end + end else for fragment, component in __lua_next, components do local component_index = chunk_component_indices[fragment] @@ -1931,6 +2120,24 @@ local function __clone_entity(entity, prefab, components) component_storage[place] = new_component end end + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + local req_component_index = chunk_component_indices[req_fragment] + + if req_component_index then + local req_component = true + + local req_component_storage = chunk_component_storages[req_component_index] + + req_component_storage[place] = req_component + end + end + end end if chunk.__has_insert_hooks then @@ -1969,6 +2176,14 @@ local function __clone_entity(entity, prefab, components) end end end + + if req_fragment_set then + __release_table(__table_pool_tag.fragment_set, req_fragment_set) + end + + if req_fragment_list then + __release_table(__table_pool_tag.fragment_list, req_fragment_list) + end end --- @@ -2381,6 +2596,29 @@ function __chunk_set(old_chunk, fragment, component) end end else + local req_fragment_set + local req_fragment_list + local req_fragment_count = 0 + + local ini_new_chunk = new_chunk + local ini_fragment_set = ini_new_chunk.__fragment_set + + if new_chunk.__has_required_fragments then + ---@type table + req_fragment_set = __acquire_table(__table_pool_tag.fragment_set) + + ---@type evolved.fragment[] + req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) + + req_fragment_count = __chunk_required_fragments(ini_new_chunk, + req_fragment_set, req_fragment_list, req_fragment_count) + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + new_chunk = __chunk_with_fragment(new_chunk, req_fragment) + end + end + local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count @@ -2527,6 +2765,110 @@ function __chunk_set(old_chunk, fragment, component) end end + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + ---@type evolved.default?, evolved.duplicate?, evolved.set_hook?, evolved.insert_hook? + local req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert + + if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then + req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert = + __evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + end + + if req_fragment_on_set or req_fragment_on_insert then + local req_component_index = new_component_indices[req_fragment] + + if req_component_index then + local req_component_storage = new_component_storages[req_component_index] + + if req_fragment_duplicate then + for new_place = new_entity_count + 1, new_entity_count + old_entity_count do + local entity = new_entity_list[new_place] + + local req_component = req_fragment_default + if req_component ~= nil then req_component = req_fragment_duplicate(req_component) end + if req_component == nil then req_component = true end + + req_component_storage[new_place] = req_component + + if req_fragment_on_set then + __defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component) + end + + if req_fragment_on_insert then + __defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component) + end + end + else + local req_component = req_fragment_default + if req_component == nil then req_component = true end + + for new_place = new_entity_count + 1, new_entity_count + old_entity_count do + local entity = new_entity_list[new_place] + + req_component_storage[new_place] = req_component + + if req_fragment_on_set then + __defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component) + end + + if req_fragment_on_insert then + __defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component) + end + end + end + else + for new_place = new_entity_count + 1, new_entity_count + old_entity_count do + local entity = new_entity_list[new_place] + + if req_fragment_on_set then + __defer_call_hook(req_fragment_on_set, entity, req_fragment) + end + + if req_fragment_on_insert then + __defer_call_hook(req_fragment_on_insert, entity, req_fragment) + end + end + end + else + local req_component_index = new_component_indices[req_fragment] + + if req_component_index then + local req_component_storage = new_component_storages[req_component_index] + + if req_fragment_duplicate then + for new_place = new_entity_count + 1, new_entity_count + old_entity_count do + local req_component = req_fragment_default + if req_component ~= nil then req_component = req_fragment_duplicate(req_component) end + if req_component == nil then req_component = true end + req_component_storage[new_place] = req_component + end + else + local req_component = req_fragment_default + if req_component == nil then req_component = true end + for new_place = new_entity_count + 1, new_entity_count + old_entity_count do + req_component_storage[new_place] = req_component + end + end + else + -- nothing + end + end + end + end + + if req_fragment_set then + __release_table(__table_pool_tag.fragment_set, req_fragment_set) + end + + if req_fragment_list then + __release_table(__table_pool_tag.fragment_list, req_fragment_list) + end + __structural_changes = __structural_changes + 1 end end @@ -3883,6 +4225,29 @@ function __evolved_set(entity, fragment, component) end end else + local req_fragment_set + local req_fragment_list + local req_fragment_count = 0 + + local ini_new_chunk = new_chunk + local ini_fragment_set = ini_new_chunk.__fragment_set + + if new_chunk.__has_required_fragments then + ---@type table + req_fragment_set = __acquire_table(__table_pool_tag.fragment_set) + + ---@type evolved.fragment[] + req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) + + req_fragment_count = __chunk_required_fragments(ini_new_chunk, + req_fragment_set, req_fragment_list, req_fragment_count) + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + new_chunk = __chunk_with_fragment(new_chunk, req_fragment) + end + end + local new_entity_list = new_chunk.__entity_list local new_entity_count = new_chunk.__entity_count @@ -3958,6 +4323,64 @@ function __evolved_set(entity, fragment, component) end end end + + for i = 1, req_fragment_count do + local req_fragment = req_fragment_list[i] + + if ini_fragment_set[req_fragment] then + -- this fragment has already been initialized + else + ---@type evolved.default?, evolved.duplicate?, evolved.set_hook?, evolved.insert_hook? + local req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert + + if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then + req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert = + __evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) + end + + local req_component_index = new_component_indices[req_fragment] + + if req_component_index then + local req_component_storage = new_component_storages[req_component_index] + + local req_component = req_fragment_default + + if req_component ~= nil and req_fragment_duplicate then + req_component = req_fragment_duplicate(req_component) + end + + if req_component == nil then + req_component = true + end + + req_component_storage[new_place] = req_component + + if req_fragment_on_set then + __defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component) + end + + if req_fragment_on_insert then + __defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component) + end + else + if req_fragment_on_set then + __defer_call_hook(req_fragment_on_set, entity, req_fragment) + end + + if req_fragment_on_insert then + __defer_call_hook(req_fragment_on_insert, entity, req_fragment) + end + end + end + end + + if req_fragment_set then + __release_table(__table_pool_tag.fragment_set, req_fragment_set) + end + + if req_fragment_list then + __release_table(__table_pool_tag.fragment_list, req_fragment_list) + end end __evolved_commit() From ba5ee22f5d6b0d8117d9055ee45d8c887287e337 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 30 May 2025 01:26:56 +0700 Subject: [PATCH 09/15] little improvements REQUIRES fragments --- develop/testing/requires_fragment_tests.lua | 48 ++++-- evolved.lua | 175 +++++++++++++------- 2 files changed, 147 insertions(+), 76 deletions(-) diff --git a/develop/testing/requires_fragment_tests.lua b/develop/testing/requires_fragment_tests.lua index 529817a..d089466 100644 --- a/develop/testing/requires_fragment_tests.lua +++ b/develop/testing/requires_fragment_tests.lua @@ -24,8 +24,9 @@ do end do - local f1, f2 = evo.id(2) + local f1, f2, f3 = evo.id(3) evo.set(f1, evo.REQUIRES, { f2 }) + evo.set(f3, evo.REQUIRES, { f1, f2 }) do local e = evo.id() @@ -49,8 +50,11 @@ do assert(not evo.has(e, f2)) local e2 = evo.clone(e) - assert(evo.has(e2, f2)) - assert(evo.get(e2, f2) == true) + assert(not evo.has(e2, f2)) + + local e3 = evo.clone(e, { [f3] = true }) + assert(evo.has(e3, f2)) + assert(evo.get(e3, f2) == true) end do @@ -70,9 +74,10 @@ do end do - local f1, f2 = evo.id(2) + local f1, f2, f3 = evo.id(3) evo.set(f1, evo.REQUIRES, { f2 }) evo.set(f2, evo.DEFAULT, 42) + evo.set(f3, evo.REQUIRES, { f1, f2 }) do local e = evo.id() @@ -96,8 +101,11 @@ do assert(not evo.has(e, f2)) local e2 = evo.clone(e) - assert(evo.has(e2, f2)) - assert(evo.get(e2, f2) == 42) + assert(not evo.has(e2, f2)) + + local e3 = evo.clone(e, { [f3] = true }) + assert(evo.has(e3, f2)) + assert(evo.get(e3, f2) == 42) end do @@ -121,6 +129,7 @@ do evo.set(f1, evo.REQUIRES, { f2 }) evo.set(f2, evo.REQUIRES, { f3 }) evo.set(f3, evo.DEFAULT, 42) + evo.set(f3, evo.REQUIRES, { f1, f2 }) do local e = evo.id() @@ -150,11 +159,15 @@ do assert(not evo.has(e, f2)) assert(not evo.has(e, f3)) - local e2 = evo.clone(e) - assert(evo.has(e2, f2)) - assert(evo.get(e2, f2) == true) - assert(evo.has(e2, f3)) - assert(evo.get(e2, f3) == 42) + local e2 = evo.clone(e, { [f1] = 21 }) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) + assert(not evo.has(e2, f2)) + assert(not evo.has(e2, f3)) + + local e3 = evo.clone(e, { [f3] = 21 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == true) + assert(evo.has(e3, f2) and evo.get(e3, f2) == true) + assert(evo.has(e3, f3) and evo.get(e3, f3) == 21) end end @@ -189,9 +202,14 @@ do assert(not evo.has(e, f2)) assert(not evo.has(e, f3)) - local e2 = evo.clone(e) - assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) - assert(evo.has(e2, f2) and evo.get(e2, f2) == true) - assert(evo.has(e2, f3) and evo.get(e2, f3) == true) + local e2 = evo.clone(e, { [f1] = 21 }) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) + assert(not evo.has(e2, f2)) + assert(not evo.has(e2, f3)) + + local e3 = evo.clone(e, { [f3] = 21 }) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 42) + assert(evo.has(e3, f2) and evo.get(e3, f2) == true) + assert(evo.has(e3, f3) and evo.get(e3, f3) == 21) end end diff --git a/evolved.lua b/evolved.lua index a8d6b61..881fa44 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1376,63 +1376,6 @@ local function __chunk_without_unique_fragments(chunk) return new_chunk end ----@param chunk evolved.chunk ----@param req_fragment_set table ----@param req_fragment_list evolved.fragment[] ----@param req_fragment_count integer ----@return integer ----@nodiscard -local function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_list, req_fragment_count) - if not chunk.__has_required_fragments then - return req_fragment_count - end - - ---@type evolved.fragment[] - local fragment_stack = __acquire_table(__table_pool_tag.fragment_list) - local fragment_stack_size = 0 - - do - local chunk_fragment_list = chunk.__fragment_list - local chunk_fragment_count = chunk.__fragment_count - - __lua_table_move( - chunk_fragment_list, 1, chunk_fragment_count, - 1, fragment_stack) - - fragment_stack_size = fragment_stack_size + chunk_fragment_count - end - - while fragment_stack_size > 0 do - local fragment = fragment_stack[fragment_stack_size] - - fragment_stack[fragment_stack_size] = nil - fragment_stack_size = fragment_stack_size - 1 - - local fragment_requires = __sorted_requires[fragment] - local fragment_require_list = fragment_requires and fragment_requires.__item_list - local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 - - for fragment_require_index = 1, fragment_require_count do - ---@cast fragment_require_list -? - local fragment_require = fragment_require_list[fragment_require_index] - - if req_fragment_set[fragment_require] then - -- this fragment has already been gathered - else - req_fragment_count = req_fragment_count + 1 - req_fragment_set[fragment_require] = req_fragment_count - req_fragment_list[req_fragment_count] = fragment_require - - fragment_stack_size = fragment_stack_size + 1 - fragment_stack[fragment_stack_size] = fragment_require - end - end - end - - __release_table(__table_pool_tag.fragment_list, fragment_stack, true) - return req_fragment_count -end - --- --- --- @@ -1675,6 +1618,112 @@ end --- --- +---@param chunk evolved.chunk +---@param req_fragment_set table +---@param req_fragment_list evolved.fragment[] +---@param req_fragment_count integer +---@return integer +---@nodiscard +local function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_list, req_fragment_count) + ---@type evolved.fragment[] + local fragment_stack = __acquire_table(__table_pool_tag.fragment_list) + local fragment_stack_size = 0 + + do + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + + __lua_table_move( + chunk_fragment_list, 1, chunk_fragment_count, + 1, fragment_stack) + + fragment_stack_size = fragment_stack_size + chunk_fragment_count + end + + while fragment_stack_size > 0 do + local stack_fragment = fragment_stack[fragment_stack_size] + + fragment_stack[fragment_stack_size] = nil + fragment_stack_size = fragment_stack_size - 1 + + local fragment_requires = __sorted_requires[stack_fragment] + local fragment_require_list = fragment_requires and fragment_requires.__item_list + local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 + + for fragment_require_index = 1, fragment_require_count do + ---@cast fragment_require_list -? + local required_fragment = fragment_require_list[fragment_require_index] + + if req_fragment_set[required_fragment] then + -- this fragment has already been gathered + else + req_fragment_count = req_fragment_count + 1 + req_fragment_set[required_fragment] = req_fragment_count + req_fragment_list[req_fragment_count] = required_fragment + + fragment_stack_size = fragment_stack_size + 1 + fragment_stack[fragment_stack_size] = required_fragment + end + end + end + + __release_table(__table_pool_tag.fragment_list, fragment_stack, true) + return req_fragment_count +end + +---@param fragment evolved.fragment +---@param req_fragment_set table +---@param req_fragment_list evolved.fragment[] +---@param req_fragment_count integer +---@return integer +---@nodiscard +local function __fragment_required_fragments(fragment, req_fragment_set, req_fragment_list, req_fragment_count) + ---@type evolved.fragment[] + local fragment_stack = __acquire_table(__table_pool_tag.fragment_list) + local fragment_stack_size = 0 + + do + fragment_stack_size = fragment_stack_size + 1 + fragment_stack[fragment_stack_size] = fragment + end + + while fragment_stack_size > 0 do + local stack_fragment = fragment_stack[fragment_stack_size] + + fragment_stack[fragment_stack_size] = nil + fragment_stack_size = fragment_stack_size - 1 + + local fragment_requires = __sorted_requires[stack_fragment] + local fragment_require_list = fragment_requires and fragment_requires.__item_list + local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0 + + for fragment_require_index = 1, fragment_require_count do + ---@cast fragment_require_list -? + local required_fragment = fragment_require_list[fragment_require_index] + + if req_fragment_set[required_fragment] then + -- this fragment has already been gathered + else + req_fragment_count = req_fragment_count + 1 + req_fragment_set[required_fragment] = req_fragment_count + req_fragment_list[req_fragment_count] = required_fragment + + fragment_stack_size = fragment_stack_size + 1 + fragment_stack[fragment_stack_size] = required_fragment + end + end + end + + __release_table(__table_pool_tag.fragment_list, fragment_stack, true) + return req_fragment_count +end + +--- +--- +--- +--- +--- + local __defer_set local __defer_remove local __defer_clear @@ -1969,8 +2018,12 @@ local function __clone_entity(entity, prefab, components) ---@type evolved.fragment[] req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) - req_fragment_count = __chunk_required_fragments(ini_chunk, - req_fragment_set, req_fragment_list, req_fragment_count) + for fragment in __lua_next, components do + if not prefab_chunk or not prefab_chunk.__fragment_set[fragment] then + req_fragment_count = __fragment_required_fragments(fragment, + req_fragment_set, req_fragment_list, req_fragment_count) + end + end for i = 1, req_fragment_count do local req_fragment = req_fragment_list[i] @@ -2610,7 +2663,7 @@ function __chunk_set(old_chunk, fragment, component) ---@type evolved.fragment[] req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) - req_fragment_count = __chunk_required_fragments(ini_new_chunk, + req_fragment_count = __fragment_required_fragments(fragment, req_fragment_set, req_fragment_list, req_fragment_count) for i = 1, req_fragment_count do @@ -4239,7 +4292,7 @@ function __evolved_set(entity, fragment, component) ---@type evolved.fragment[] req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) - req_fragment_count = __chunk_required_fragments(ini_new_chunk, + req_fragment_count = __fragment_required_fragments(fragment, req_fragment_set, req_fragment_list, req_fragment_count) for i = 1, req_fragment_count do From a559c73c35e116373a0a07259861b719f87e0c23 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 30 May 2025 03:41:42 +0700 Subject: [PATCH 10/15] update README to reflect the new REQUIRES trait feature --- README.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 51497bc..1734837 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,13 @@ - [Deferred Operations](#deferred-operations) - [Batch Operations](#batch-operations) - [Systems](#systems) - - [Advanced Topics](#advanced-topics) + - [Predefined Traits](#predefined-traits) - [Fragment Tags](#fragment-tags) - [Fragment Hooks](#fragment-hooks) - [Unique Fragments](#unique-fragments) - [Explicit Fragments](#explicit-fragments) - [Shared Components](#shared-components) + - [Fragment Requirements](#fragment-requirements) - [Destruction Policies](#destruction-policies) - [Cheat Sheet](#cheat-sheet) - [Aliases](#aliases) @@ -766,7 +767,7 @@ 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. -### Advanced Topics +### Predefined Traits #### Fragment Tags @@ -910,6 +911,35 @@ local enemy2 = evolved.builder() assert(evolved.get(enemy1, position) ~= evolved.get(enemy2, position)) ``` +#### Fragment Requirements + +Sometimes you want to add additional fragments to an entity when it receives a specific fragment. For example, you might want to add `position` and `velocity` fragments when an entity is given a `physical` fragment. This can be done using the [`evolved.REQUIRES`](#evolvedrequires) fragment trait. This trait expects a list of fragments that will be added to the entity when the fragment is inserted. + +```lua +local evolved = require 'evolved' + +local position = evolved.builder() + :default(vector2(0, 0)) + :duplicate(vector2_duplicate) + :spawn() + +local velocity = evolved.builder() + :default(vector2(0, 0)) + :duplicate(vector2_duplicate) + :spawn() + +local physical = evolved.builder() + :tag() + :require(position, velocity) + :spawn() + +local enemy = evolved.builder() + :set(physical) + :spawn() + +assert(evolved.has_all(enemy, position, velocity)) +``` + #### Destruction Policies Typically, fragments remain alive for the entire lifetime of the program. However, in some cases, you might want to destroy fragments when they are no longer needed. For example, you can use some runtime entities as fragments for other entities. In this case, you might want to destroy such fragments even while they are still attached to other entities. Since entities cannot have destroyed fragments, a destruction policy must be applied to resolve this. By default, the library will remove the destroyed fragment from all entities that have it. From cb98a4e4616d69c9ad1cfe12046f124e99971957 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 31 May 2025 07:14:03 +0700 Subject: [PATCH 11/15] update README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a8b404a..77b4f7e 100644 --- a/README.md +++ b/README.md @@ -684,7 +684,6 @@ local system = evolved.builder() local health_components = chunk:components(health) for i = 1, entity_count do - print(i) health_components[i] = math.max( health_components[i] - 1, 0) From 47fcc9cc13d9928b71f19a305abdf24683860d55 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 5 Jun 2025 14:46:16 +0700 Subject: [PATCH 12/15] additional requries-fragment test --- develop/testing/requires_fragment_tests.lua | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/develop/testing/requires_fragment_tests.lua b/develop/testing/requires_fragment_tests.lua index d089466..befa3a4 100644 --- a/develop/testing/requires_fragment_tests.lua +++ b/develop/testing/requires_fragment_tests.lua @@ -213,3 +213,31 @@ do assert(evo.has(e3, f3) and evo.get(e3, f3) == 21) end end + +do + local f1, f2, f3, f4 = evo.id(4) + + do + evo.set(f1, evo.REQUIRES, { f2 }) + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + end + evo.set(f1, evo.REQUIRES, { f2, f3 }) + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == true) + end + evo.set(f3, evo.REQUIRES, { f4 }) + do + local e = evo.id() + evo.set(e, f1) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == true) + assert(evo.has(e, f4) and evo.get(e, f4) == true) + end + end +end From fbd9f9f970a2545622cc3b77daf9a6c14f95b475 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 6 Jun 2025 18:32:56 +0700 Subject: [PATCH 13/15] additional requries-fragment tests --- develop/all.lua | 2 + develop/fuzzing/requires_fuzz.lua | 113 ++++++++++++++++++++ develop/unbench.lua | 170 +++++++++++++++++++++++++++++- 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 develop/fuzzing/requires_fuzz.lua diff --git a/develop/all.lua b/develop/all.lua index 0b67fbf..d376b62 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -18,4 +18,6 @@ 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/fuzzing/requires_fuzz.lua b/develop/fuzzing/requires_fuzz.lua new file mode 100644 index 0000000..d0ad46c --- /dev/null +++ b/develop/fuzzing/requires_fuzz.lua @@ -0,0 +1,113 @@ +local evo = require 'evolved' + +evo.debug_mode(true) + +--- +--- +--- +--- +--- + +local __table_unpack = (function() + ---@diagnostic disable-next-line: deprecated + return table.unpack or unpack +end)() + +--- +--- +--- +--- +--- + +local all_fragment_list = {} ---@type evolved.fragment[] + +for i = 1, math.random(1, 10) do + local fragment = evo.builder() + :default(42) + :spawn() + all_fragment_list[i] = fragment +end + +for _, fragment in ipairs(all_fragment_list) do + if math.random(1, 2) == 1 then + for _ = 0, math.random(0, #all_fragment_list) do + local require_list = evo.get(fragment, evo.REQUIRES) or {} + require_list[#require_list + 1] = all_fragment_list[math.random(1, #all_fragment_list)] + evo.set(fragment, evo.REQUIRES, require_list) + end + end +end + +local all_entity_list = {} ---@type evolved.entity[] + +for i = 1, math.random(1, 10) do + local entity = evo.id() + all_entity_list[i] = entity + + for _ = 0, math.random(0, #all_fragment_list) do + local fragment = all_fragment_list[math.random(1, #all_fragment_list)] + + if math.random(1, 2) == 1 then + evo.set(entity, fragment, 42) + else + local query = evo.builder() + :include(all_fragment_list[math.random(1, #all_fragment_list)]) + :spawn() + evo.batch_set(query, fragment, 42) + evo.destroy(query) + end + end +end + +for _ = 1, math.random(1, #all_entity_list) do + local components = {} + for _ = 1, math.random(1, #all_fragment_list) do + local fragment = all_fragment_list[math.random(1, #all_fragment_list)] + components[fragment] = 42 + end + all_entity_list[#all_entity_list + 1] = evo.spawn(components) +end + +for _ = 1, math.random(1, #all_entity_list) do + local prefab = all_entity_list[math.random(1, #all_entity_list)] + all_entity_list[#all_entity_list + 1] = evo.clone(prefab) +end + +--- +--- +--- +--- +--- + +local function collect_required_fragments_for(fragment, req_fragment_set, req_fragment_list) + local fragment_requires = evo.get(fragment, evo.REQUIRES) or {} + for _, required_fragment in ipairs(fragment_requires) do + if not req_fragment_set[required_fragment] then + req_fragment_set[required_fragment] = true + req_fragment_list[#req_fragment_list + 1] = required_fragment + collect_required_fragments_for(required_fragment, req_fragment_set, req_fragment_list) + end + end +end + +for _, entity in ipairs(all_entity_list) do + for fragment in evo.each(entity) do + local req_fragment_list = {} + collect_required_fragments_for(fragment, {}, req_fragment_list) + for _, required_fragment in ipairs(req_fragment_list) do + assert(evo.has(entity, required_fragment)) + local required_component = evo.get(entity, required_fragment) + assert(required_component == 42) + end + end +end + +--- +--- +--- +--- +--- + +evo.destroy(__table_unpack(all_entity_list)) +evo.destroy(__table_unpack(all_fragment_list)) +evo.collect_garbage() diff --git a/develop/unbench.lua b/develop/unbench.lua index dad7af8..55d9014 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -7,6 +7,11 @@ local N = 1000 local B = evo.builder() local F1, F2, F3, F4, F5 = evo.id(5) local Q1 = evo.builder():include(F1):spawn() +local R1 = evo.builder():require(F1):spawn() +local R2 = evo.builder():require(F1, F2):spawn() +local R3 = evo.builder():require(F1, F2, F3):spawn() +local R4 = evo.builder():require(F1, F2, F3, F4):spawn() +local R5 = evo.builder():require(F1, F2, F3, F4, F5):spawn() print '----------------------------------------' @@ -610,7 +615,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo print '----------------------------------------' - basics.describe_bench(string.format('create and destroy %d entities with 1 components / clone', N), ---@param entities evolved.id[] function(entities) @@ -690,3 +694,167 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo end, function() return {} end) + +print '----------------------------------------' + +basics.describe_bench(string.format('create and destroy %d entities with 1 requires / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [R1] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 requires / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [R2] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 requires / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [R3] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 requires / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [R4] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 requires / spawn', N), + ---@param entities evolved.id[] + function(entities) + local spawn = evo.spawn + + local components = { [R5] = true } + + for i = 1, N do + entities[i] = spawn(components) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +print '----------------------------------------' + +basics.describe_bench(string.format('create and destroy %d entities with 1 requires / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [R1] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 2 requires / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [R2] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 3 requires / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [R3] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 4 requires / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [R4] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) + +basics.describe_bench(string.format('create and destroy %d entities with 5 requires / clone', N), + ---@param entities evolved.id[] + function(entities) + local clone = evo.clone + + local prefab = evo.spawn({ [R5] = true }) + + for i = 1, N do + entities[i] = clone(prefab) + end + + evo.batch_destroy(Q1) + end, function() + return {} + end) From c222a492572c176633555923bf3eea3505bcdd31 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 6 Jun 2025 21:18:09 +0700 Subject: [PATCH 14/15] fix the cloning with requires behavior for removed required fragments --- ROADMAP.md | 1 - develop/testing/requires_fragment_tests.lua | 14 ++++++++------ evolved.lua | 10 +++------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 2347516..6b91c36 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,6 +7,5 @@ - cached queries - observers and events - add INDEX fragment trait -- add REQUIRES fragment trait - use compact prefix-tree for chunks - optional ffi component storages diff --git a/develop/testing/requires_fragment_tests.lua b/develop/testing/requires_fragment_tests.lua index befa3a4..f1fcb1d 100644 --- a/develop/testing/requires_fragment_tests.lua +++ b/develop/testing/requires_fragment_tests.lua @@ -50,7 +50,8 @@ do assert(not evo.has(e, f2)) local e2 = evo.clone(e) - assert(not evo.has(e2, f2)) + assert(evo.has(e2, f2)) + assert(evo.get(e2, f2) == true) local e3 = evo.clone(e, { [f3] = true }) assert(evo.has(e3, f2)) @@ -101,7 +102,8 @@ do assert(not evo.has(e, f2)) local e2 = evo.clone(e) - assert(not evo.has(e2, f2)) + assert(evo.has(e2, f2)) + assert(evo.get(e2, f2) == 42) local e3 = evo.clone(e, { [f3] = true }) assert(evo.has(e3, f2)) @@ -161,8 +163,8 @@ do local e2 = evo.clone(e, { [f1] = 21 }) assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) - assert(not evo.has(e2, f2)) - assert(not evo.has(e2, f3)) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + assert(evo.has(e2, f3) and evo.get(e2, f3) == 42) local e3 = evo.clone(e, { [f3] = 21 }) assert(evo.has(e3, f1) and evo.get(e3, f1) == true) @@ -204,8 +206,8 @@ do local e2 = evo.clone(e, { [f1] = 21 }) assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) - assert(not evo.has(e2, f2)) - assert(not evo.has(e2, f3)) + assert(evo.has(e2, f2) and evo.get(e2, f2) == true) + assert(evo.has(e2, f3) and evo.get(e2, f3) == true) local e3 = evo.clone(e, { [f3] = 21 }) assert(evo.has(e3, f1) and evo.get(e3, f1) == 42) diff --git a/evolved.lua b/evolved.lua index 881fa44..7aa511e 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1635,7 +1635,7 @@ local function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_ __lua_table_move( chunk_fragment_list, 1, chunk_fragment_count, - 1, fragment_stack) + fragment_stack_size + 1, fragment_stack) fragment_stack_size = fragment_stack_size + chunk_fragment_count end @@ -2018,12 +2018,8 @@ local function __clone_entity(entity, prefab, components) ---@type evolved.fragment[] req_fragment_list = __acquire_table(__table_pool_tag.fragment_list) - for fragment in __lua_next, components do - if not prefab_chunk or not prefab_chunk.__fragment_set[fragment] then - req_fragment_count = __fragment_required_fragments(fragment, - req_fragment_set, req_fragment_list, req_fragment_count) - end - end + req_fragment_count = __chunk_required_fragments(ini_chunk, + req_fragment_set, req_fragment_list, req_fragment_count) for i = 1, req_fragment_count do local req_fragment = req_fragment_list[i] From 9a5ea20778d4d068fffa642b0f32869aea5715f7 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 6 Jun 2025 21:25:08 +0700 Subject: [PATCH 15/15] v1.1.0 --- README.md | 5 ++-- evolved.lua | 2 +- rockspecs/evolved.lua-1.1.0-0.rockspec | 34 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 rockspecs/evolved.lua-1.1.0-0.rockspec diff --git a/README.md b/README.md index b20920d..8229e56 100644 --- a/README.md +++ b/README.md @@ -1179,9 +1179,10 @@ builder_mt:destruction_policy :: id -> builder # Changelog -## vX.X.X-dev +## v1.1.0 -- Systems can be queries themselves +- [`Systems`](#systems) can be queries themselves now +- Added the new [`evolved.REQUIRES`](#evolvedrequires) fragment trait ## v1.0.0 diff --git a/evolved.lua b/evolved.lua index 7aa511e..7714f9b 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.0.0', + __VERSION = '1.1.0', __LICENSE = [[ MIT License diff --git a/rockspecs/evolved.lua-1.1.0-0.rockspec b/rockspecs/evolved.lua-1.1.0-0.rockspec new file mode 100644 index 0000000..8d279a9 --- /dev/null +++ b/rockspecs/evolved.lua-1.1.0-0.rockspec @@ -0,0 +1,34 @@ +rockspec_format = "3.0" +package = "evolved.lua" +version = "1.1.0-0" +source = { + url = "git://github.com/BlackMATov/evolved.lua", + tag = "v1.1.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", + } +}