diff --git a/README.md b/README.md index ba1ade2..d738460 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ - [Structural Changes](#structural-changes) - [Spawning Entities](#spawning-entities) - [Entity Builders](#entity-builders) + - [Multi-Entity Spawning](#multi-entity-spawning) - [Access Operations](#access-operations) - [Iterating Over Fragments](#iterating-over-fragments) - [Modifying Operations](#modifying-operations) @@ -53,6 +54,7 @@ - [Shared Components](#shared-components) - [Fragment Requirements](#fragment-requirements) - [Destruction Policies](#destruction-policies) + - [Custom Component Storages](#custom-component-storages) - [Cheat Sheet](#cheat-sheet) - [Aliases](#aliases) - [Predefs](#predefs) @@ -61,6 +63,7 @@ - [Chunk](#chunk) - [Builder](#builder) - [Changelog](#changelog) + - [v1.8.0](#v180) - [v1.7.0](#v170) - [v1.6.0](#v160) - [v1.5.0](#v150) @@ -418,17 +421,19 @@ You should try to avoid structural changes, especially in performance-critical c #### Spawning Entities ```lua ----@param components? table +---@param component_table? table +---@param component_mapper? fun(chunk: evolved.chunk, place: integer) ---@return evolved.entity -function evolved.spawn(components) end +function evolved.spawn(component_table, component_mapper) end ---@param prefab evolved.entity ----@param components? table +---@param component_table? table +---@param component_mapper? fun(chunk: evolved.chunk, place: integer) ---@return evolved.entity -function evolved.clone(prefab, components) end +function evolved.clone(prefab, component_table, component_mapper) end ``` -The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. +The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `component_table` every time; consider using a predefined table for maximum performance. You can also use the [`evolved.clone`](#evolvedclone) function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. @@ -472,6 +477,54 @@ local enemy = evolved.builder() 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. +#### Multi-Entity Spawning + +When you need to spawn multiple entities with identical fragments, use `multi_spawn` and `multi_clone` to optimize performance and reduce overhead. + +```lua +---@param entity_count integer +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +---@return evolved.entity[] entity_list +---@return integer entity_count +function evolved.multi_spawn(entity_count, component_table, component_mapper) end + +---@param entity_count integer +---@param prefab evolved.entity +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +---@return evolved.entity[] entity_list +---@return integer entity_count +function evolved.multi_clone(entity_count, prefab, component_table, component_mapper) end +``` + +These functions behave like their single-entity counterparts, but they allow you to spawn or clone multiple entities in one call. This approach minimizes the overhead of repeated function calls and structural changes, improving performance when handling large numbers of entities. + +Typically, when spawning multiple entities, they share the same set of fragments, but their components can differ. You can achieve this by providing a `component_mapper` function, which receives the chunk and the range of places for the newly spawned entities. This avoids many `evolved.set` calls after spawning, which can be costly when creating many entities. + +Here is a small example of using `evolved.multi_spawn` with a `component_mapper`: + +```lua +local evolved = require 'evolved' + +local position_x, position_y = evolved.id(2) + +evolved.multi_spawn(1000, { + [position_x] = 0, + [position_y] = 0, +}, function(chunk, b_place, e_place) + local x_components = chunk:components(position_x) + local y_components = chunk:components(position_y) + + for i = b_place, e_place do + x_components[i] = math.random(-100, 100) + y_components[i] = math.random(-100, 100) + end +end) +``` + +Of course, you can use `evolved.multi_clone` in the same way. Builders can also be used for multi-entity spawning and cloning by calling the corresponding methods on the builder object. + ### Access Operations The library provides all the necessary functions to access entities and their components. I'm not going to cover all the accessor functions here, because they are pretty straightforward and self-explanatory. You can check the [API Reference](#api-reference) for all of them. Here are some of the most important ones: @@ -1153,6 +1206,145 @@ evolved.destroy(world) assert(not evolved.alive(entity)) ``` +#### Custom Component Storages + +In some cases, you might want custom storages for fragment components. For example, you might want to store components in a specialized way for performance reasons. The library provides two fragment traits for this purpose: [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove). + +The [`evolved.REALLOC`](#evolvedrealloc) trait expects a function that is called when the fragment storage needs to be reallocated. The [`evolved.COMPMOVE`](#evolvedcompmove) trait expects a function that is called when components need to be moved from one storage to another. + +A canonical example of using custom storages is implementing a fragment that stores components in an FFI-backed storage for better processing performance in LuaJIT. This is an advanced topic and requires a good understanding of LuaJIT FFI and memory management. So I won't cover it here in detail, but here is a simple example to give you an idea of how it works. More information can be found on the [LuaJIT](https://luajit.org/ext_ffi.html) website. + +```lua +local ffi = require 'ffi' +local evolved = require 'evolved' + +-- +-- +-- Define FFI double storage realloc and compmove functions +-- +-- + +local FFI_DOUBLE_TYPEOF = ffi.typeof('double') +local FFI_DOUBLE_SIZEOF = ffi.sizeof(FFI_DOUBLE_TYPEOF) +local FFI_DOUBLE_STORAGE_TYPEOF = ffi.typeof('double[?]') + +---@param src ffi.cdata*? +---@param src_size integer +---@param dst_size integer +---@return ffi.cdata*? +local function FFI_DOUBLE_STORAGE_REALLOC(src, src_size, dst_size) + if dst_size == 0 then + -- freeing the src storage, just let the GC handle it + return + end + + -- to support 1-based indexing, allocate one extra element + local dst = ffi.new(FFI_DOUBLE_STORAGE_TYPEOF, dst_size + 1) + + if src and src_size > 0 then + -- handle both expanding and shrinking + local min_size = math.min(src_size, dst_size) + ffi.copy(dst + 1, src + 1, min_size * FFI_DOUBLE_SIZEOF) + end + + return dst +end + +---@param src ffi.cdata* +---@param f integer +---@param e integer +---@param t integer +---@param dst ffi.cdata* +local function FFI_DOUBLE_STORAGE_COMPMOVE(src, f, e, t, dst) + ffi.copy(dst + t, src + f, (e - f + 1) * FFI_DOUBLE_SIZEOF) +end + +-- +-- +-- Define fragments with our custom FFI storages +-- +-- + +local POSITION_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local POSITION_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +-- +-- +-- Define a movement system that uses these components +-- +-- + +local MOVEMENT_SYSTEM = evolved.builder() + :include(POSITION_X, POSITION_Y) + :include(VELOCITY_X, VELOCITY_Y) + :execute(function(chunk, entity_list, entity_count, delta_time) + local position_xs, position_ys = chunk:components(POSITION_X, POSITION_Y) + local velocity_xs, velocity_ys = chunk:components(VELOCITY_X, VELOCITY_Y) + + for i = 1, entity_count do + local px, py = position_xs[i], position_ys[i] + local vx, vy = velocity_xs[i], velocity_ys[i] + + px = px + vx * delta_time + py = py + vy * delta_time + + position_xs[i], position_ys[i] = px, py + end + end):build() + +-- +-- +-- Spawn some entities with these components +-- +-- + +evolved.builder() + :set(POSITION_X) + :set(POSITION_Y) + :set(VELOCITY_X) + :set(VELOCITY_Y) + :multi_spawn(10000, function(chunk, b_place, e_place) + local position_xs, position_ys = chunk:components(POSITION_X, POSITION_Y) + local velocity_xs, velocity_ys = chunk:components(VELOCITY_X, VELOCITY_Y) + + for place = b_place, e_place do + position_xs[place] = math.random(0, 640) + position_ys[place] = math.random(0, 480) + velocity_xs[place] = math.random(-100, 100) + velocity_ys[place] = math.random(-100, 100) + end + end) + +-- +-- +-- Process the movement system with a delta time payload +-- +-- + +evolved.process_with(MOVEMENT_SYSTEM, 0.016) +``` + ## Cheat Sheet ### Aliases @@ -1168,9 +1360,15 @@ system :: id component :: any storage :: component[] +component_table :: +component_mapper :: {chunk, integer, integer} + default :: component duplicate :: {component -> component} +realloc :: {storage?, integer, integer -> storage?} +compmove :: {storage, integer, integer, integer, storage} + execute :: {chunk, entity[], integer, any...} prologue :: {any...} epilogue :: {any...} @@ -1200,6 +1398,9 @@ INTERNAL :: fragment DEFAULT :: fragment DUPLICATE :: fragment +REALLOC :: fragment +COMPMOVE :: fragment + PREFAB :: fragment DISABLED :: fragment @@ -1240,11 +1441,11 @@ depth :: integer commit :: boolean cancel :: boolean -spawn :: ? -> entity -multi_spawn :: integer, ? -> entity[], integer +spawn :: component_table?, component_mapper? -> entity +multi_spawn :: integer, component_table?, component_mapper? -> entity[], integer -clone :: entity, ? -> entity -multi_clone :: integer, entity, ? -> entity[], integer +clone :: entity, component_table?, component_mapper? -> entity +multi_clone :: integer, entity, component_table?, component_mapper? -> entity[], integer alive :: entity -> boolean alive_all :: entity... -> boolean @@ -1258,7 +1459,7 @@ has :: entity, fragment -> boolean has_all :: entity, fragment... -> boolean has_any :: entity, fragment... -> boolean -get :: entity, fragment... -> component... +get :: entity, fragment... -> component... set :: entity, fragment, component -> () remove :: entity, fragment... -> () @@ -1306,14 +1507,14 @@ chunk_mt:components :: fragment... -> storage... ``` builder :: builder -builder_mt:build :: entity? -> entity -builder_mt:multi_build :: integer, entity? -> entity[], integer +builder_mt:build :: entity?, component_mapper? -> entity +builder_mt:multi_build :: integer, entity?, component_mapper? -> entity[], integer -builder_mt:spawn :: entity -builder_mt:multi_spawn :: integer -> entity[], integer +builder_mt:spawn :: component_mapper? -> entity +builder_mt:multi_spawn :: integer, component_mapper? -> entity[], integer -builder_mt:clone :: entity -> entity -builder_mt:multi_clone :: integer, entity -> entity[], integer +builder_mt:clone :: entity, component_mapper? -> entity +builder_mt:multi_clone :: integer, entity, component_mapper? -> entity[], integer builder_mt:has :: fragment -> boolean builder_mt:has_all :: fragment... -> boolean @@ -1335,6 +1536,9 @@ builder_mt:internal :: builder builder_mt:default :: component -> builder builder_mt:duplicate :: {component -> component} -> builder +builder_mt:realloc :: {storage?, integer, integer -> storage?} -> builder +builder_mt:compmove :: {storage, integer, integer, integer, storage} -> builder + builder_mt:prefab :: builder builder_mt:disabled :: builder @@ -1361,6 +1565,11 @@ builder_mt:destruction_policy :: id -> builder ## Changelog +### v1.8.0 + +- Added the new [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove) fragment traits that allow customizing component storages +- Added `component_mapper` argument to the spawning and cloning functions that allows filling components in chunks during the operation + ### v1.7.0 - Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries @@ -1430,6 +1639,10 @@ builder_mt:destruction_policy :: id -> builder ### `evolved.DUPLICATE` +### `evolved.REALLOC` + +### `evolved.COMPMOVE` + ### `evolved.PREFAB` ### `evolved.DISABLED` @@ -1538,28 +1751,31 @@ function evolved.cancel() end ### `evolved.spawn` ```lua ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.spawn(components) end +function evolved.spawn(component_table, component_mapper) end ``` ### `evolved.multi_spawn` ```lua ---@param entity_count integer ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.multi_spawn(entity_count, components) end +function evolved.multi_spawn(entity_count, component_table, component_mapper) end ``` ### `evolved.clone` ```lua ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.clone(prefab, components) end +function evolved.clone(prefab, component_table, component_mapper) end ``` ### `evolved.multi_clone` @@ -1567,10 +1783,11 @@ function evolved.clone(prefab, components) end ```lua ---@param entity_count integer ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.multi_clone(entity_count, prefab, components) end +function evolved.multi_clone(entity_count, prefab, component_table, component_mapper) end ``` ### `evolved.alive` @@ -1887,8 +2104,9 @@ function evolved.builder() end ```lua ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:build(prefab) end +function evolved.builder_mt:build(prefab, component_mapper) end ``` ### `evolved.builder_mt:multi_build` @@ -1896,33 +2114,37 @@ function evolved.builder_mt:build(prefab) end ```lua ---@param entity_count integer ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_build(entity_count, prefab) end +function evolved.builder_mt:multi_build(entity_count, prefab, component_mapper) end ``` #### `evolved.builder_mt:spawn` ```lua +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:spawn() end +function evolved.builder_mt:spawn(component_mapper) end ``` #### `evolved.builder_mt:multi_spawn` ```lua ---@param entity_count integer +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_spawn(entity_count) end +function evolved.builder_mt:multi_spawn(entity_count, component_mapper) end ``` #### `evolved.builder_mt:clone` ```lua ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function evolved.builder_mt:clone(prefab) end +function evolved.builder_mt:clone(prefab, component_mapper) end ``` #### `evolved.builder_mt:multi_clone` @@ -1930,9 +2152,10 @@ function evolved.builder_mt:clone(prefab) end ```lua ---@param entity_count integer ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function evolved.builder_mt:multi_clone(entity_count, prefab) end +function evolved.builder_mt:multi_clone(entity_count, prefab, component_mapper) end ``` #### `evolved.builder_mt:has` @@ -2047,6 +2270,22 @@ function evolved.builder_mt:default(default) end function evolved.builder_mt:duplicate(duplicate) end ``` +#### `evolved.builder_mt:realloc` + +```lua +---@param realloc evolved.realloc +---@return evolved.builder builder +function evolved.builder_mt:realloc(realloc) end +``` + +#### `evolved.builder_mt:compmove` + +```lua +---@param compmove evolved.compmove +---@return evolved.builder builder +function evolved.builder_mt:compmove(compmove) end +``` + #### `evolved.builder_mt:prefab` ```lua diff --git a/develop/ROADMAP.md b/develop/ROADMAP.md index 75f88c5..5b53895 100644 --- a/develop/ROADMAP.md +++ b/develop/ROADMAP.md @@ -10,7 +10,13 @@ ## Thoughts - We should have a way to not copy components on deferred spawn/clone +- Not all assoc_list_remove operations need to keep order, we can have an unordered variant also +- We still have several places where we use __lua_next without deterministic order, we should fix that +- Having a light version of the gargabe collector can be useful for some use-cases +- We can shrink the table pool tables on garbage collection if they are too large +- Should we sort chunk children by fragment id? +- Basic default component value as true looks awful, should we use something else? ## Known Issues -- Errors in hooks are cannot be handled properly right now +- Errors in hooks or rellocs/compmoves/mappers are cannot be handled properly right now diff --git a/develop/all.lua b/develop/all.lua index ca2350e..7e01a14 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -5,9 +5,11 @@ require 'develop.testing.depth_tests' require 'develop.testing.destroy_tests' require 'develop.testing.locate_tests' require 'develop.testing.main_tests' +require 'develop.testing.mappers_tests' require 'develop.testing.multi_spawn_tests' require 'develop.testing.name_tests' require 'develop.testing.process_with_tests' +require 'develop.testing.realloc_tests' require 'develop.testing.requires_fragment_tests' require 'develop.testing.spawn_tests' require 'develop.testing.system_as_query_tests' @@ -35,3 +37,5 @@ print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.requires_fuzz' print '----------------------------------------' basics.describe_fuzz 'develop.fuzzing.unique_fuzz' + +print 'All tests passed.' diff --git a/develop/testing/mappers_tests.lua b/develop/testing/mappers_tests.lua new file mode 100644 index 0000000..1e84950 --- /dev/null +++ b/develop/testing/mappers_tests.lua @@ -0,0 +1,242 @@ +local evo = require 'evolved' + +do + local f1, f2 = evo.id(2) + + local e1 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + local e2 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 11) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 22) + + local e11 = evo.clone(e1, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 3 + f2s[p] = f2s[p] * 4 + end) + + local e22 = evo.clone(e2, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 30 + f2s[p] = f2s[p] + 40 + end) + + assert(evo.has(e11, f1) and evo.get(e11, f1) == 6) + assert(evo.has(e11, f2) and evo.get(e11, f2) == 24) + assert(evo.has(e22, f1) and evo.get(e22, f1) == 41) + assert(evo.has(e22, f2) and evo.get(e22, f2) == 62) +end + +do + local f1, f2 = evo.id(2) + + evo.defer() + + local e1 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + local e2 = evo.spawn({ [f1] = 1, [f2] = 2 }, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(not evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(not evo.has(e1, f2) and evo.get(e1, f2) == nil) + assert(not evo.has(e2, f1) and evo.get(e2, f1) == nil) + assert(not evo.has(e2, f2) and evo.get(e2, f2) == nil) + + evo.commit() + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 11) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 22) + + evo.defer() + + local e11 = evo.clone(e1, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 3 + f2s[p] = f2s[p] * 4 + end) + + local e22 = evo.clone(e2, nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 30 + f2s[p] = f2s[p] + 40 + end) + + assert(not evo.has(e11, f1) and evo.get(e11, f1) == nil) + assert(not evo.has(e11, f2) and evo.get(e11, f2) == nil) + assert(not evo.has(e22, f1) and evo.get(e22, f1) == nil) + assert(not evo.has(e22, f2) and evo.get(e22, f2) == nil) + + evo.commit() + + assert(evo.has(e11, f1) and evo.get(e11, f1) == 6) + assert(evo.has(e11, f2) and evo.get(e11, f2) == 24) + assert(evo.has(e22, f1) and evo.get(e22, f1) == 41) + assert(evo.has(e22, f2) and evo.get(e22, f2) == 62) +end + +do + local f1, f2 = evo.id(2) + + local es, ec = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + local es2, ec2 = evo.multi_clone(10, es[1], nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end + +do + local f1, f2 = evo.id(2) + + evo.defer() + + local es, ec = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + end + + evo.commit() + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + evo.defer() + + local es2, ec2 = evo.multi_clone(10, es[1], nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(not evo.has(e, f1) and evo.get(e, f1) == nil) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + end + + evo.commit() + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end + +do + local f1, f2 = evo.id(2) + + local e1 = evo.builder():set(f1, 1):set(f2, 2):build(nil, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end) + + assert(evo.has(e1, f1) and evo.get(e1, f1) == 2) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 6) + + local e2 = evo.builder():build(e1, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e2, f1) and evo.get(e2, f1) == 12) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 26) + + local e3 = evo.builder():set(f2, 3):build(e1, function(c, p) + local f1s, f2s = c:components(f1, f2) + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end) + + assert(evo.has(e3, f1) and evo.get(e3, f1) == 12) + assert(evo.has(e3, f2) and evo.get(e3, f2) == 23) +end + +do + local f1, f2 = evo.id(2) + + local es, ec = evo.builder():set(f1, 1):set(f2, 2):multi_build(10, nil, function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] * 2 + f2s[p] = f2s[p] * 3 + end + end) + + for i = 1, ec do + local e = es[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 2) + assert(evo.has(e, f2) and evo.get(e, f2) == 6) + end + + local es2, ec2 = evo.builder():multi_build(10, es[1], function(c, b, e) + local f1s, f2s = c:components(f1, f2) + for p = b, e do + f1s[p] = f1s[p] + 10 + f2s[p] = f2s[p] + 20 + end + end) + + for i = 1, ec2 do + local e = es2[i] + assert(evo.has(e, f1) and evo.get(e, f1) == 12) + assert(evo.has(e, f2) and evo.get(e, f2) == 26) + end +end diff --git a/develop/testing/realloc_tests.lua b/develop/testing/realloc_tests.lua new file mode 100644 index 0000000..2d81905 --- /dev/null +++ b/develop/testing/realloc_tests.lua @@ -0,0 +1,588 @@ +local evo = require 'evolved' + +---@type ffilib? +local ffi = (function() + local ffi_loader = package and package.preload and package.preload['ffi'] + local ffi = ffi_loader and ffi_loader() + return ffi +end)() + +if not ffi then + return +end + +local FLOAT_TYPEOF = ffi.typeof('float') +local FLOAT_SIZEOF = ffi.sizeof(FLOAT_TYPEOF) +local FLOAT_STORAGE_TYPEOF = ffi.typeof('$[?]', FLOAT_TYPEOF) + +local DOUBLE_TYPEOF = ffi.typeof('double') +local DOUBLE_SIZEOF = ffi.sizeof(DOUBLE_TYPEOF) +local DOUBLE_STORAGE_TYPEOF = ffi.typeof('$[?]', DOUBLE_TYPEOF) + +local STORAGE_SIZES = {} + +---@type evolved.realloc +local function float_realloc(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + STORAGE_SIZES[src] = nil + return + else + if src then + assert(src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + else + assert(src_size == 0) + end + + local dst = ffi.new(FLOAT_STORAGE_TYPEOF, dst_size + 1) + STORAGE_SIZES[dst] = dst_size + + if src then + ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * FLOAT_SIZEOF) + end + + return dst + end +end + +---@type evolved.realloc +local function double_realloc(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + STORAGE_SIZES[src] = nil + return + else + if src then + assert(src_size > 0) + local expected_src_size = STORAGE_SIZES[src] + assert(expected_src_size == src_size) + else + assert(src_size == 0) + end + + local dst = ffi.new(DOUBLE_STORAGE_TYPEOF, dst_size + 1) + STORAGE_SIZES[dst] = dst_size + + if src then + ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * DOUBLE_SIZEOF) + end + + return dst + end +end + +---@type evolved.compmove +local function double_compmove(src, f, e, t, dst) + ffi.copy(dst + t, src + f, (e - f + 1) * DOUBLE_SIZEOF) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + + local e1 = evo.builder():set(f1, 21):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + + local e2 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + + local e3 = evo.builder():set(f1, 84):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 84) + + evo.destroy(e1) + assert(not evo.has(e1, f1)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(evo.has(e3, f1) and evo.get(e3, f1) == 84) + + evo.destroy(e3) + assert(not evo.has(e1, f1)) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(not evo.has(e3, f1)) + + evo.destroy(e2) + assert(not evo.has(e1, f1)) + assert(not evo.has(e2, f1)) + assert(not evo.has(e3, f1)) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + local q1 = evo.builder():include(f1):build() + + do + local es, ec = {}, 10 + for i = 1, ec do es[i] = evo.spawn({ [f1] = i }) end + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == i) end + end + + do + local p = evo.builder():set(f1, 42):build() + local es, ec = {}, 10 + for i = 1, ec do es[i] = evo.clone(p) end + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) end + end + + do + local es1, ec1 = evo.multi_spawn(10, { [f1] = 42 }) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 42) end + + local es2, ec2 = evo.multi_spawn(20, { [f1] = 84 }) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 42) end + for i = 1, ec2 do assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 84) end + end + + do + local p = evo.builder():set(f1, 21):build() + + local es1, ec1 = evo.multi_clone(10, p) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 21) end + + local es2, ec2 = evo.multi_clone(20, p) + for i = 1, ec1 do assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 21) end + for i = 1, ec2 do assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 21) end + end + + evo.batch_destroy(q1) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + local f2 = evo.builder():realloc(double_realloc):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local e = evo.builder():set(f1, 21):set(f2, 42):build() + assert(evo.has(e, f1) and evo.get(e, f1) == 21) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + + evo.remove(e, f1) + + assert(not evo.has(e, f1)) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + end + + do + local e = evo.builder():set(f1, 21):set(f2, 42):build() + assert(evo.has(e, f1) and evo.get(e, f1) == 21) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + + evo.clear(e) + + assert(not evo.has(e, f1)) + assert(not evo.has(e, f2)) + end + + do + local es, ec = evo.multi_spawn(10, { [f1] = 21, [f2] = 42 }) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_remove(q1, f1) + + local e12 = evo.builder():set(f1, 1):set(f2, 2):build() + assert(evo.has(e12, f1) and evo.get(e12, f1) == 1) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_set(q2, f1, 84) + + assert(evo.has(e12, f1) and evo.get(e12, f1) == 84) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 84) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + + evo.batch_set(q2, f1, 21) + + assert(evo.has(e12, f1) and evo.get(e12, f1) == 21) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 2) + + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 42) + end + end +end + +do + local f1 = evo.builder():realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es1, ec1 = evo.multi_spawn(10, { [f1] = 1, [f2] = 2 }) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 1) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + local es2, ec2 = evo.multi_spawn(20, { [f1] = 3, [f2] = 4 }) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 1) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 3) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + + local e2 = evo.builder():set(f2, 42):build() + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + + evo.batch_remove(q1, f1) + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + + for i = 1, ec1 do + assert(not evo.has(es1[i], f1)) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(not evo.has(es2[i], f1)) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + + local e12 = evo.builder():set(f1, 21):set(f2, 42):build() + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + assert(evo.has(e12, f1) and evo.get(e12, f1) == 21) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 42) + + evo.batch_set(q2, f1, 84) + + assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) + assert(evo.has(e12, f1) and evo.get(e12, f1) == 84) + assert(evo.has(e12, f2) and evo.get(e12, f2) == 42) + + for i = 1, ec1 do + assert(evo.has(es1[i], f1) and evo.get(es1[i], f1) == 84) + assert(evo.has(es1[i], f2) and evo.get(es1[i], f2) == 2) + end + + for i = 1, ec2 do + assert(evo.has(es2[i], f1) and evo.get(es2[i], f1) == 84) + assert(evo.has(es2[i], f2) and evo.get(es2[i], f2) == 4) + end + end +end + +do + local f1 = evo.builder():default(42):build() + + local es, ec = evo.multi_spawn(10, { [f1] = 21 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 21) end + + evo.set(f1, evo.TAG) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + + evo.remove(f1, evo.TAG) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) end +end + +do + local f1 = evo.builder():realloc(float_realloc):build() + + local e1 = evo.builder():set(f1, 3):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.set(f1, evo.REALLOC, double_realloc) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.remove(f1, evo.REALLOC) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) + + evo.set(f1, evo.REALLOC, double_realloc) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 3) +end + +do + local f1 = evo.builder():realloc(double_realloc):build() + + local es, ec = evo.multi_spawn(20, { [f1] = 42 }) + + for i = 1, ec / 2 do + evo.destroy(es[ec - i + 1]) + end + + evo.collect_garbage() + + for i = 1, ec / 2 do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) + end +end + +do + evo.collect_garbage() + + local f1 = evo.builder():name('f1'):realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():name('f2'):realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es, ec = evo.multi_spawn(40, { [f2] = 2 }) + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + evo.batch_destroy(q2) + end + + do + local es, ec = evo.multi_spawn(50, { [f1] = 1, [f2] = 2 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_remove(q1, f1) + for i = 1, ec do + assert(not evo.has(es[i], f1)) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_destroy(q1, q2) + end + + do + evo.spawn({ [f1] = 1 }) + evo.spawn({ [f2] = 2 }) + evo.spawn({ [f1] = 1, [f2] = 2 }) + end + + evo.collect_garbage() +end + +do + evo.collect_garbage() + + local f1 = evo.builder():name('f1'):realloc(double_realloc):compmove(double_compmove):build() + local f2 = evo.builder():name('f2'):realloc(double_realloc):compmove(double_compmove):build() + + local q1 = evo.builder():include(f1):build() + local q2 = evo.builder():include(f2):build() + + do + local es, ec = evo.multi_spawn(40, { [f1] = 1, [f2] = 2 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + evo.batch_destroy(q2) + end + + do + local es, ec = evo.multi_spawn(50, { [f1] = 1 }) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(not evo.has(es[i], f2)) + end + + evo.batch_set(q1, f2, 2) + for i = 1, ec do + assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 1) + assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 2) + end + + evo.batch_destroy(q1, q2) + end + + do + evo.spawn({ [f1] = 1 }) + evo.spawn({ [f2] = 2 }) + evo.spawn({ [f1] = 1, [f2] = 2 }) + end + + evo.collect_garbage() +end + +do + evo.collect_garbage() + + local alloc_call_count = 0 + local free_call_count = 0 + local resize_call_count = 0 + + local function ctor_realloc() + ---@type evolved.realloc + return function(src, src_size, dst_size) + if dst_size == 0 then + assert(src and src_size > 0) + free_call_count = free_call_count + 1 + return + else + if src then + assert(src_size > 0) + resize_call_count = resize_call_count + 1 + else + assert(src_size == 0) + alloc_call_count = alloc_call_count + 1 + end + + local dst = {} + + if src then + for i = 1, math.min(src_size, dst_size) do + dst[i] = src[i] + end + end + + return dst + end + end + end + + do + local realloc1 = ctor_realloc() + local realloc2 = ctor_realloc() + + local f1 = evo.builder():default(44):realloc(realloc1):build() + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 21):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(alloc_call_count == 1 and free_call_count == 0) + + local e2 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 21) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 42) + assert(alloc_call_count == 1 and free_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e1) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e2) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local es, ec = evo.multi_spawn(10, { [f1] = 84 }) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + for i = 1, ec / 2 do evo.destroy(es[i]) end + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 1) + + evo.set(f1, evo.REALLOC, realloc2) + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 1) + + for i = 1, ec do evo.destroy(es[i]) end + evo.collect_garbage() + assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 1) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 24):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 24) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + local es, ec = evo.multi_spawn(20, { [f1] = 48 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.remove(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 44) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 44) end + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0) + + evo.destroy(e1) + for i = 1, ec do evo.destroy(es[i]) end + assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 0) + end + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 100):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 100) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + local es, ec = evo.multi_spawn(20, { [f1] = 48 }) + for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.destroy(e1) + for i = 1, ec do evo.destroy(es[i]) end + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + + evo.collect_garbage() + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + end + + + do + local realloc = ctor_realloc() + + local f1 = evo.builder():realloc(realloc):build() + + alloc_call_count, free_call_count, resize_call_count = 0, 0, 0 + + do + local e1 = evo.builder():set(f1, 42):build() + assert(evo.has(e1, f1) and evo.get(e1, f1) == 42) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.destroy(e1) + assert(not evo.has(e1, f1) and evo.get(e1, f1) == nil) + assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0) + + evo.set(f1, evo.TAG) + assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0) + end + end +end diff --git a/evolved.d.tl b/evolved.d.tl index 8e390b0..54d3ac4 100644 --- a/evolved.d.tl +++ b/evolved.d.tl @@ -31,14 +31,30 @@ end interface Builder - build: function(self: Builder, prefab?: Entity): Entity - multi_build: function(self: Builder, entity_count: integer, prefab?: Entity): { Entity }, integer + build: function(self: Builder, + prefab?: Entity, + component_mapper?: function(Chunk, integer)): Entity - spawn: function(self: Builder): Entity - multi_spawn: function(self: Builder, entity_count: integer): { Entity }, integer + multi_build: function(self: Builder, + entity_count: integer, + prefab?: Entity, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer - clone: function(self: Builder, prefab: Entity): Entity - multi_clone: function(self: Builder, entity_count: integer, prefab: Entity): { Entity }, integer + spawn: function(self: Builder, + component_mapper?: function(Chunk, integer)): Entity + + multi_spawn: function(self: Builder, + entity_count: integer, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer + + clone: function(self: Builder, + prefab: Entity, + component_mapper?: function(Chunk, integer)): Entity + + multi_clone: function(self: Builder, + entity_count: integer, + prefab: Entity, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer has: function(self: Builder, fragment: Fragment): boolean has_all: function(self: Builder, ...: Fragment): boolean @@ -64,6 +80,9 @@ default: function(self: Builder, default: Component): Builder duplicate: function(self: Builder, duplicate: function(Component): Component): Builder + realloc: function(self: Builder, realloc: function({ Component } | nil, integer, integer): { Component } | nil): Builder + compmove: function(self: Builder, compmove: function({ Component }, integer, integer, integer, { Component })): Builder + prefab: function(self: Builder): Builder disabled: function(self: Builder): Builder @@ -98,6 +117,9 @@ DEFAULT: Fragment DUPLICATE: Fragment + REALLOC: Fragment + COMPMOVE: Fragment + PREFAB: Fragment DISABLED: Fragment @@ -133,11 +155,25 @@ commit: function(): boolean cancel: function(): boolean - spawn: function(components?: { Fragment: any }): Entity - multi_spawn: function(entity_count: integer, components?: { Fragment: any }): { Entity }, integer - - clone: function(prefab: Entity, components?: { Fragment: any }): Entity - multi_clone: function(entity_count: integer, prefab: Entity, components?: { Fragment: any }): { Entity }, integer + spawn: function( + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer)): Entity + + multi_spawn: function( + entity_count: integer, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer + + clone: function( + prefab: Entity, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer)): Entity + + multi_clone: function( + entity_count: integer, + prefab: Entity, + component_table?: { Fragment: any }, + component_mapper?: function(Chunk, integer, integer)): { Entity }, integer alive: function(entity: Entity): boolean alive_all: function(...: Entity): boolean diff --git a/evolved.lua b/evolved.lua index 2e90401..6562d7f 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.7.0', + __VERSION = '1.8.0', __LICENSE = [[ MIT License @@ -37,9 +37,15 @@ local evolved = { ---@alias evolved.component any ---@alias evolved.storage evolved.component[] +---@alias evolved.component_table table +---@alias evolved.component_mapper fun(chunk: evolved.chunk, b_place: integer, e_place: integer) + ---@alias evolved.default evolved.component ---@alias evolved.duplicate fun(component: evolved.component): evolved.component +---@alias evolved.realloc fun(src?: evolved.storage, src_size: integer, dst_size: integer): evolved.storage? +---@alias evolved.compmove fun(src: evolved.storage, f: integer, e: integer, t: integer, dst: evolved.storage) + ---@alias evolved.execute fun( --- chunk: evolved.chunk, --- entity_list: evolved.entity[], @@ -158,6 +164,7 @@ local __structural_changes = 0 ---@type integer ---@field package __child_count integer ---@field package __entity_list evolved.entity[] ---@field package __entity_count integer +---@field package __entity_capacity integer ---@field package __fragment evolved.fragment ---@field package __fragment_set table ---@field package __fragment_list evolved.fragment[] @@ -168,6 +175,8 @@ local __structural_changes = 0 ---@type integer ---@field package __component_fragments evolved.fragment[] ---@field package __component_defaults evolved.default[] ---@field package __component_duplicates evolved.duplicate[] +---@field package __component_reallocs evolved.realloc[] +---@field package __component_compmoves evolved.compmove[] ---@field package __with_fragment_edges table ---@field package __without_fragment_edges table ---@field package __with_required_fragments? evolved.chunk @@ -192,7 +201,7 @@ __chunk_mt.__index = __chunk_mt ---@class evolved.builder ---@field package __chunk? evolved.chunk ----@field package __components table +---@field package __component_table evolved.component_table local __builder_mt = {} __builder_mt.__index = __builder_mt @@ -676,13 +685,11 @@ local __table_pool_tag = { system_list = 3, each_state = 4, execute_state = 5, - entity_set = 6, - entity_list = 7, - fragment_set = 8, - fragment_list = 9, - component_map = 10, - component_list = 11, - __count = 11, + entity_list = 6, + fragment_set = 7, + fragment_list = 8, + component_table = 9, + __count = 9, } ---@class (exact) evolved.table_pool @@ -971,6 +978,9 @@ local __INTERNAL = __acquire_id() local __DEFAULT = __acquire_id() local __DUPLICATE = __acquire_id() +local __REALLOC = __acquire_id() +local __COMPMOVE = __acquire_id() + local __PREFAB = __acquire_id() local __DISABLED = __acquire_id() @@ -1008,21 +1018,6 @@ local __safe_tbls = { __newindex = function() __error_fmt 'attempt to modify empty fragment set' end }) --[[@as table]], - __EMPTY_FRAGMENT_LIST = __lua_setmetatable({}, { - __tostring = function() return 'empty fragment list' end, - __newindex = function() __error_fmt 'attempt to modify empty fragment list' end - }) --[=[@as evolved.fragment[]]=], - - __EMPTY_COMPONENT_MAP = __lua_setmetatable({}, { - __tostring = function() return 'empty component map' end, - __newindex = function() __error_fmt 'attempt to modify empty component map' end - }) --[[@as table]], - - __EMPTY_COMPONENT_LIST = __lua_setmetatable({}, { - __tostring = function() return 'empty component list' end, - __newindex = function() __error_fmt 'attempt to modify empty component list' end - }) --[=[@as evolved.component[]]=], - __EMPTY_COMPONENT_STORAGE = __lua_setmetatable({}, { __tostring = function() return 'empty component storage' end, __newindex = function() __error_fmt 'attempt to modify empty component storage' end @@ -1100,6 +1095,9 @@ local __id_name local __new_chunk +local __default_realloc +local __default_compmove + local __update_chunk_caches local __update_chunk_queries local __update_chunk_storages @@ -1144,6 +1142,8 @@ local __clone_entity local __multi_clone_entity local __purge_chunk +local __expand_chunk +local __shrink_chunk local __clear_chunk_list local __destroy_entity_list local __destroy_fragment_list @@ -1170,8 +1170,6 @@ local __defer_multi_clone_entity ---@return string ---@nodiscard function __id_name(id) - local id_primary, id_secondary = __evolved_unpack(id) - ---@type string? local id_name = __evolved_get(id, __NAME) @@ -1179,6 +1177,7 @@ function __id_name(id) return id_name end + local id_primary, id_secondary = __evolved_unpack(id) return __lua_string_format('$%d#%d:%d', id, id_primary, id_secondary) end @@ -1221,6 +1220,7 @@ function __new_chunk(chunk_parent, chunk_fragment) __child_count = 0, __entity_list = {}, __entity_count = 0, + __entity_capacity = 0, __fragment = chunk_fragment, __fragment_set = chunk_fragment_set, __fragment_list = chunk_fragment_list, @@ -1231,6 +1231,8 @@ function __new_chunk(chunk_parent, chunk_fragment) __component_fragments = {}, __component_defaults = {}, __component_duplicates = {}, + __component_reallocs = {}, + __component_compmoves = {}, __with_fragment_edges = {}, __without_fragment_edges = {}, __with_required_fragments = nil, @@ -1304,6 +1306,37 @@ function __new_chunk(chunk_parent, chunk_fragment) return chunk end +---@param src? evolved.storage +---@param src_size integer +---@param dst_size integer +---@return evolved.storage? +function __default_realloc(src, src_size, dst_size) + if dst_size == 0 then + return + end + + if src and dst_size >= src_size then + return src + end + + local dst = __lua_table_new(dst_size) + + if src then + __lua_table_move(src, 1, dst_size, 1, dst) + end + + return dst +end + +---@param src evolved.storage +---@param f integer +---@param e integer +---@param t integer +---@param dst evolved.storage +function __default_compmove(src, f, e, t, dst) + __lua_table_move(src, f, e, t, dst) +end + ---@param chunk evolved.chunk function __update_chunk_caches(chunk) local chunk_parent = chunk.__parent @@ -1415,6 +1448,7 @@ end ---@param chunk evolved.chunk function __update_chunk_storages(chunk) local entity_count = chunk.__entity_count + local entity_capacity = chunk.__entity_capacity local fragment_list = chunk.__fragment_list local fragment_count = chunk.__fragment_count @@ -1425,29 +1459,49 @@ function __update_chunk_storages(chunk) local component_fragments = chunk.__component_fragments local component_defaults = chunk.__component_defaults local component_duplicates = chunk.__component_duplicates + local component_reallocs = chunk.__component_reallocs + local component_compmoves = chunk.__component_compmoves for fragment_index = 1, fragment_count do local fragment = fragment_list[fragment_index] + local component_index = component_indices[fragment] + local component_realloc = component_index and component_reallocs[component_index] - ---@type evolved.default?, evolved.duplicate? - local fragment_default, fragment_duplicate = - __evolved_get(fragment, __DEFAULT, __DUPLICATE) + ---@type evolved.default?, evolved.duplicate?, evolved.realloc?, evolved.compmove? + local fragment_default, fragment_duplicate, fragment_realloc, fragment_compmove = + __evolved_get(fragment, __DEFAULT, __DUPLICATE, __REALLOC, __COMPMOVE) local is_fragment_tag = __evolved_has(fragment, __TAG) if component_index and is_fragment_tag then + if entity_capacity > 0 then + local component_storage = component_storages[component_index] + + if component_realloc then + component_realloc(component_storage, entity_capacity, 0) + else + __default_realloc(component_storage, entity_capacity, 0) + end + + component_storages[component_index] = nil + end + if component_index ~= component_count then local last_component_storage = component_storages[component_count] local last_component_fragment = component_fragments[component_count] local last_component_default = component_defaults[component_count] local last_component_duplicate = component_duplicates[component_count] + local last_component_realloc = component_reallocs[component_count] + local last_component_compmove = component_compmoves[component_count] component_indices[last_component_fragment] = component_index component_storages[component_index] = last_component_storage component_fragments[component_index] = last_component_fragment component_defaults[component_index] = last_component_default component_duplicates[component_index] = last_component_duplicate + component_reallocs[component_index] = last_component_realloc + component_compmoves[component_index] = last_component_compmove end component_indices[fragment] = nil @@ -1455,6 +1509,8 @@ function __update_chunk_storages(chunk) component_fragments[component_count] = nil component_defaults[component_count] = nil component_duplicates[component_count] = nil + component_reallocs[component_count] = nil + component_compmoves[component_count] = nil component_count = component_count - 1 chunk.__component_count = component_count @@ -1462,32 +1518,93 @@ function __update_chunk_storages(chunk) component_count = component_count + 1 chunk.__component_count = component_count - local component_storage = __lua_table_new(entity_count) - local component_storage_index = component_count + component_index = component_count + + component_indices[fragment] = component_index + component_storages[component_index] = nil + component_fragments[component_index] = fragment + component_defaults[component_index] = fragment_default + component_duplicates[component_index] = fragment_duplicate + component_reallocs[component_index] = fragment_realloc + component_compmoves[component_index] = fragment_compmove + + if entity_capacity > 0 then + local new_component_storage ---@type evolved.storage? - component_indices[fragment] = component_storage_index - component_storages[component_storage_index] = component_storage - component_fragments[component_storage_index] = fragment - component_defaults[component_storage_index] = fragment_default - component_duplicates[component_storage_index] = fragment_duplicate + if fragment_realloc then + new_component_storage = fragment_realloc(nil, 0, entity_capacity) + else + new_component_storage = __default_realloc(nil, 0, entity_capacity) + end + + if not new_component_storage then + __error_fmt('component storage allocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(fragment)) + end - if fragment_duplicate then - for place = 1, entity_count do + if fragment_duplicate then + for place = 1, entity_count do + local new_component = fragment_default + if new_component ~= nil then new_component = fragment_duplicate(new_component) end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + else local new_component = fragment_default - if new_component ~= nil then new_component = fragment_duplicate(new_component) end if new_component == nil then new_component = true end - component_storage[place] = new_component - end - else - local new_component = fragment_default - if new_component == nil then new_component = true end - for place = 1, entity_count do - component_storage[place] = new_component + for place = 1, entity_count do + new_component_storage[place] = new_component + end end + + component_storages[component_index] = new_component_storage end elseif component_index then + if fragment_realloc ~= component_realloc and entity_capacity > 0 then + local new_component_storage ---@type evolved.storage? + local old_component_storage = component_storages[component_index] + + if fragment_realloc then + new_component_storage = fragment_realloc(nil, 0, entity_capacity) + else + new_component_storage = __default_realloc(nil, 0, entity_capacity) + end + + if not new_component_storage then + __error_fmt('component storage allocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(fragment)) + end + + if fragment_duplicate then + for place = 1, entity_count do + local new_component = old_component_storage[place] + if new_component == nil then new_component = fragment_default end + if new_component ~= nil then new_component = fragment_duplicate(new_component) end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + else + for place = 1, entity_count do + local new_component = old_component_storage[place] + if new_component == nil then new_component = fragment_default end + if new_component == nil then new_component = true end + new_component_storage[place] = new_component + end + end + + if component_realloc then + component_realloc(old_component_storage, entity_capacity, 0) + else + __default_realloc(old_component_storage, entity_capacity, 0) + end + + component_storages[component_index] = new_component_storage + end + component_defaults[component_index] = fragment_default component_duplicates[component_index] = fragment_duplicate + component_reallocs[component_index] = fragment_realloc + component_compmoves[component_index] = fragment_compmove end end end @@ -1775,7 +1892,7 @@ function __chunk_with_fragment(chunk, fragment) end ---@param chunk? evolved.chunk ----@param components table +---@param components evolved.component_table ---@return evolved.chunk? ---@nodiscard function __chunk_with_components(chunk, components) @@ -1932,7 +2049,7 @@ function __chunk_fragments(head_fragment, ...) return chunk end ----@param components table +---@param components evolved.component_table ---@return evolved.chunk? ---@nodiscard function __chunk_components(components) @@ -2159,27 +2276,16 @@ function __detach_entity(chunk, place) local component_count = chunk.__component_count local component_storages = chunk.__component_storages - if place == entity_count then - entity_list[entity_count] = nil - - for component_index = 1, component_count do - local component_storage = component_storages[component_index] - component_storage[entity_count] = nil - end - else + if place ~= entity_count then local last_entity = entity_list[entity_count] - local last_entity_primary = last_entity % 2 ^ 20 + entity_list[place] = last_entity + local last_entity_primary = last_entity % 2 ^ 20 __entity_places[last_entity_primary] = place - entity_list[place] = last_entity - entity_list[entity_count] = nil - for component_index = 1, component_count do local component_storage = component_storages[component_index] - local last_entity_component = component_storage[entity_count] - component_storage[place] = last_entity_component - component_storage[entity_count] = nil + component_storage[place] = component_storage[entity_count] end end @@ -2188,31 +2294,20 @@ end ---@param chunk evolved.chunk function __detach_all_entities(chunk) - local entity_list = chunk.__entity_list - - local component_count = chunk.__component_count - local component_storages = chunk.__component_storages - - __lua_table_clear(entity_list) - - for component_index = 1, component_count do - local component_storage = component_storages[component_index] - __lua_table_clear(component_storage) - end - chunk.__entity_count = 0 end ---@param chunk? evolved.chunk ---@param entity evolved.entity ----@param components table -function __spawn_entity(chunk, entity, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __spawn_entity(chunk, entity, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('spawn entity operations should be deferred') end if not chunk or chunk.__unreachable_or_collected then - chunk = __chunk_components(components) + chunk = component_table and __chunk_components(component_table) end while chunk and chunk.__has_required_fragments do @@ -2230,9 +2325,6 @@ function __spawn_entity(chunk, entity, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages @@ -2240,7 +2332,13 @@ function __spawn_entity(chunk, entity, components) local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local place = chunk_entity_count + 1 + local place = chunk.__entity_count + 1 + + if place > chunk.__entity_capacity then + __expand_chunk(chunk, place) + end + + local chunk_entity_list = chunk.__entity_list do chunk.__entity_count = place @@ -2258,7 +2356,7 @@ function __spawn_entity(chunk, entity, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then ini_component = chunk_component_defaults[component_index] @@ -2281,6 +2379,10 @@ function __spawn_entity(chunk, entity, components) end end + if component_mapper then + component_mapper(chunk, place, place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2324,14 +2426,15 @@ end ---@param chunk? evolved.chunk ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param components table -function __multi_spawn_entity(chunk, entity_list, entity_count, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('spawn entity operations should be deferred') end if not chunk or chunk.__unreachable_or_collected then - chunk = __chunk_components(components) + chunk = component_table and __chunk_components(component_table) end while chunk and chunk.__has_required_fragments do @@ -2349,9 +2452,6 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages @@ -2359,8 +2459,14 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local chunk_component_defaults = chunk.__component_defaults local chunk_component_duplicates = chunk.__component_duplicates - local b_place = chunk_entity_count + 1 - local e_place = chunk_entity_count + entity_count + local b_place = chunk.__entity_count + 1 + local e_place = b_place + entity_count - 1 + + if e_place > chunk.__entity_capacity then + __expand_chunk(chunk, e_place) + end + + local chunk_entity_list = chunk.__entity_list do chunk.__entity_count = e_place @@ -2384,7 +2490,7 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then ini_component = chunk_component_defaults[component_index] @@ -2411,6 +2517,10 @@ function __multi_spawn_entity(chunk, entity_list, entity_count, components) end end + if component_mapper then + component_mapper(chunk, b_place, e_place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2461,8 +2571,9 @@ end ---@param prefab evolved.entity ---@param entity evolved.entity ----@param components table -function __clone_entity(prefab, entity, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __clone_entity(prefab, entity, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('clone entity operations should be deferred') end @@ -2478,12 +2589,12 @@ function __clone_entity(prefab, entity, components) end if not prefab_chunk or not prefab_chunk.__without_unique_fragments then - return __spawn_entity(nil, entity, components) + return __spawn_entity(nil, entity, component_table, component_mapper) end - local chunk = __chunk_with_components( - prefab_chunk.__without_unique_fragments, - components) + local chunk = component_table + and __chunk_with_components(prefab_chunk.__without_unique_fragments, component_table) + or prefab_chunk.__without_unique_fragments while chunk and chunk.__has_required_fragments do local required_chunk = chunk.__with_required_fragments @@ -2500,9 +2611,6 @@ function __clone_entity(prefab, entity, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages @@ -2513,7 +2621,13 @@ function __clone_entity(prefab, entity, components) local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages - local place = chunk_entity_count + 1 + local place = chunk.__entity_count + 1 + + if place > chunk.__entity_capacity then + __expand_chunk(chunk, place) + end + + local chunk_entity_list = chunk.__entity_list do chunk.__entity_count = place @@ -2531,7 +2645,7 @@ function __clone_entity(prefab, entity, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then if chunk == prefab_chunk then @@ -2564,6 +2678,10 @@ function __clone_entity(prefab, entity, components) end end + if component_mapper then + component_mapper(chunk, place, place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2607,8 +2725,9 @@ end ---@param prefab evolved.entity ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param components table -function __multi_clone_entity(prefab, entity_list, entity_count, components) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) if __defer_depth <= 0 then __error_fmt('clone entity operations should be deferred') end @@ -2624,12 +2743,12 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) end if not prefab_chunk or not prefab_chunk.__without_unique_fragments then - return __multi_spawn_entity(nil, entity_list, entity_count, components) + return __multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) end - local chunk = __chunk_with_components( - prefab_chunk.__without_unique_fragments, - components) + local chunk = component_table + and __chunk_with_components(prefab_chunk.__without_unique_fragments, component_table) + or prefab_chunk.__without_unique_fragments while chunk and chunk.__has_required_fragments do local required_chunk = chunk.__with_required_fragments @@ -2646,9 +2765,6 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) return end - local chunk_entity_list = chunk.__entity_list - local chunk_entity_count = chunk.__entity_count - local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages @@ -2659,8 +2775,14 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local prefab_component_indices = prefab_chunk.__component_indices local prefab_component_storages = prefab_chunk.__component_storages - local b_place = chunk_entity_count + 1 - local e_place = chunk_entity_count + entity_count + local b_place = chunk.__entity_count + 1 + local e_place = b_place + entity_count - 1 + + if e_place > chunk.__entity_capacity then + __expand_chunk(chunk, e_place) + end + + local chunk_entity_list = chunk.__entity_list do chunk.__entity_count = e_place @@ -2684,7 +2806,7 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) local component_fragment = chunk_component_fragments[component_index] local component_duplicate = chunk_component_duplicates[component_index] - local ini_component = components[component_fragment] + local ini_component = component_table and component_table[component_fragment] if ini_component == nil then if chunk == prefab_chunk then @@ -2721,6 +2843,10 @@ function __multi_clone_entity(prefab, entity_list, entity_count, components) end end + if component_mapper then + component_mapper(chunk, b_place, e_place) + end + if chunk.__has_insert_hooks then local chunk_fragment_list = chunk.__fragment_list local chunk_fragment_count = chunk.__fragment_count @@ -2779,6 +2905,10 @@ function __purge_chunk(chunk) __error_fmt('chunk should be empty before purging') end + if chunk.__entity_capacity > 0 then + __shrink_chunk(chunk, 0) + end + local chunk_parent = chunk.__parent local chunk_fragment = chunk.__fragment @@ -2833,6 +2963,143 @@ function __purge_chunk(chunk) chunk.__unreachable_or_collected = true end +---@param chunk evolved.chunk +---@param min_capacity integer +function __expand_chunk(chunk, min_capacity) + if __defer_depth <= 0 then + __error_fmt('this operation should be deferred') + end + + local entity_count = chunk.__entity_count + + if min_capacity < entity_count then + min_capacity = entity_count + end + + local old_capacity = chunk.__entity_capacity + if old_capacity >= min_capacity then + -- no need to expand, the chunk is already large enough + return + end + + local new_capacity = old_capacity * 2 + + if new_capacity < min_capacity then + new_capacity = min_capacity + end + + if new_capacity < 4 then + new_capacity = 4 + end + + do + local component_count = chunk.__component_count + local component_storages = chunk.__component_storages + local component_reallocs = chunk.__component_reallocs + + for component_index = 1, component_count do + local component_realloc = component_reallocs[component_index] + + local new_component_storage ---@type evolved.storage? + local old_component_storage = component_storages[component_index] + + if component_realloc then + new_component_storage = component_realloc( + old_component_storage, old_capacity, new_capacity) + else + new_component_storage = __default_realloc( + old_component_storage, old_capacity, new_capacity) + end + + if min_capacity > 0 and not new_component_storage then + __error_fmt( + 'component storage reallocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + elseif min_capacity == 0 and new_component_storage then + __warning_fmt( + 'component storage reallocation for zero capacity should return nil: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + new_component_storage = nil + end + + component_storages[component_index] = new_component_storage + end + end + + chunk.__entity_capacity = new_capacity +end + +---@param chunk evolved.chunk +---@param min_capacity integer +function __shrink_chunk(chunk, min_capacity) + if __defer_depth <= 0 then + __error_fmt('this operation should be deferred') + end + + local entity_count = chunk.__entity_count + + if min_capacity < entity_count then + min_capacity = entity_count + end + + if min_capacity > 0 and min_capacity < 4 then + min_capacity = 4 + end + + local old_capacity = chunk.__entity_capacity + if old_capacity <= min_capacity then + -- no need to shrink, the chunk is already small enough + return + end + + do + local old_entity_list = chunk.__entity_list + local new_entity_list = __lua_table_new(min_capacity) + + __lua_table_move( + old_entity_list, 1, entity_count, + 1, new_entity_list) + + chunk.__entity_list = new_entity_list + end + + do + local component_count = chunk.__component_count + local component_storages = chunk.__component_storages + local component_reallocs = chunk.__component_reallocs + + for component_index = 1, component_count do + local component_realloc = component_reallocs[component_index] + + local new_component_storage ---@type evolved.storage? + local old_component_storage = component_storages[component_index] + + if component_realloc then + new_component_storage = component_realloc( + old_component_storage, old_capacity, min_capacity) + else + new_component_storage = __default_realloc( + old_component_storage, old_capacity, min_capacity) + end + + if min_capacity > 0 and not new_component_storage then + __error_fmt( + 'component storage reallocation failed: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + elseif min_capacity == 0 and new_component_storage then + __warning_fmt( + 'component storage reallocation for zero capacity should return nil: chunk (%s), fragment (%s)', + __lua_tostring(chunk), __id_name(chunk.__component_fragments[component_index])) + new_component_storage = nil + end + + component_storages[component_index] = new_component_storage + end + end + + chunk.__entity_capacity = min_capacity +end + ---@param chunk_list evolved.chunk[] ---@param chunk_count integer function __clear_chunk_list(chunk_list, chunk_count) @@ -3152,11 +3419,10 @@ function __chunk_set(old_chunk, fragment, component) end end - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages + local new_component_reallocs = new_chunk.__component_reallocs + local new_component_compmoves = new_chunk.__component_compmoves local new_chunk_has_setup_hooks = new_chunk.__has_setup_hooks local new_chunk_has_insert_hooks = new_chunk.__has_insert_hooks @@ -3169,40 +3435,53 @@ function __chunk_set(old_chunk, fragment, component) __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end - local sum_entity_count = old_entity_count + new_entity_count - - if new_entity_count == 0 then - old_chunk.__entity_list, new_chunk.__entity_list = - new_entity_list, old_entity_list + local sum_entity_count = old_entity_count + new_chunk.__entity_count - old_entity_list, new_entity_list = - new_entity_list, old_entity_list - - for old_ci = 1, old_component_count do - local old_f = old_component_fragments[old_ci] - local new_ci = new_component_indices[old_f] + if sum_entity_count > new_chunk.__entity_capacity then + __expand_chunk(new_chunk, sum_entity_count) + end - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] - end + local new_entity_list = new_chunk.__entity_list + local new_entity_count = new_chunk.__entity_count - new_chunk.__entity_count = sum_entity_count - else + do for old_ci = 1, old_component_count do local old_f = old_component_fragments[old_ci] local old_cs = old_component_storages[old_ci] local new_ci = new_component_indices[old_f] local new_cs = new_component_storages[new_ci] + local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] - __lua_table_move( - old_cs, 1, old_entity_count, - new_entity_count + 1, new_cs) + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + else + if new_entity_count > 0 then + __default_compmove(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end + end end - __lua_table_move( - old_entity_list, 1, old_entity_count, - new_entity_count + 1, new_entity_list) + if new_entity_count > 0 then + __lua_table_move( + old_entity_list, 1, old_entity_count, + new_entity_count + 1, new_entity_list) + else + old_chunk.__entity_list, new_chunk.__entity_list = + new_entity_list, old_entity_list + + old_entity_list, new_entity_list = + new_entity_list, old_entity_list + end new_chunk.__entity_count = sum_entity_count end @@ -3475,47 +3754,59 @@ function __chunk_remove(old_chunk, ...) end if new_chunk then - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments + local new_component_reallocs = new_chunk.__component_reallocs + local new_component_compmoves = new_chunk.__component_compmoves - local sum_entity_count = old_entity_count + new_entity_count + local sum_entity_count = old_entity_count + new_chunk.__entity_count - if new_entity_count == 0 then - old_chunk.__entity_list, new_chunk.__entity_list = - new_entity_list, old_entity_list - - old_entity_list, new_entity_list = - new_entity_list, old_entity_list - - for new_ci = 1, new_component_count do - local new_f = new_component_fragments[new_ci] - local old_ci = old_component_indices[new_f] + if sum_entity_count > new_chunk.__entity_capacity then + __expand_chunk(new_chunk, sum_entity_count) + end - old_component_storages[old_ci], new_component_storages[new_ci] = - new_component_storages[new_ci], old_component_storages[old_ci] - end + local new_entity_list = new_chunk.__entity_list + local new_entity_count = new_chunk.__entity_count - new_chunk.__entity_count = sum_entity_count - else + do for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] local new_cs = new_component_storages[new_ci] + local new_cr = new_component_reallocs[new_ci] + local new_cm = new_component_compmoves[new_ci] local old_ci = old_component_indices[new_f] local old_cs = old_component_storages[old_ci] - __lua_table_move( - old_cs, 1, old_entity_count, - new_entity_count + 1, new_cs) + if new_cm then + new_cm(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + elseif new_cr then + for old_place = 1, old_entity_count do + local new_place = new_entity_count + old_place + new_cs[new_place] = old_cs[old_place] + end + else + if new_entity_count > 0 then + __default_compmove(old_cs, 1, old_entity_count, new_entity_count + 1, new_cs) + else + old_component_storages[old_ci], new_component_storages[new_ci] = + new_component_storages[new_ci], old_component_storages[old_ci] + end + end end - __lua_table_move( - old_entity_list, 1, old_entity_count, - new_entity_count + 1, new_entity_list) + if new_entity_count > 0 then + __lua_table_move( + old_entity_list, 1, old_entity_count, + new_entity_count + 1, new_entity_list) + else + old_chunk.__entity_list, new_chunk.__entity_list = + new_entity_list, old_entity_list + + old_entity_list, new_entity_list = + new_entity_list, old_entity_list + end new_chunk.__entity_count = sum_entity_count end @@ -3633,7 +3924,7 @@ local __defer_op = { } ---@type table -local __defer_ops = __list_new(__defer_op.__count) +local __defer_ops = __lua_table_new(__defer_op.__count) ---@param hook fun(...) ---@param ... any hook arguments @@ -3710,13 +4001,18 @@ end ---@param chunk? evolved.chunk ---@param entity evolved.entity ----@param component_map table -function __defer_spawn_entity(chunk, entity, component_map) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_spawn_entity(chunk, entity, component_table, component_mapper) + ---@type evolved.component_table? + local component_table2 + + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -3725,43 +4021,53 @@ function __defer_spawn_entity(chunk, entity, component_map) bytecode[length + 1] = __defer_op.spawn_entity bytecode[length + 2] = chunk bytecode[length + 3] = entity - bytecode[length + 4] = component_map_dup + bytecode[length + 4] = component_table2 + bytecode[length + 5] = component_mapper - __defer_length = length + 4 + __defer_length = length + 5 end __defer_ops[__defer_op.spawn_entity] = function(bytes, index) local chunk = bytes[index + 0] local entity = bytes[index + 1] - local component_map_dup = bytes[index + 2] + local component_table2 = bytes[index + 2] + local component_mapper = bytes[index + 3] __evolved_defer() do - __spawn_entity(chunk, entity, component_map_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __spawn_entity(chunk, entity, component_table2, component_mapper) + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 3 + return 4 end ---@param chunk? evolved.chunk ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param component_map table -function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) ---@type evolved.entity[] - local entity_list_dup = __acquire_table(__table_pool_tag.entity_list) + local entity_list2 = __acquire_table(__table_pool_tag.entity_list) __lua_table_move( entity_list, 1, entity_count, - 1, entity_list_dup) + 1, entity_list2) + + ---@type evolved.component_table? + local component_table2 - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -3770,38 +4076,51 @@ function __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_ bytecode[length + 1] = __defer_op.multi_spawn_entity bytecode[length + 2] = chunk bytecode[length + 3] = entity_count - bytecode[length + 4] = entity_list_dup - bytecode[length + 5] = component_map_dup + bytecode[length + 4] = entity_list2 + bytecode[length + 5] = component_table2 + bytecode[length + 6] = component_mapper - __defer_length = length + 5 + __defer_length = length + 6 end __defer_ops[__defer_op.multi_spawn_entity] = function(bytes, index) local chunk = bytes[index + 0] local entity_count = bytes[index + 1] - local entity_list_dup = bytes[index + 2] - local component_map_dup = bytes[index + 3] + local entity_list2 = bytes[index + 2] + local component_table2 = bytes[index + 3] + local component_mapper = bytes[index + 4] __evolved_defer() do - __multi_spawn_entity(chunk, entity_list_dup, entity_count, component_map_dup) - __release_table(__table_pool_tag.entity_list, entity_list_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __multi_spawn_entity(chunk, entity_list2, entity_count, component_table2, component_mapper) + + if entity_list2 then + __release_table(__table_pool_tag.entity_list, entity_list2) + end + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 4 + return 5 end ---@param prefab evolved.entity ---@param entity evolved.entity ----@param component_map table -function __defer_clone_entity(prefab, entity, component_map) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_clone_entity(prefab, entity, component_table, component_mapper) + ---@type evolved.component_table? + local component_table2 + + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -3810,43 +4129,53 @@ function __defer_clone_entity(prefab, entity, component_map) bytecode[length + 1] = __defer_op.clone_entity bytecode[length + 2] = prefab bytecode[length + 3] = entity - bytecode[length + 4] = component_map_dup + bytecode[length + 4] = component_table2 + bytecode[length + 5] = component_mapper - __defer_length = length + 4 + __defer_length = length + 5 end __defer_ops[__defer_op.clone_entity] = function(bytes, index) local prefab = bytes[index + 0] local entity = bytes[index + 1] - local component_map_dup = bytes[index + 2] + local component_table2 = bytes[index + 2] + local component_mapper = bytes[index + 3] __evolved_defer() do - __clone_entity(prefab, entity, component_map_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __clone_entity(prefab, entity, component_table2, component_mapper) + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 3 + return 4 end ---@param prefab evolved.entity ---@param entity_list evolved.entity[] ---@param entity_count integer ----@param component_map table -function __defer_multi_clone_entity(prefab, entity_list, entity_count, component_map) +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper +function __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) ---@type evolved.entity[] - local entity_list_dup = __acquire_table(__table_pool_tag.entity_list) + local entity_list2 = __acquire_table(__table_pool_tag.entity_list) __lua_table_move( entity_list, 1, entity_count, - 1, entity_list_dup) + 1, entity_list2) - ---@type table - local component_map_dup = __acquire_table(__table_pool_tag.component_map) + ---@type evolved.component_table? + local component_table2 - for fragment, component in __lua_next, component_map do - component_map_dup[fragment] = component + if component_table then + component_table2 = __acquire_table(__table_pool_tag.component_table) + + for fragment, component in __lua_next, component_table do + component_table2[fragment] = component + end end local length = __defer_length @@ -3855,27 +4184,35 @@ function __defer_multi_clone_entity(prefab, entity_list, entity_count, component bytecode[length + 1] = __defer_op.multi_clone_entity bytecode[length + 2] = prefab bytecode[length + 3] = entity_count - bytecode[length + 4] = entity_list_dup - bytecode[length + 5] = component_map_dup + bytecode[length + 4] = entity_list2 + bytecode[length + 5] = component_table2 + bytecode[length + 6] = component_mapper - __defer_length = length + 5 + __defer_length = length + 6 end __defer_ops[__defer_op.multi_clone_entity] = function(bytes, index) local prefab = bytes[index + 0] local entity_count = bytes[index + 1] - local entity_list_dup = bytes[index + 2] - local component_map_dup = bytes[index + 3] + local entity_list2 = bytes[index + 2] + local component_table2 = bytes[index + 3] + local component_mapper = bytes[index + 4] __evolved_defer() do - __multi_clone_entity(prefab, entity_list_dup, entity_count, component_map_dup) - __release_table(__table_pool_tag.entity_list, entity_list_dup) - __release_table(__table_pool_tag.component_map, component_map_dup) + __multi_clone_entity(prefab, entity_list2, entity_count, component_table2, component_mapper) + + if entity_list2 then + __release_table(__table_pool_tag.entity_list, entity_list2) + end + + if component_table2 then + __release_table(__table_pool_tag.component_table, component_table2) + end end __evolved_commit() - return 4 + return 5 end --- @@ -4194,28 +4531,33 @@ function __evolved_cancel() return __evolved_commit() end ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __evolved_spawn(components) - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - +function __evolved_spawn(component_table, component_mapper) if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end local entity = __acquire_id() + if not component_table or not __lua_next(component_table) then + return entity + end + if __defer_depth > 0 then - __defer_spawn_entity(nil, entity, components) + __defer_spawn_entity(nil, entity, component_table, component_mapper) else __evolved_defer() do - __spawn_entity(nil, entity, components) + __spawn_entity(nil, entity, component_table, component_mapper) end __evolved_commit() end @@ -4224,37 +4566,42 @@ function __evolved_spawn(components) end ---@param entity_count integer ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __evolved_multi_spawn(entity_count, components) +function __evolved_multi_spawn(entity_count, component_table, component_mapper) if entity_count <= 0 then return {}, 0 end - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() end + if not component_table or not __lua_next(component_table) then + return entity_list, entity_count + end + if __defer_depth > 0 then - __defer_multi_spawn_entity(nil, entity_list, entity_count, components) + __defer_multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_spawn_entity(nil, entity_list, entity_count, components) + __multi_spawn_entity(nil, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -4263,21 +4610,22 @@ function __evolved_multi_spawn(entity_count, components) end ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __evolved_clone(prefab, components) - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - +function __evolved_clone(prefab, component_table, component_mapper) if __debug_mode then if not __evolved_alive(prefab) then __error_fmt('the prefab (%s) is not alive and cannot be used', __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -4285,11 +4633,11 @@ function __evolved_clone(prefab, components) local entity = __acquire_id() if __defer_depth > 0 then - __defer_clone_entity(prefab, entity, components) + __defer_clone_entity(prefab, entity, component_table, component_mapper) else __evolved_defer() do - __clone_entity(prefab, entity, components) + __clone_entity(prefab, entity, component_table, component_mapper) end __evolved_commit() end @@ -4299,42 +4647,43 @@ end ---@param entity_count integer ---@param prefab evolved.entity ----@param components? table +---@param component_table? evolved.component_table +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __evolved_multi_clone(entity_count, prefab, components) +function __evolved_multi_clone(entity_count, prefab, component_table, component_mapper) if entity_count <= 0 then return {}, 0 end - components = components or __safe_tbls.__EMPTY_COMPONENT_MAP - if __debug_mode then if not __evolved_alive(prefab) then __error_fmt('the prefab (%s) is not alive and cannot be used', __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() end if __defer_depth > 0 then - __defer_multi_clone_entity(prefab, entity_list, entity_count, components) + __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_clone_entity(prefab, entity_list, entity_count, components) + __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -4649,9 +4998,6 @@ function __evolved_set(entity, fragment, component) end end - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages @@ -4666,10 +5012,16 @@ function __evolved_set(entity, fragment, component) __evolved_get(fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT) end - local new_place = new_entity_count + 1 - new_chunk.__entity_count = new_place + local new_place = new_chunk.__entity_count + 1 + + if new_place > new_chunk.__entity_capacity then + __expand_chunk(new_chunk, new_place) + end + + local new_entity_list = new_chunk.__entity_list new_entity_list[new_place] = entity + new_chunk.__entity_count = new_place if old_chunk then local old_component_count = old_chunk.__component_count @@ -4855,17 +5207,20 @@ function __evolved_remove(entity, ...) end if new_chunk then - local new_entity_list = new_chunk.__entity_list - local new_entity_count = new_chunk.__entity_count - local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments - local new_place = new_entity_count + 1 - new_chunk.__entity_count = new_place + local new_place = new_chunk.__entity_count + 1 + + if new_place > new_chunk.__entity_capacity then + __expand_chunk(new_chunk, new_place) + end + + local new_entity_list = new_chunk.__entity_list new_entity_list[new_place] = entity + new_chunk.__entity_count = new_place for new_ci = 1, new_component_count do local new_f = new_component_fragments[new_ci] @@ -5463,12 +5818,21 @@ function __evolved_collect_garbage() for postorder_chunk_index = postorder_chunk_stack_size, 1, -1 do local postorder_chunk = postorder_chunk_stack[postorder_chunk_index] + local postorder_chunk_child_count = postorder_chunk.__child_count + local postorder_chunk_entity_count = postorder_chunk.__entity_count + local postorder_chunk_entity_capacity = postorder_chunk.__entity_capacity + local should_be_purged = - postorder_chunk.__child_count == 0 and - postorder_chunk.__entity_count == 0 + postorder_chunk_child_count == 0 and + postorder_chunk_entity_count == 0 + + local should_be_shrunk = + postorder_chunk_entity_count < postorder_chunk_entity_capacity if should_be_purged then __purge_chunk(postorder_chunk) + elseif should_be_shrunk then + __shrink_chunk(postorder_chunk, 0) end end @@ -5676,7 +6040,7 @@ end ---@nodiscard function __evolved_builder() return __lua_setmetatable({ - __components = {}, + __component_table = {}, }, __builder_mt) end @@ -5684,7 +6048,7 @@ function __builder_mt:__tostring() local fragment_list = {} ---@type evolved.fragment[] local fragment_count = 0 ---@type integer - for fragment in __lua_next, self.__components do + for fragment in __lua_next, self.__component_table do fragment_count = fragment_count + 1 fragment_list[fragment_count] = fragment end @@ -5701,49 +6065,58 @@ function __builder_mt:__tostring() end ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:build(prefab) +function __builder_mt:build(prefab, component_mapper) if prefab then - return self:clone(prefab) + return self:clone(prefab, component_mapper) else - return self:spawn() + return self:spawn(component_mapper) end end ---@param entity_count integer ---@param prefab? evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_build(entity_count, prefab) +function __builder_mt:multi_build(entity_count, prefab, component_mapper) if prefab then - return self:multi_clone(entity_count, prefab) + return self:multi_clone(entity_count, prefab, component_mapper) else - return self:multi_spawn(entity_count) + return self:multi_spawn(entity_count, component_mapper) end end +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:spawn() +function __builder_mt:spawn(component_mapper) local chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end local entity = __acquire_id() + if not component_table or not __lua_next(component_table) then + return entity + end + if __defer_depth > 0 then - __defer_spawn_entity(chunk, entity, components) + __defer_spawn_entity(chunk, entity, component_table, component_mapper) else __evolved_defer() do - __spawn_entity(chunk, entity, components) + __spawn_entity(chunk, entity, component_table, component_mapper) end __evolved_commit() end @@ -5752,37 +6125,44 @@ function __builder_mt:spawn() end ---@param entity_count integer +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_spawn(entity_count) +function __builder_mt:multi_spawn(entity_count, component_mapper) if entity_count <= 0 then return {}, 0 end local chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table if __debug_mode then - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() end + if not component_table or not __lua_next(component_table) then + return entity_list, entity_count + end + if __defer_depth > 0 then - __defer_multi_spawn_entity(chunk, entity_list, entity_count, components) + __defer_multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_spawn_entity(chunk, entity_list, entity_count, components) + __multi_spawn_entity(chunk, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -5791,9 +6171,10 @@ function __builder_mt:multi_spawn(entity_count) end ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity entity -function __builder_mt:clone(prefab) - local components = self.__components +function __builder_mt:clone(prefab, component_mapper) + local component_table = self.__component_table if __debug_mode then if not __evolved_alive(prefab) then @@ -5801,10 +6182,12 @@ function __builder_mt:clone(prefab) __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end @@ -5812,11 +6195,11 @@ function __builder_mt:clone(prefab) local entity = __acquire_id() if __defer_depth > 0 then - __defer_clone_entity(prefab, entity, components) + __defer_clone_entity(prefab, entity, component_table, component_mapper) else __evolved_defer() do - __clone_entity(prefab, entity, components) + __clone_entity(prefab, entity, component_table, component_mapper) end __evolved_commit() end @@ -5826,14 +6209,15 @@ end ---@param entity_count integer ---@param prefab evolved.entity +---@param component_mapper? evolved.component_mapper ---@return evolved.entity[] entity_list ---@return integer entity_count -function __builder_mt:multi_clone(entity_count, prefab) +function __builder_mt:multi_clone(entity_count, prefab, component_mapper) if entity_count <= 0 then return {}, 0 end - local components = self.__components + local component_table = self.__component_table if __debug_mode then if not __evolved_alive(prefab) then @@ -5841,26 +6225,28 @@ function __builder_mt:multi_clone(entity_count, prefab) __id_name(prefab)) end - for fragment in __lua_next, components do - if not __evolved_alive(fragment) then - __error_fmt('the fragment (%s) is not alive and cannot be used', - __id_name(fragment)) + if component_table then + for fragment in __lua_next, component_table do + if not __evolved_alive(fragment) then + __error_fmt('the fragment (%s) is not alive and cannot be used', + __id_name(fragment)) + end end end end - local entity_list = __list_new(entity_count) + local entity_list = __lua_table_new(entity_count) for entity_index = 1, entity_count do entity_list[entity_index] = __acquire_id() end if __defer_depth > 0 then - __defer_multi_clone_entity(prefab, entity_list, entity_count, components) + __defer_multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) else __evolved_defer() do - __multi_clone_entity(prefab, entity_list, entity_count, components) + __multi_clone_entity(prefab, entity_list, entity_count, component_table, component_mapper) end __evolved_commit() end @@ -5917,7 +6303,7 @@ function __builder_mt:get(...) return end - local cs = self.__components + local cs = self.__component_table if fragment_count == 1 then local f1 = ... @@ -5960,7 +6346,7 @@ function __builder_mt:set(fragment, component) end local new_chunk = __chunk_with_fragment(self.__chunk, fragment) - local components = self.__components + local component_table = self.__component_table if new_chunk.__has_setup_hooks then ---@type evolved.default?, evolved.duplicate? @@ -5972,12 +6358,12 @@ function __builder_mt:set(fragment, component) if new_component ~= nil and fragment_duplicate then new_component = fragment_duplicate(new_component) end if new_component == nil then new_component = true end - components[fragment] = new_component + component_table[fragment] = new_component else local new_component = component if new_component == nil then new_component = true end - components[fragment] = new_component + component_table[fragment] = new_component end self.__chunk = new_chunk @@ -5994,12 +6380,12 @@ function __builder_mt:remove(...) end local new_chunk = self.__chunk - local components = self.__components + local component_table = self.__component_table for fragment_index = 1, fragment_count do ---@type evolved.fragment local fragment = __lua_select(fragment_index, ...) - new_chunk, components[fragment] = __chunk_without_fragment(new_chunk, fragment), nil + new_chunk, component_table[fragment] = __chunk_without_fragment(new_chunk, fragment), nil end self.__chunk = new_chunk @@ -6009,7 +6395,7 @@ end ---@return evolved.builder builder function __builder_mt:clear() self.__chunk = nil - __lua_table_clear(self.__components) + __lua_table_clear(self.__component_table) return self end @@ -6051,6 +6437,18 @@ function __builder_mt:duplicate(duplicate) return self:set(__DUPLICATE, duplicate) end +---@param realloc evolved.realloc +---@return evolved.builder builder +function __builder_mt:realloc(realloc) + return self:set(__REALLOC, realloc) +end + +---@param compmove evolved.compmove +---@return evolved.builder builder +function __builder_mt:compmove(compmove) + return self:set(__COMPMOVE, compmove) +end + ---@return evolved.builder builder function __builder_mt:prefab() return self:set(__PREFAB) @@ -6263,6 +6661,12 @@ __evolved_set(__DEFAULT, __ON_REMOVE, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_INSERT, __update_major_chunks) __evolved_set(__DUPLICATE, __ON_REMOVE, __update_major_chunks) +__evolved_set(__REALLOC, __ON_SET, __update_major_chunks) +__evolved_set(__REALLOC, __ON_REMOVE, __update_major_chunks) + +__evolved_set(__COMPMOVE, __ON_SET, __update_major_chunks) +__evolved_set(__COMPMOVE, __ON_REMOVE, __update_major_chunks) + --- --- --- @@ -6279,6 +6683,9 @@ __evolved_set(__INTERNAL, __NAME, 'INTERNAL') __evolved_set(__DEFAULT, __NAME, 'DEFAULT') __evolved_set(__DUPLICATE, __NAME, 'DUPLICATE') +__evolved_set(__REALLOC, __NAME, 'REALLOC') +__evolved_set(__COMPMOVE, __NAME, 'COMPMOVE') + __evolved_set(__PREFAB, __NAME, 'PREFAB') __evolved_set(__DISABLED, __NAME, 'DISABLED') @@ -6320,6 +6727,9 @@ __evolved_set(__INTERNAL, __INTERNAL) __evolved_set(__DEFAULT, __INTERNAL) __evolved_set(__DUPLICATE, __INTERNAL) +__evolved_set(__REALLOC, __INTERNAL) +__evolved_set(__COMPMOVE, __INTERNAL) + __evolved_set(__PREFAB, __INTERNAL) __evolved_set(__DISABLED, __INTERNAL) @@ -6683,6 +7093,9 @@ evolved.INTERNAL = __INTERNAL evolved.DEFAULT = __DEFAULT evolved.DUPLICATE = __DUPLICATE +evolved.REALLOC = __REALLOC +evolved.COMPMOVE = __COMPMOVE + evolved.PREFAB = __PREFAB evolved.DISABLED = __DISABLED diff --git a/example/main.lua b/example/main.lua index a7ac0c6..84abfec 100644 --- a/example/main.lua +++ b/example/main.lua @@ -52,25 +52,28 @@ evolved.builder() :name('SYSTEMS.STARTUP') :group(STAGES.ON_SETUP) :prologue(function() - local screen_width, screen_height = love.graphics.getDimensions() - - local circle_list, circle_count = evolved.multi_clone(100, PREFABS.CIRCLE) + evolved.multi_clone(500, PREFABS.CIRCLE, nil, function(chunk, b_place, e_place) + local screen_width, screen_height = love.graphics.getDimensions() - for i = 1, circle_count do - local circle = circle_list[i] + ---@type number[], number[] + local position_xs, position_ys = chunk:components( + FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) - local px = math.random() * screen_width - local py = math.random() * screen_height + ---@type number[], number[] + local velocity_xs, velocity_ys = chunk:components( + FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y) - local vx = math.random(-100, 100) - local vy = math.random(-100, 100) + for i = b_place, e_place do + local px = math.random() * screen_width + local py = math.random() * screen_height - evolved.set(circle, FRAGMENTS.POSITION_X, px) - evolved.set(circle, FRAGMENTS.POSITION_Y, py) + local vx = math.random(-100, 100) + local vy = math.random(-100, 100) - evolved.set(circle, FRAGMENTS.VELOCITY_X, vx) - evolved.set(circle, FRAGMENTS.VELOCITY_Y, vy) - end + position_xs[i], position_ys[i] = px, py + velocity_xs[i], velocity_ys[i] = vx, vy + end + end) end):build() evolved.builder() @@ -124,7 +127,7 @@ evolved.builder() for i = 1, entity_count do local x, y = position_xs[i], position_ys[i] - love.graphics.circle('fill', x, y, 10) + love.graphics.circle('fill', x, y, 5) end end):build() diff --git a/rockspecs/evolved.lua-1.8.0-0.rockspec b/rockspecs/evolved.lua-1.8.0-0.rockspec new file mode 100644 index 0000000..96be015 --- /dev/null +++ b/rockspecs/evolved.lua-1.8.0-0.rockspec @@ -0,0 +1,34 @@ +rockspec_format = "3.0" +package = "evolved.lua" +version = "1.8.0-0" +source = { + url = "git://github.com/BlackMATov/evolved.lua", + tag = "v1.8.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", + } +}