From cd1a58368203f3abc7e7d63bb74a8a13fc34ab36 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 7 Feb 2026 08:08:43 +0700 Subject: [PATCH 1/4] lookup api and basic tests --- README.md | 22 +++++++ develop/all.lua | 1 + develop/testing/lookup_tests.lua | 110 +++++++++++++++++++++++++++++++ evolved.d.tl | 3 + 4 files changed, 136 insertions(+) create mode 100644 develop/testing/lookup_tests.lua diff --git a/README.md b/README.md index 0ccb7b0..071c792 100644 --- a/README.md +++ b/README.md @@ -1496,6 +1496,9 @@ execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_sta locate :: entity -> chunk?, integer +lookup :: string -> entity? +multi_lookup :: string -> entity[], integer + process :: system... -> () process_with :: system, ... -> () @@ -2001,6 +2004,25 @@ function evolved.execute(query) end function evolved.locate(entity) end ``` +### `evolved.lookup` + +```lua +---@param name string +---@return evolved.entity? entity +---@nodiscard +function evolved.lookup(name) end +``` + +### `evolved.multi_lookup` + +```lua +---@param name string +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function evolved.multi_lookup(name) end +``` + ### `evolved.process` ```lua diff --git a/develop/all.lua b/develop/all.lua index a4ff731..f6c928b 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -4,6 +4,7 @@ require 'develop.testing.clone_tests' require 'develop.testing.depth_tests' require 'develop.testing.destroy_tests' require 'develop.testing.locate_tests' +require 'develop.testing.lookup_tests' require 'develop.testing.main_tests' require 'develop.testing.mappers_tests' require 'develop.testing.multi_spawn_tests' diff --git a/develop/testing/lookup_tests.lua b/develop/testing/lookup_tests.lua new file mode 100644 index 0000000..a6c0441 --- /dev/null +++ b/develop/testing/lookup_tests.lua @@ -0,0 +1,110 @@ +local evo = require 'evolved' + +evo.debug_mode(true) + +do + local e1, e2, e3 = evo.id(3) + + do + assert(evo.lookup('hello') == nil) + assert(evo.lookup('world') == nil) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end + + do + local entity_list, entity_count = evo.multi_lookup('world') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end + end + + evo.set(e1, evo.NAME, 'hello') + + do + assert(evo.lookup('hello') == e1) + assert(evo.lookup('world') == nil) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 1 and entity_count == 1) + assert(entity_list[1] == e1) + end + + do + local entity_list, entity_count = evo.multi_lookup('world') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end + end + + evo.set(e2, evo.NAME, 'hello') + evo.set(e3, evo.NAME, 'hello') + + do + assert(evo.lookup('hello') == e3) + assert(evo.lookup('world') == nil) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 3 and entity_count == 3) + assert(entity_list[1] == e1 and entity_list[2] == e2 and entity_list[3] == e3) + end + end + + evo.set(e2, evo.NAME, 'world') + + do + assert(evo.lookup('hello') == e3) + assert(evo.lookup('world') == e2) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 2 and entity_count == 2) + assert(entity_list[1] == e1 and entity_list[2] == e3) + end + + do + local entity_list, entity_count = evo.multi_lookup('world') + assert(entity_list and #entity_list == 1 and entity_count == 1) + assert(entity_list[1] == e2) + end + end + + evo.set(e3, evo.NAME, 'world') + + do + assert(evo.lookup('hello') == e1) + assert(evo.lookup('world') == e3) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 1 and entity_count == 1) + assert(entity_list[1] == e1) + end + + do + local entity_list, entity_count = evo.multi_lookup('world') + assert(entity_list and #entity_list == 2 and entity_count == 2) + assert(entity_list[1] == e2 or entity_list[1] == e3) + end + end + + evo.remove(e1, evo.NAME) + + do + assert(evo.lookup('hello') == nil) + assert(evo.lookup('world') == e3) + + do + local entity_list, entity_count = evo.multi_lookup('hello') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end + + do + local entity_list, entity_count = evo.multi_lookup('world') + assert(entity_list and #entity_list == 2 and entity_count == 2) + assert(entity_list[1] == e2 or entity_list[1] == e3) + end + end +end diff --git a/evolved.d.tl b/evolved.d.tl index 76bcdcc..c31c92f 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -208,6 +208,9 @@ locate: function(entity: Entity): Chunk | nil, integer + lookup: function(name: string): Entity | nil + multi_lookup: function(name: string): { Entity }, integer + process: function(...: System) process_with: function(system: System, ...: any) From e49a339f5ed169f73029316e082b49531ff16a4a Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 8 Feb 2026 06:56:20 +0700 Subject: [PATCH 2/4] dummy lookup functions and name hooks --- evolved.lua | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/evolved.lua b/evolved.lua index 5086951..a94d03f 100644 --- a/evolved.lua +++ b/evolved.lua @@ -1155,6 +1155,9 @@ local __evolved_execute local __evolved_locate +local __evolved_lookup +local __evolved_multi_lookup + local __evolved_process local __evolved_process_with @@ -6086,6 +6089,19 @@ function __evolved_locate(entity) return entity_chunk, __entity_places[entity_primary] end +---@param name string +---@return evolved.entity? entity +---@nodiscard +function __evolved_lookup(name) +end + +---@param name string +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function __evolved_multi_lookup(name) +end + ---@param ... evolved.system systems function __evolved_process(...) local argument_count = __lua_select('#', ...) @@ -7147,6 +7163,24 @@ __evolved_set(__ON_ASSIGN, __UNIQUE) __evolved_set(__ON_INSERT, __UNIQUE) __evolved_set(__ON_REMOVE, __UNIQUE) +--- +--- +--- +--- +--- + +---@param entity evolved.entity +---@param new_name string +---@param old_name string +__evolved_set(__NAME, __ON_SET, function(entity, _, new_name, old_name) +end) + +---@param entity evolved.entity +---@param old_name string +__evolved_set(__NAME, __ON_REMOVE, function(entity, _, old_name) +end) + + --- --- --- @@ -7524,6 +7558,9 @@ evolved.execute = __evolved_execute evolved.locate = __evolved_locate +evolved.lookup = __evolved_lookup +evolved.multi_lookup = __evolved_multi_lookup + evolved.process = __evolved_process evolved.process_with = __evolved_process_with From 2c4cb179bcf7851471862c83e8519327439b9cb2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 9 Feb 2026 09:27:00 +0700 Subject: [PATCH 3/4] (multi_)lookup first impl --- develop/testing/lookup_tests.lua | 122 +++++++++++++++++++------ evolved.lua | 151 +++++++++++++++++++++++++++++-- 2 files changed, 237 insertions(+), 36 deletions(-) diff --git a/develop/testing/lookup_tests.lua b/develop/testing/lookup_tests.lua index a6c0441..bc1374d 100644 --- a/develop/testing/lookup_tests.lua +++ b/develop/testing/lookup_tests.lua @@ -6,85 +6,85 @@ do local e1, e2, e3 = evo.id(3) do - assert(evo.lookup('hello') == nil) - assert(evo.lookup('world') == nil) + assert(evo.lookup('lookup_hello') == nil) + assert(evo.lookup('lookup_world') == nil) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 0 and entity_count == 0) end do - local entity_list, entity_count = evo.multi_lookup('world') + local entity_list, entity_count = evo.multi_lookup('lookup_world') assert(entity_list and #entity_list == 0 and entity_count == 0) end end - evo.set(e1, evo.NAME, 'hello') + evo.set(e1, evo.NAME, 'lookup_hello') do - assert(evo.lookup('hello') == e1) - assert(evo.lookup('world') == nil) + assert(evo.lookup('lookup_hello') == e1) + assert(evo.lookup('lookup_world') == nil) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 1 and entity_count == 1) assert(entity_list[1] == e1) end do - local entity_list, entity_count = evo.multi_lookup('world') + local entity_list, entity_count = evo.multi_lookup('lookup_world') assert(entity_list and #entity_list == 0 and entity_count == 0) end end - evo.set(e2, evo.NAME, 'hello') - evo.set(e3, evo.NAME, 'hello') + evo.set(e2, evo.NAME, 'lookup_hello') + evo.set(e3, evo.NAME, 'lookup_hello') do - assert(evo.lookup('hello') == e3) - assert(evo.lookup('world') == nil) + assert(evo.lookup('lookup_hello') == e1) + assert(evo.lookup('lookup_world') == nil) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 3 and entity_count == 3) assert(entity_list[1] == e1 and entity_list[2] == e2 and entity_list[3] == e3) end end - evo.set(e2, evo.NAME, 'world') + evo.set(e2, evo.NAME, 'lookup_world') do - assert(evo.lookup('hello') == e3) - assert(evo.lookup('world') == e2) + assert(evo.lookup('lookup_hello') == e1) + assert(evo.lookup('lookup_world') == e2) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 2 and entity_count == 2) assert(entity_list[1] == e1 and entity_list[2] == e3) end do - local entity_list, entity_count = evo.multi_lookup('world') + local entity_list, entity_count = evo.multi_lookup('lookup_world') assert(entity_list and #entity_list == 1 and entity_count == 1) assert(entity_list[1] == e2) end end - evo.set(e3, evo.NAME, 'world') + evo.set(e3, evo.NAME, 'lookup_world') do - assert(evo.lookup('hello') == e1) - assert(evo.lookup('world') == e3) + assert(evo.lookup('lookup_hello') == e1) + assert(evo.lookup('lookup_world') == e2) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 1 and entity_count == 1) assert(entity_list[1] == e1) end do - local entity_list, entity_count = evo.multi_lookup('world') + local entity_list, entity_count = evo.multi_lookup('lookup_world') assert(entity_list and #entity_list == 2 and entity_count == 2) assert(entity_list[1] == e2 or entity_list[1] == e3) end @@ -93,18 +93,84 @@ do evo.remove(e1, evo.NAME) do - assert(evo.lookup('hello') == nil) - assert(evo.lookup('world') == e3) + assert(evo.lookup('lookup_hello') == nil) + assert(evo.lookup('lookup_world') == e2) do - local entity_list, entity_count = evo.multi_lookup('hello') + local entity_list, entity_count = evo.multi_lookup('lookup_hello') assert(entity_list and #entity_list == 0 and entity_count == 0) end do - local entity_list, entity_count = evo.multi_lookup('world') + local entity_list, entity_count = evo.multi_lookup('lookup_world') assert(entity_list and #entity_list == 2 and entity_count == 2) assert(entity_list[1] == e2 or entity_list[1] == e3) end end end + +do + local e1, e2, e3 = evo.id(3) + + evo.set(e1, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 1 and entity_count == 1) + assert(entity_list[1] == e1) + end + + evo.set(e2, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 2 and entity_count == 2) + assert(entity_list[1] == e1 and entity_list[2] == e2) + end + + evo.set(e3, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 3 and entity_count == 3) + assert(entity_list[1] == e1 and entity_list[2] == e2 and entity_list[3] == e3) + end + + evo.clear(e1, e2, e3) + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end + + evo.set(e3, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 1 and entity_count == 1) + assert(entity_list[1] == e3) + end + + evo.set(e2, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 2 and entity_count == 2) + assert(entity_list[1] == e3 and entity_list[2] == e2) + end + + evo.set(e1, evo.NAME, 'lookup_e') + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 3 and entity_count == 3) + assert(entity_list[1] == e3 and entity_list[2] == e2 and entity_list[3] == e1) + end + + evo.destroy(e3, e2, e1) + + do + local entity_list, entity_count = evo.multi_lookup('lookup_e') + assert(entity_list and #entity_list == 0 and entity_count == 0) + end +end diff --git a/evolved.lua b/evolved.lua index a94d03f..451369b 100644 --- a/evolved.lua +++ b/evolved.lua @@ -144,6 +144,9 @@ local __major_queries = {} ---@type table +local __named_entities = {} ---@type table> + local __sorted_includes = {} ---@type table> local __sorted_excludes = {} ---@type table> local __sorted_variants = {} ---@type table> @@ -896,6 +899,22 @@ function __assoc_list_fns.new(reserve) } end +---@generic K +---@param ... K +---@return evolved.assoc_list +---@nodiscard +function __assoc_list_fns.from(...) + local item_count = __lua_select('#', ...) + + local al = __assoc_list_fns.new(item_count) + + for item_index = 1, item_count do + __assoc_list_fns.insert(al, __lua_select(item_index, ...)) + end + + return al +end + ---@generic K ---@param src_item_list K[] ---@param src_item_first integer @@ -1039,6 +1058,46 @@ function __assoc_list_fns.remove_ex(al_item_set, al_item_list, al_item_count, it return al_item_count end +---@generic K +---@param al evolved.assoc_list +---@param item K +---@return integer new_al_count +function __assoc_list_fns.unordered_remove(al, item) + local new_al_count = __assoc_list_fns.unordered_remove_ex( + al.__item_set, al.__item_list, al.__item_count, + item) + + al.__item_count = new_al_count + return new_al_count +end + +---@generic K +---@param al_item_set table +---@param al_item_list K[] +---@param al_item_count integer +---@param item K +---@return integer new_al_count +---@nodiscard +function __assoc_list_fns.unordered_remove_ex(al_item_set, al_item_list, al_item_count, item) + local item_index = al_item_set[item] + + if not item_index then + return al_item_count + end + + if item_index ~= al_item_count then + local al_last_item = al_item_list[al_item_count] + al_item_set[al_last_item] = item_index + al_item_list[item_index] = al_last_item + end + + al_item_set[item] = nil + al_item_list[al_item_count] = nil + al_item_count = al_item_count - 1 + + return al_item_count +end + --- --- --- @@ -1090,6 +1149,11 @@ local __DESTRUCTION_POLICY_REMOVE_FRAGMENT = __acquire_id() --- local __safe_tbls = { + __EMPTY_ENTITY_LIST = __lua_setmetatable({}, { + __tostring = function() return 'empty entity list' end, + __newindex = function() __error_fmt 'attempt to modify empty entity list' end + }) --[=[@as evolved.id[]]=], + __EMPTY_FRAGMENT_SET = __lua_setmetatable({}, { __tostring = function() return 'empty fragment set' end, __newindex = function() __error_fmt 'attempt to modify empty fragment set' end @@ -4860,7 +4924,7 @@ end ---@return integer entity_count function __evolved_multi_spawn(entity_count, component_table, component_mapper) if entity_count <= 0 then - return {}, 0 + return __safe_tbls.__EMPTY_ENTITY_LIST, 0 end if __debug_mode then @@ -4941,7 +5005,7 @@ end ---@return integer entity_count function __evolved_multi_clone(entity_count, prefab, component_table, component_mapper) if entity_count <= 0 then - return {}, 0 + return __safe_tbls.__EMPTY_ENTITY_LIST, 0 end if __debug_mode then @@ -6093,6 +6157,7 @@ end ---@return evolved.entity? entity ---@nodiscard function __evolved_lookup(name) + return __named_entity[name] end ---@param name string @@ -6100,6 +6165,25 @@ end ---@return integer entity_count ---@nodiscard function __evolved_multi_lookup(name) + do + local named_entities = __named_entities[name] + local named_entity_list = named_entities and named_entities.__item_list + local named_entity_count = named_entities and named_entities.__item_count or 0 + + if named_entity_count > 0 then + return __list_fns.dup(named_entity_list, named_entity_count), named_entity_count + end + end + + do + local named_entity = __named_entity[name] + + if named_entity then + return { named_entity }, 1 + end + end + + return __safe_tbls.__EMPTY_ENTITY_LIST, 0 end ---@param ... evolved.system systems @@ -6497,7 +6581,7 @@ end ---@return integer entity_count function __builder_mt:multi_spawn(entity_count, component_mapper) if entity_count <= 0 then - return {}, 0 + return __safe_tbls.__EMPTY_ENTITY_LIST, 0 end local chunk = self.__chunk @@ -6581,7 +6665,7 @@ end ---@return integer entity_count function __builder_mt:multi_clone(entity_count, prefab, component_mapper) if entity_count <= 0 then - return {}, 0 + return __safe_tbls.__EMPTY_ENTITY_LIST, 0 end local component_table = self.__component_table @@ -7169,18 +7253,69 @@ __evolved_set(__ON_REMOVE, __UNIQUE) --- --- +---@param name string ---@param entity evolved.entity ----@param new_name string ----@param old_name string +local function __insert_named_entity(name, entity) + ---@type evolved.entity? + local named_entity = __named_entity[name] + + if not named_entity then + __named_entity[name] = entity + return + end + + ---@type evolved.assoc_list? + local named_entities = __named_entities[name] + + if not named_entities then + __named_entities[name] = __assoc_list_fns.from(named_entity, entity) + return + end + + __assoc_list_fns.insert(named_entities, entity) +end + +---@param name string +---@param entity evolved.entity +local function __remove_named_entity(name, entity) + ---@type evolved.assoc_list? + local named_entities = __named_entities[name] + + if named_entities then + if __assoc_list_fns.remove(named_entities, entity) == 0 then + __named_entities[name], named_entities = nil, nil + end + end + + ---@type evolved.entity? + local named_entity = __named_entity[name] + + if named_entity == entity then + __named_entity[name] = named_entities and named_entities.__item_list[1] or nil + end +end + +---@param entity evolved.entity +---@param new_name? string +---@param old_name? string __evolved_set(__NAME, __ON_SET, function(entity, _, new_name, old_name) + if old_name then + __remove_named_entity(old_name, entity) + end + + if new_name then + __insert_named_entity(new_name, entity) + end end) ---@param entity evolved.entity ----@param old_name string +---@param old_name? string __evolved_set(__NAME, __ON_REMOVE, function(entity, _, old_name) + if old_name then + __remove_named_entity(old_name, entity) + end end) - --- --- --- From 655c0aef07c2d01c1b751eccc58a00719a7c6798 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 9 Feb 2026 09:47:18 +0700 Subject: [PATCH 4/4] update README --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 071c792..dac0735 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - [Internal Fragments](#internal-fragments) - [Shared Components](#shared-components) - [Fragment Requirements](#fragment-requirements) + - [Id Names](#id-names) - [Destruction Policies](#destruction-policies) - [Custom Component Storages](#custom-component-storages) - [Garbage Collection](#garbage-collection) @@ -64,6 +65,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) + - [vX.Y.Z](#vxyz) - [v1.9.0](#v190) - [v1.8.0](#v180) - [v1.7.0](#v170) @@ -1161,6 +1163,41 @@ local enemy = evolved.builder() assert(evolved.has_all(enemy, position, velocity)) ``` +#### Id Names + +The library provides a way to assign names to any id using the [`evolved.NAME`](#evolvedname) fragment. This is useful for debugging and development purposes, as it allows you to identify entities or fragments by their names instead of their identifiers. The name of an entity can be retrieved using the [`evolved.name`](#evolvedname-1) function. + +```lua +local evolved = require 'evolved' + +local player = evolved.builder() + :name('Player') + :build() + +assert(evolved.name(player) == 'Player') +``` + +Names are not unique, so multiple entities can have the same name. Also, the name of an entity can be changed at any time by setting a new name using the [`evolved.NAME`](#evolvedname) fragment as a usual component. + +You can find entities by their names using the [`evolved.lookup`](#evolvedlookup) and [`evolved.multi_lookup`](#evolvedmulti_lookup) functions. The [`evolved.lookup`](#evolvedlookup) function returns the first entity with the specified name, while the [`evolved.multi_lookup`](#evolvedmulti_lookup) function returns a list of all entities with the specified name. + +```lua +local evolved = require 'evolved' + +local player1 = evolved.builder() + :name('Player') + :build() + +local player2 = evolved.builder() + :name('Player') + :build() + +assert(evolved.lookup('Player') == player1) + +local player_list, player_count = evolved.multi_lookup('Player') +assert(player_count == 2 and player_list[1] == player1 and player_list[2] == player2) +``` + #### 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. @@ -1588,6 +1625,10 @@ builder_mt:destruction_policy :: id -> builder ## Changelog +### vX.Y.Z + +- Added the new [`evolved.lookup`](#evolvedlookup) and [`evolved.multi_lookup`](#evolvedmulti_lookup) functions that allow finding ids by their names + ### v1.9.0 - Performance improvements of the [`evolved.destroy`](#evolveddestroy) and [`evolved.batch_destroy`](#evolvedbatch_destroy) functions