Merge branch 'feature/realloc' into dev

This commit is contained in:
BlackMATov
2026-01-16 07:27:02 +07:00
6 changed files with 1245 additions and 142 deletions

174
README.md
View File

@@ -53,6 +53,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 +62,7 @@
- [Chunk](#chunk)
- [Builder](#builder)
- [Changelog](#changelog)
- [vX.Y.Z](#vxyz)
- [v1.7.0](#v170)
- [v1.6.0](#v160)
- [v1.5.0](#v150)
@@ -1153,6 +1155,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
--
--
do
local entity_list, entity_count = evolved.builder()
:set(POSITION_X)
:set(POSITION_Y)
:set(VELOCITY_X)
:set(VELOCITY_Y)
:multi_build(10000)
for i = 1, entity_count do
local entity = entity_list[i]
evolved.set(entity, POSITION_X, math.random(0, 640))
evolved.set(entity, POSITION_Y, math.random(0, 480))
evolved.set(entity, VELOCITY_X, math.random(-100, 100))
evolved.set(entity, VELOCITY_Y, 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
@@ -1171,6 +1312,9 @@ storage :: component[]
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 +1344,9 @@ INTERNAL :: fragment
DEFAULT :: fragment
DUPLICATE :: fragment
REALLOC :: fragment
COMPMOVE :: fragment
PREFAB :: fragment
DISABLED :: fragment
@@ -1335,6 +1482,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 +1511,10 @@ builder_mt:destruction_policy :: id -> builder
## Changelog
### vX.Y.Z
- Added the new [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove) fragment traits that allow customizing component storages
### v1.7.0
- Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries
@@ -1430,6 +1584,10 @@ builder_mt:destruction_policy :: id -> builder
### `evolved.DUPLICATE`
### `evolved.REALLOC`
### `evolved.COMPMOVE`
### `evolved.PREFAB`
### `evolved.DISABLED`
@@ -2047,6 +2205,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

View File

@@ -10,7 +10,12 @@
## 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?
## Known Issues
- Errors in hooks are cannot be handled properly right now
- Errors in hooks or rellocs/compmoves are cannot be handled properly right now

View File

@@ -8,6 +8,7 @@ require 'develop.testing.main_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 +36,5 @@ print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.requires_fuzz'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.unique_fuzz'
print 'All tests passed.'

View File

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

View File

@@ -64,6 +64,9 @@
default: function<Component>(self: Builder, default: Component): Builder
duplicate: function<Component>(self: Builder, duplicate: function(Component): Component): Builder
realloc: function<Component>(self: Builder, realloc: function({ Component } | nil, integer, integer): { Component } | nil): Builder
compmove: function<Component>(self: Builder, compmove: function({ Component }, integer, integer, integer, { Component })): Builder
prefab: function(self: Builder): Builder
disabled: function(self: Builder): Builder
@@ -98,6 +101,9 @@
DEFAULT: Fragment
DUPLICATE: Fragment
REALLOC: Fragment
COMPMOVE: Fragment
PREFAB: Fragment
DISABLED: Fragment

View File

@@ -40,6 +40,9 @@ local evolved = {
---@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 +161,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<evolved.fragment, integer>
---@field package __fragment_list evolved.fragment[]
@@ -168,6 +172,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<evolved.fragment, evolved.chunk>
---@field package __without_fragment_edges table<evolved.fragment, evolved.chunk>
---@field package __with_required_fragments? evolved.chunk
@@ -971,6 +977,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()
@@ -1100,6 +1109,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 +1156,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 +1184,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 +1191,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 +1234,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 +1245,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 +1320,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 +1462,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 +1473,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]
---@type evolved.default?, evolved.duplicate?
local fragment_default, fragment_duplicate =
__evolved_get(fragment, __DEFAULT, __DUPLICATE)
local component_index = component_indices[fragment]
local component_realloc = component_index and component_reallocs[component_index]
---@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 +1523,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 +1532,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_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_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
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
end
end
elseif component_index then
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?
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 = 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 = true end
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
@@ -2159,27 +2290,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,18 +2308,6 @@ 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
@@ -2230,9 +2338,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 +2345,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
@@ -2349,9 +2460,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 +2467,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
@@ -2500,9 +2614,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 +2624,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
@@ -2646,9 +2763,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 +2773,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
@@ -2779,6 +2899,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 +2957,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 +3413,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 +3429,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
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
if sum_entity_count > new_chunk.__entity_capacity then
__expand_chunk(new_chunk, sum_entity_count)
end
old_entity_list, new_entity_list =
new_entity_list, old_entity_list
local new_entity_list = new_chunk.__entity_list
local new_entity_count = new_chunk.__entity_count
for old_ci = 1, old_component_count do
local old_f = old_component_fragments[old_ci]
local new_ci = new_component_indices[old_f]
old_component_storages[old_ci], new_component_storages[new_ci] =
new_component_storages[new_ci], old_component_storages[old_ci]
end
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 +3748,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
if sum_entity_count > new_chunk.__entity_capacity then
__expand_chunk(new_chunk, sum_entity_count)
end
old_entity_list, new_entity_list =
new_entity_list, old_entity_list
local new_entity_list = new_chunk.__entity_list
local new_entity_count = new_chunk.__entity_count
for new_ci = 1, new_component_count do
local new_f = new_component_fragments[new_ci]
local old_ci = old_component_indices[new_f]
old_component_storages[old_ci], new_component_storages[new_ci] =
new_component_storages[new_ci], old_component_storages[old_ci]
end
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 +3918,7 @@ local __defer_op = {
}
---@type table<evolved.defer_op, fun(bytes: any[], index: integer): integer>
local __defer_ops = __list_new(__defer_op.__count)
local __defer_ops = __lua_table_new(__defer_op.__count)
---@param hook fun(...)
---@param ... any hook arguments
@@ -4243,7 +4528,7 @@ function __evolved_multi_spawn(entity_count, components)
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()
@@ -4323,7 +4608,7 @@ function __evolved_multi_clone(entity_count, prefab, components)
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()
@@ -4649,9 +4934,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 +4948,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 +5143,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 +5754,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
@@ -5771,7 +6071,7 @@ function __builder_mt:multi_spawn(entity_count)
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()
@@ -5849,7 +6149,7 @@ function __builder_mt:multi_clone(entity_count, prefab)
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()
@@ -6051,6 +6351,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 +6575,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 +6597,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 +6641,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 +7007,9 @@ evolved.INTERNAL = __INTERNAL
evolved.DEFAULT = __DEFAULT
evolved.DUPLICATE = __DUPLICATE
evolved.REALLOC = __REALLOC
evolved.COMPMOVE = __COMPMOVE
evolved.PREFAB = __PREFAB
evolved.DISABLED = __DISABLED