16 Commits

Author SHA1 Message Date
e48bdf0511 Merge pull request #39 from BlackMATov/dev
Dev
2026-01-18 20:27:35 +07:00
BlackMATov
e9824a4776 v1.8.0 2026-01-18 20:24:53 +07:00
BlackMATov
c3760c40bf update README 2026-01-18 20:20:13 +07:00
BlackMATov
a2a4fc56e3 Merge branch 'feature/mappers' into dev 2026-01-18 19:30:08 +07:00
BlackMATov
21d5091d14 first mappers impl 2026-01-18 19:28:12 +07:00
BlackMATov
18ee0d9a12 Merge branch 'feature/realloc' into dev 2026-01-16 07:27:02 +07:00
BlackMATov
12c86ca679 update README 2026-01-16 07:24:38 +07:00
BlackMATov
a0f252f47c additional realloc checks 2026-01-16 06:14:59 +07:00
BlackMATov
b17118544d little cleanup 2026-01-16 04:17:09 +07:00
BlackMATov
b9cdbe961b custom storage free semantic 2026-01-16 04:12:17 +07:00
BlackMATov
4f78c8245c update roadmap 2026-01-14 16:12:24 +07:00
BlackMATov
a7e5652ad4 update readme and teal defs (REALLOC/COMPMOVE fragments) 2026-01-13 05:11:04 +07:00
BlackMATov
c52f708184 fix batch optimizations with reallocs 2026-01-13 04:47:16 +07:00
BlackMATov
e75ddef396 first realloc/compmove imp 2026-01-13 01:14:50 +07:00
BlackMATov
c9bfb26748 dummy chunk entity capacity 2026-01-12 16:29:44 +07:00
BlackMATov
3a3abbd2fd dummy realloc fragments 2026-01-12 16:28:32 +07:00
9 changed files with 1955 additions and 390 deletions

299
README.md
View File

@@ -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<evolved.fragment, evolved.component>
---@param component_table? table<evolved.fragment, evolved.component>
---@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<evolved.fragment, evolved.component>
---@param component_table? table<evolved.fragment, evolved.component>
---@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 :: <fragment, component>
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 :: <fragment, component>? -> entity
multi_spawn :: integer, <fragment, component>? -> entity[], integer
spawn :: component_table?, component_mapper? -> entity
multi_spawn :: integer, component_table?, component_mapper? -> entity[], integer
clone :: entity, <fragment, component>? -> entity
multi_clone :: integer, entity, <fragment, component>? -> 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<evolved.fragment, evolved.component>
---@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<evolved.fragment, evolved.component>
---@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<evolved.fragment, evolved.component>
---@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<evolved.fragment, evolved.component>
---@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

View File

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

View File

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

View File

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

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

@@ -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<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 +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
spawn: function(
component_table?: { Fragment: any },
component_mapper?: function(Chunk, integer)): Entity
clone: function(prefab: Entity, components?: { Fragment: any }): Entity
multi_clone: function(entity_count: integer, prefab: Entity, components?: { Fragment: any }): { Entity }, integer
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

File diff suppressed because it is too large Load Diff

View File

@@ -52,25 +52,28 @@ evolved.builder()
:name('SYSTEMS.STARTUP')
:group(STAGES.ON_SETUP)
:prologue(function()
local screen_width, screen_height = love.graphics.getDimensions()
evolved.multi_clone(500, PREFABS.CIRCLE, nil, function(chunk, b_place, e_place)
local screen_width, screen_height = love.graphics.getDimensions()
local circle_list, circle_count = evolved.multi_clone(100, PREFABS.CIRCLE)
---@type number[], number[]
local position_xs, position_ys = chunk:components(
FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y)
for i = 1, circle_count do
local circle = circle_list[i]
---@type number[], number[]
local velocity_xs, velocity_ys = chunk:components(
FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y)
local px = math.random() * screen_width
local py = math.random() * screen_height
for i = b_place, e_place do
local px = math.random() * screen_width
local py = math.random() * screen_height
local vx = math.random(-100, 100)
local vy = math.random(-100, 100)
local vx = math.random(-100, 100)
local vy = math.random(-100, 100)
evolved.set(circle, FRAGMENTS.POSITION_X, px)
evolved.set(circle, FRAGMENTS.POSITION_Y, py)
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()

View File

@@ -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",
}
}