From dc912eb6da3748c65b677932934bd0ace2b54e96 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 6 Oct 2025 19:05:16 +0700 Subject: [PATCH] the universal builder:build/multi_build methods --- README.md | 88 ++++++++++++++++++++------------- ROADMAP.md | 1 - develop/all.lua | 1 + develop/testing/build_tests.lua | 41 +++++++++++++++ evolved.lua | 22 +++++++++ 5 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 develop/testing/build_tests.lua diff --git a/README.md b/README.md index 2fc5d34..546fa4e 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ local health, stamina = evolved.id(2) local enemy = evolved.builder() :set(health, 100) :set(stamina, 50) - :spawn() + :build() ``` Builders can be reused, so you can create a builder with a specific set of fragments and components and then use it to spawn multiple entities with the same fragments and components. @@ -503,16 +503,16 @@ local evolved = require 'evolved' local health = evolved.builder() :name('health') - :spawn() + :build() local stamina = evolved.builder() :name('stamina') - :spawn() + :build() local player = evolved.builder() :set(health, 100) :set(stamina, 50) - :spawn() + :build() for fragment, component in evolved.each(player) do print(string.format('Fragment (%s) has value %d', @@ -601,7 +601,7 @@ The builder interface can be used to create queries too. It is more convenient t local query = evolved.builder() :include(health, poisoned) :exclude(resistant) - :spawn() + :build() ``` We don't have to set both [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes) fragments, we can even do it without filters at all, then the query will match all chunks in the world. @@ -658,7 +658,7 @@ local health, poisoned = evolved.id(2) local player = evolved.builder() :set(health, 100) :set(poisoned, true) - :spawn() + :build() -- start a deferred scope evolved.defer() @@ -686,7 +686,7 @@ local health, poisoned = evolved.id(2) local player = evolved.builder() :set(health, 100) :set(poisoned, true) - :spawn() + :build() -- start a deferred scope evolved.defer() @@ -734,7 +734,7 @@ local destroying_mark = evolved.id() local destroying_mark_query = evolved.builder() :include(destroying_mark) - :spawn() + :build() -- destroy all entities with the destroying_mark fragment evolved.batch_destroy(destroying_mark_query) @@ -756,7 +756,7 @@ local health, max_health = evolved.id(2) local query = evolved.builder() :include(health, max_health) - :spawn() + :build() local system = evolved.builder() :query(query) @@ -769,7 +769,7 @@ local system = evolved.builder() health_components[i] + 1, max_health_components[i]) end - end):spawn() + end):build() ``` The [`evolved.process`](#evolvedprocess) function is used to process systems. It takes systems as arguments and executes them in the order they were passed. @@ -796,7 +796,7 @@ local system = evolved.builder() health_components[i] - 1, 0) end - end):spawn() + end):build() evolved.process(system) ``` @@ -815,7 +815,7 @@ local velocity_x, velocity_y = evolved.id(2) local physical_body_query = evolved.builder() :include(position_x, position_y) :include(velocity_x, velocity_y) - :spawn() + :build() local physics_group = evolved.id() @@ -830,7 +830,7 @@ evolved.builder() vx[i] = vx[i] + gravity_x vy[i] = vy[i] + gravity_y end - end):spawn() + end):build() evolved.builder() :group(physics_group) @@ -846,7 +846,7 @@ evolved.builder() px[i] = px[i] + vx[i] py[i] = py[i] + vy[i] end - end):spawn() + end):build() evolved.process(physics_group) ``` @@ -863,7 +863,7 @@ local system = evolved.builder() :epilogue(function() print('Epilogue') end) - :spawn() + :build() evolved.process(system) ``` @@ -905,7 +905,7 @@ local evolved = require 'evolved' local health = evolved.builder() :on_set(function(entity, fragment, component) print('health set to ' .. component) - end):spawn() + end):build() local player = evolved.id() evolved.set(player, health, 100) -- prints "health set to 100" @@ -930,7 +930,7 @@ local enemy_prefab = evolved.builder() :prefab() :set(health, 100) :set(stamina, 50) - :spawn() + :build() local enemy_clone = evolved.clone(enemy_prefab) @@ -952,16 +952,16 @@ local evolved = require 'evolved' local enemy_tag = evolved.builder() :tag() - :spawn() + :build() local only_enabled_enemies = evolved.builder() :include(enemy_tag) - :spawn() + :build() local all_enemies_including_disabled = evolved.builder() :include(enemy_tag) :include(evolved.DISABLED) - :spawn() + :build() ``` #### Shared Components @@ -977,11 +977,11 @@ local position = evolved.id() local enemy1 = evolved.builder() :set(position, initial_position) - :spawn() + :build() local enemy2 = evolved.builder() :set(position, initial_position) - :spawn() + :build() -- the enemy1 and enemy2 share the same table, -- and that's definitely not what we want in this case @@ -1006,15 +1006,15 @@ end local position = evolved.builder() :default(vector2(0, 0)) :duplicate(vector2_duplicate) - :spawn() + :build() local enemy1 = evolved.builder() :set(position) - :spawn() + :build() local enemy2 = evolved.builder() :set(position) - :spawn() + :build() -- the enemy1 and enemy2 have different tables now assert(evolved.get(enemy1, position) ~= evolved.get(enemy2, position)) @@ -1030,21 +1030,21 @@ local evolved = require 'evolved' local position = evolved.builder() :default(vector2(0, 0)) :duplicate(vector2_duplicate) - :spawn() + :build() local velocity = evolved.builder() :default(vector2(0, 0)) :duplicate(vector2_duplicate) - :spawn() + :build() local physical = evolved.builder() :tag() :require(position, velocity) - :spawn() + :build() local enemy = evolved.builder() :set(physical) - :spawn() + :build() assert(evolved.has_all(enemy, position, velocity)) ``` @@ -1058,11 +1058,11 @@ local evolved = require 'evolved' local world = evolved.builder() :tag() - :spawn() + :build() local entity = evolved.builder() :set(world) - :spawn() + :build() -- destroy the world fragment that is attached to the entity evolved.destroy(world) @@ -1083,11 +1083,11 @@ local evolved = require 'evolved' local world = evolved.builder() :tag() :destruction_policy(evolved.DESTRUCTION_POLICY_DESTROY_ENTITY) - :spawn() + :build() local entity = evolved.builder() :set(world) - :spawn() + :build() -- destroy the world fragment that is attached to the entity evolved.destroy(world) @@ -1246,6 +1246,9 @@ chunk_mt:components :: fragment... -> storage... ``` builder :: builder +builder_mt:build :: entity? -> entity +builder_mt:multi_build :: integer, entity? -> entity[], integer + builder_mt:spawn :: entity builder_mt:multi_spawn :: integer -> entity[], integer @@ -1300,6 +1303,7 @@ builder_mt:destruction_policy :: id -> builder ### vX.X.X - Improved query execution performance by caching some internal calculations +- Added the universal [`builder.build`](#evolvedbuilder_mtbuild) and [`builder.multi_build`](#evolvedbuilder_mtmulti_build) methods that can be used to spawn or clone entities depending on the method arguments ### v1.3.0 @@ -1783,6 +1787,24 @@ function evolved.chunk_mt:components(...) end function evolved.builder() end ``` +#### `evolved.builder_mt:build` + +```lua +---@param prefab? evolved.entity +---@return evolved.entity entity +function evolved.builder_mt:build(prefab) end +``` + +### `evolved.builder_mt:multi_build` + +```lua +---@param entity_count integer +---@param prefab? evolved.entity +---@return evolved.entity[] entity_list +---@return integer entity_count +function evolved.builder_mt:multi_build(entity_count, prefab) end +``` + #### `evolved.builder_mt:spawn` ```lua diff --git a/ROADMAP.md b/ROADMAP.md index 697f3f7..b82e52b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,7 +10,6 @@ ## Thoughts - We can return deferred status from modifying operations and spawn/clone methods. -- Should we make one builder:build method instead of :spawn and :clone? ## Known Issues diff --git a/develop/all.lua b/develop/all.lua index 7c5c79b..a43de7f 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,5 +1,6 @@ require 'develop.samples.systems' +require 'develop.testing.build_tests' require 'develop.testing.cancel_tests' require 'develop.testing.locate_tests' require 'develop.testing.multi_spawn_tests' diff --git a/develop/testing/build_tests.lua b/develop/testing/build_tests.lua new file mode 100644 index 0000000..626b88b --- /dev/null +++ b/develop/testing/build_tests.lua @@ -0,0 +1,41 @@ +local evo = require 'evolved' + +do + local f1, f2 = evo.id(2) + + do + local e = evo.builder():set(f1, 42):set(f2, 'hello'):build() + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') + end + + do + local p = evo.builder():set(f1, 42):build() + local e = evo.builder():set(f2, 'hello'):build(p) + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') + end + + do + local entity_list, entity_count = evo.builder():set(f1, 42):set(f2, 'hello'):multi_build(5) + assert(entity_count == 5) + + for i = 1, entity_count do + local e = entity_list[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') + end + end + + do + local p = evo.builder():set(f1, 42):build() + local entity_list, entity_count = evo.builder():set(f2, 'hello'):multi_build(5, p) + assert(entity_count == 5) + + for i = 1, entity_count do + local e = entity_list[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 42) + assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') + end + end +end diff --git a/evolved.lua b/evolved.lua index b6b268d..971d497 100644 --- a/evolved.lua +++ b/evolved.lua @@ -6341,6 +6341,28 @@ function __builder_mt:__tostring() return __lua_string_format('<%s>', __lua_table_concat(fragment_names, ', ')) end +---@param prefab? evolved.entity +---@return evolved.entity entity +function __builder_mt:build(prefab) + if prefab then + return self:clone(prefab) + else + return self:spawn() + end +end + +---@param entity_count integer +---@param prefab? evolved.entity +---@return evolved.entity[] entity_list +---@return integer entity_count +function __builder_mt:multi_build(entity_count, prefab) + if prefab then + return self:multi_clone(entity_count, prefab) + else + return self:multi_spawn(entity_count) + end +end + ---@return evolved.entity entity function __builder_mt:spawn() local chunk = self.__chunk