Merge branch 'feature/lookup' into dev

This commit is contained in:
BlackMATov
2026-02-09 09:49:15 +07:00
5 changed files with 419 additions and 4 deletions

View File

@@ -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.
@@ -1496,6 +1533,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, ... -> ()
@@ -1585,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
@@ -2001,6 +2045,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

View File

@@ -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'

View File

@@ -0,0 +1,176 @@
local evo = require 'evolved'
evo.debug_mode(true)
do
local e1, e2, e3 = evo.id(3)
do
assert(evo.lookup('lookup_hello') == nil)
assert(evo.lookup('lookup_world') == nil)
do
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('lookup_world')
assert(entity_list and #entity_list == 0 and entity_count == 0)
end
end
evo.set(e1, evo.NAME, 'lookup_hello')
do
assert(evo.lookup('lookup_hello') == e1)
assert(evo.lookup('lookup_world') == nil)
do
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('lookup_world')
assert(entity_list and #entity_list == 0 and entity_count == 0)
end
end
evo.set(e2, evo.NAME, 'lookup_hello')
evo.set(e3, evo.NAME, 'lookup_hello')
do
assert(evo.lookup('lookup_hello') == e1)
assert(evo.lookup('lookup_world') == nil)
do
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, 'lookup_world')
do
assert(evo.lookup('lookup_hello') == e1)
assert(evo.lookup('lookup_world') == e2)
do
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('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, 'lookup_world')
do
assert(evo.lookup('lookup_hello') == e1)
assert(evo.lookup('lookup_world') == e2)
do
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('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('lookup_hello') == nil)
assert(evo.lookup('lookup_world') == e2)
do
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('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

View File

@@ -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)

View File

@@ -144,6 +144,9 @@ local __major_queries = {} ---@type table<evolved.fragment, evolved.assoc_list<e
local __entity_chunks = {} ---@type (evolved.chunk|false)[]
local __entity_places = {} ---@type integer[]
local __named_entity = {} ---@type table<string, evolved.entity>
local __named_entities = {} ---@type table<string, evolved.assoc_list<evolved.entity>>
local __sorted_includes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_variants = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
@@ -896,6 +899,22 @@ function __assoc_list_fns.new(reserve)
}
end
---@generic K
---@param ... K
---@return evolved.assoc_list<K>
---@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<K>
---@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<K, integer>
---@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
@@ -1155,6 +1219,9 @@ local __evolved_execute
local __evolved_locate
local __evolved_lookup
local __evolved_multi_lookup
local __evolved_process
local __evolved_process_with
@@ -4857,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
@@ -4938,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
@@ -6086,6 +6153,39 @@ function __evolved_locate(entity)
return entity_chunk, __entity_places[entity_primary]
end
---@param name string
---@return evolved.entity? entity
---@nodiscard
function __evolved_lookup(name)
return __named_entity[name]
end
---@param name string
---@return evolved.entity[] entity_list
---@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
function __evolved_process(...)
local argument_count = __lua_select('#', ...)
@@ -6481,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
@@ -6565,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
@@ -7153,6 +7253,75 @@ __evolved_set(__ON_REMOVE, __UNIQUE)
---
---
---@param name string
---@param entity evolved.entity
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<evolved.entity>?
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<evolved.entity>?
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
__evolved_set(__NAME, __ON_REMOVE, function(entity, _, old_name)
if old_name then
__remove_named_entity(old_name, entity)
end
end)
---
---
---
---
---
---@param query evolved.query
local function __insert_query(query)
local query_includes = __sorted_includes[query]
@@ -7524,6 +7693,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