9 Commits

Author SHA1 Message Date
74e5693443 Merge pull request #46 from BlackMATov/dev
Dev
2026-03-30 16:23:53 +07:00
BlackMATov
8c59bdfd7c v1.11.0 2026-03-30 16:20:04 +07:00
BlackMATov
a8f95c6b99 update README 2026-03-30 16:18:19 +07:00
BlackMATov
d40cd0e4ae update teal types 2026-03-30 07:12:42 +07:00
BlackMATov
3ca898bfa0 new evolved.error_handler function
#45
2026-03-30 07:01:47 +07:00
BlackMATov
e4166b7ebe more CI lua vers 2026-03-30 05:21:51 +07:00
BlackMATov
cea9b9f051 Merge branch 'feature/cached_hooks' into dev 2026-03-30 05:15:10 +07:00
BlackMATov
b6cc943850 update changelog 2026-03-06 06:03:01 +07:00
BlackMATov
a9fecee874 hold fragments with insert/remove/explicit hooks separately 2026-03-06 05:59:26 +07:00
15 changed files with 625 additions and 201 deletions

View File

@@ -1,4 +1,4 @@
name: lua5.1
name: lua5.1.5
on: [push, pull_request]
@@ -8,14 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
lua_version: ["5.1"]
lua_version: ["5.1.5"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

View File

@@ -1,4 +1,4 @@
name: lua5.2
name: lua5.2.4
on: [push, pull_request]
@@ -8,14 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
lua_version: ["5.2"]
lua_version: ["5.2.4"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

View File

@@ -1,4 +1,4 @@
name: lua5.3
name: lua5.3.6
on: [push, pull_request]
@@ -8,14 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
lua_version: ["5.3"]
lua_version: ["5.3.6"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

View File

@@ -1,4 +1,4 @@
name: lua5.4
name: lua5.4.8
on: [push, pull_request]
@@ -8,14 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
lua_version: ["5.4"]
lua_version: ["5.4.8"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

22
.github/workflows/lua5.5.0.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: lua5.5.0
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.operating_system}}
strategy:
fail-fast: false
matrix:
lua_version: ["5.5.0"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

View File

@@ -1,21 +0,0 @@
name: luajit
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.operating_system}}
strategy:
fail-fast: false
matrix:
lua_version: ["luajit"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua ./develop/all.lua

22
.github/workflows/luajit2.1.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: luajit2.1
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.operating_system}}
strategy:
fail-fast: false
matrix:
lua_version: ["luajit-2.1"]
operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"]
name: ${{matrix.operating_system}}-${{matrix.lua_version}}
steps:
- uses: actions/checkout@v6
- uses: ilammy/msvc-dev-cmd@v1
- uses: leafo/gh-actions-lua@v12
with:
luaVersion: ${{matrix.lua_version}}
- run: |
lua -v
lua ./develop/all.lua

View File

@@ -2,25 +2,35 @@
> Evolved ECS (Entity-Component-System) for Lua
[![lua5.1][badge.lua5.1]][lua5.1]
[![lua5.4][badge.lua5.4]][lua5.4]
[![luajit][badge.luajit]][luajit]
[![lua5.1.5][badge.lua5.1.5]][lua5.1.5]
[![lua5.2.4][badge.lua5.2.4]][lua5.2.4]
[![lua5.3.6][badge.lua5.3.6]][lua5.3.6]
[![lua5.4.8][badge.lua5.4.8]][lua5.4.8]
[![lua5.5.0][badge.lua5.5.0]][lua5.5.0]
[![luajit2.1][badge.luajit2.1]][luajit2.1]
[![license][badge.license]][license]
[badge.lua5.1]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.1.yml?label=Lua%205.1
[badge.lua5.4]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.4.yml?label=Lua%205.4
[badge.luajit]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/luajit.yml?label=LuaJIT
[badge.lua5.1.5]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.1.5.yml?label=Lua%205.1
[badge.lua5.2.4]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.2.4.yml?label=Lua%205.2
[badge.lua5.3.6]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.3.6.yml?label=Lua%205.3
[badge.lua5.4.8]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.4.8.yml?label=Lua%205.4
[badge.lua5.5.0]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/lua5.5.0.yml?label=Lua%205.5
[badge.luajit2.1]: https://img.shields.io/github/actions/workflow/status/BlackMATov/evolved.lua/.github/workflows/luajit2.1.yml?label=LuaJIT%202.1
[badge.license]: https://img.shields.io/badge/license-MIT-blue
[lua5.1]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.1
[lua5.4]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.4
[luajit]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Aluajit
[lua5.1.5]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.1.5
[lua5.2.4]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.2.4
[lua5.3.6]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.3.6
[lua5.4.8]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.4.8
[lua5.5.0]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Alua5.5.0
[luajit2.1]: https://github.com/BlackMATov/evolved.lua/actions?query=workflow%3Aluajit2.1
[license]: https://en.wikipedia.org/wiki/MIT_License
[evolved]: https://github.com/BlackMATov/evolved.lua
- [Introduction](#introduction)
- [Performance](#performance)
- [Benchmarks](#benchmarks)
- [Simplicity](#simplicity)
- [Flexibility](#flexibility)
- [Installation](#installation)
@@ -56,6 +66,7 @@
- [Fragment Requirements](#fragment-requirements)
- [Destruction Policies](#destruction-policies)
- [Custom Component Storages](#custom-component-storages)
- [Error Handling](#error-handling)
- [Garbage Collection](#garbage-collection)
- [Cheat Sheet](#cheat-sheet)
- [Aliases](#aliases)
@@ -65,6 +76,7 @@
- [Chunk](#chunk)
- [Builder](#builder)
- [Changelog](#changelog)
- [v1.11.0](#v1110)
- [v1.10.0](#v1100)
- [v1.9.0](#v190)
- [v1.8.0](#v180)
@@ -88,6 +100,10 @@ This library is designed to be fast. Many techniques are employed to achieve thi
Not all the optimizations I want to implement are done yet, but I will be working on them. However, I can already say that the library is fast enough for most use cases.
#### Benchmarks
The library contains some micro-benchmarks for internal use in the [develop/benchmarks](./develop/benchmarks/) directory, but they are not comprehensive and are not intended for comparison with other ECS libraries. I don't like cross-library benchmarks, as they are often biased and not representative of real-world performance. However, you can look at benchmarks from independent third-party authors, for example, [these](https://github.com/jeffzi/lua-ecs-benchmark) nice benchmarks by [@jeffzi](https://github.com/jeffzi) that cover most libraries, including `evolved.lua`.
### Simplicity
I have tried to keep the [API](#cheat-sheet) as simple and intuitive as possible. I also keep the number of functions under control. All the functions are self-explanatory and easy to use. After reading the [Overview](#overview) section, you should be able to use the library without any problems.
@@ -1416,6 +1432,24 @@ evolved.builder()
evolved.process_with(MOVEMENT_SYSTEM, 0.016)
```
### Error Handling
Since systems perform processing in a deferred scope, any errors that occur during processing can leave the library in an inconsistent state. To handle this, the library runs a protected call for each system and catches any errors that occur. By default, the library will collect the error message and the current stack trace and rethrow the error with this information. It is safe to catch errors, but it can be inconvenient to use with a debugger, because debuggers usually break on the rethrow instead of the place where the error happened. To make it easier to debug errors in systems, the library provides a way to set a custom error handler that will be called when an error occurs during system processing. For example, you can set an error handler that breaks into the debugger:
```lua
-- we use Local Lua Debugger in this example
local debugger = require 'lldebugger'
debugger.start()
local evolved = require 'evolved'
evolved.error_handler(function(message)
debugger.requestBreak()
return debug.traceback(message)
end)
```
This way, when an error occurs during system processing, the error handler will be called, which will break into the debugger, allowing you to inspect the state of the program at the moment of the error. After you continue execution in the debugger, the error will be rethrown with the original message and stack trace.
### Garbage Collection
While using the library, some internal data structures can become obsolete and should be cleaned up to free memory. For example, empty chunks that no longer contain entities can be removed. Component storages can also have unused capacity that can be shrunk to save memory. The library provides a function to control this garbage collection process.
@@ -1577,6 +1611,7 @@ process :: system... -> ()
process_with :: system, ... -> ()
debug_mode :: boolean -> ()
error_handler :: {string -> string}? -> ()
collect_garbage :: boolean? -> ()
```
@@ -1668,6 +1703,12 @@ builder_mt:destruction_policy :: id -> builder
## Changelog
### v1.11.0
- Slightly improved performance of modifying operations for fragments with [`ON_INSERT`](#evolvedon_insert) and [`ON_REMOVE`](#evolvedon_remove) hooks
- Slightly improved performance of queries with [`EXPLICIT`](#evolvedexplicit) fragments
- Added the new [`evolved.error_handler`](#evolvederror_handler) function that allows setting a custom error handler for better system processing debugging experience
### v1.10.0
- Added the new [`evolved.lookup`](#evolvedlookup) and [`evolved.multi_lookup`](#evolvedmulti_lookup) functions that allow finding ids by their names
@@ -2187,6 +2228,13 @@ function evolved.process_with(system, ...) end
function evolved.debug_mode(yesno) end
```
### `evolved.error_handler`
```lua
---@param handler? fun(message: string): string
function evolved.error_handler(handler) end
```
### `evolved.collect_garbage`
```lua

View File

@@ -1,4 +1,5 @@
require 'develop.testing.build_tests'
require 'develop.testing.cached_hooks_tests'
require 'develop.testing.cancel_tests'
require 'develop.testing.clone_tests'
require 'develop.testing.depth_tests'

View File

@@ -0,0 +1,203 @@
local evo = require 'evolved'
evo.debug_mode(true)
do
local f1, f2 = evo.id(2)
local insert_hook_calls = 0
if f1 < f2 then
evo.set(f1, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 1
end)
else
evo.set(f2, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 1
end)
end
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 1)
evo.destroy(e)
end
evo.remove(f1, evo.ON_INSERT)
evo.remove(f2, evo.ON_INSERT)
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 0)
evo.destroy(e)
end
if f1 < f2 then
evo.set(f1, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 2
end)
else
evo.set(f2, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 2
end)
end
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 2)
evo.destroy(e)
end
end
do
local f1, f2 = evo.id(2)
local insert_hook_calls = 0
if f1 > f2 then
evo.set(f1, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 1
end)
else
evo.set(f2, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 1
end)
end
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 1)
evo.destroy(e)
end
evo.remove(f1, evo.ON_INSERT)
evo.remove(f2, evo.ON_INSERT)
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 0)
evo.destroy(e)
end
if f1 > f2 then
evo.set(f1, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 2
end)
else
evo.set(f2, evo.ON_INSERT, function()
insert_hook_calls = insert_hook_calls + 2
end)
end
do
insert_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
assert(insert_hook_calls == 2)
evo.destroy(e)
end
end
do
local f1, f2 = evo.id(2)
local remove_hook_calls = 0
if f1 < f2 then
evo.set(f1, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 1
end)
else
evo.set(f2, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 1
end)
end
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 1)
end
evo.remove(f1, evo.ON_REMOVE)
evo.remove(f2, evo.ON_REMOVE)
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 0)
end
if f1 < f2 then
evo.set(f1, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 2
end)
else
evo.set(f2, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 2
end)
end
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 2)
end
end
do
local f1, f2 = evo.id(2)
local remove_hook_calls = 0
if f1 > f2 then
evo.set(f1, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 1
end)
else
evo.set(f2, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 1
end)
end
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 1)
end
evo.remove(f1, evo.ON_REMOVE)
evo.remove(f2, evo.ON_REMOVE)
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 0)
end
if f1 > f2 then
evo.set(f1, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 2
end)
else
evo.set(f2, evo.ON_REMOVE, function()
remove_hook_calls = remove_hook_calls + 2
end)
end
do
remove_hook_calls = 0
local e = evo.spawn { [f1] = 42, [f2] = 'hello' }
evo.destroy(e)
assert(remove_hook_calls == 2)
end
end

View File

@@ -2928,8 +2928,8 @@ do
last_insert_entity, last_insert_component = 0, 0
local e = evo.spawn({ [f2] = 21, [f1] = true })
assert(set_count == 2 and insert_count == 2)
assert(last_set_entity == e and last_set_component == 21)
assert(last_insert_entity == e and last_insert_component == 21)
assert(last_set_entity == e and (last_set_component == 21 or last_set_component == true))
assert(last_insert_entity == e and (last_insert_component == 21 or last_insert_component == true))
end
do
@@ -2948,8 +2948,8 @@ do
last_insert_entity, last_insert_component = 0, 0
local e = evo.spawn({ [f3] = 33, [f2] = 22 })
assert(set_count == 2 and insert_count == 2)
assert(last_set_entity == e and last_set_component == nil)
assert(last_insert_entity == e and last_insert_component == nil)
assert(last_set_entity == e and (last_set_component == nil or last_set_component == 22))
assert(last_insert_entity == e and (last_insert_component == nil or last_insert_component == 22))
end
end

View File

@@ -276,6 +276,7 @@
process_with: function(system: System, ...: any)
debug_mode: function(yesno: boolean)
error_handler: function(handler?: function(message: string): string)
collect_garbage: function(no_shrink?: boolean)
chunk: function(fragment: Fragment, ...: Fragment): Chunk, { Entity }, integer

View File

@@ -1,7 +1,7 @@
local evolved = {
__HOMEPAGE = 'https://github.com/BlackMATov/evolved.lua',
__DESCRIPTION = 'Evolved ECS (Entity-Component-System) for Lua',
__VERSION = '1.10.0',
__VERSION = '1.11.0',
__LICENSE = [[
MIT License
@@ -120,49 +120,6 @@ local evolved = {
---
---
local __debug_mode = false ---@type boolean
local __freelist_ids = {} ---@type integer[]
local __acquired_count = 0 ---@type integer
local __available_primary = 0 ---@type integer
local __defer_depth = 0 ---@type integer
local __defer_points = {} ---@type integer[]
local __defer_length = 0 ---@type integer
local __defer_bytecode = {} ---@type any[]
local __root_set = {} ---@type table<evolved.fragment, integer>
local __root_list = {} ---@type evolved.chunk[]
local __root_count = 0 ---@type integer
local __major_chunks = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.chunk>>
local __minor_chunks = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.chunk>>
local __query_chunks = {} ---@type table<evolved.query, evolved.assoc_list<evolved.chunk>>
local __major_queries = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.query>>
local __entity_chunks = {} ---@type (evolved.chunk|false)[]
local __entity_places = {} ---@type integer[]
local __named_entity = {} ---@type table<string, evolved.entity>
local __named_entities = {} ---@type table<string, evolved.assoc_list<evolved.entity>>
local __sorted_includes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_variants = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_requires = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.fragment>>
local __subsystem_groups = {} ---@type table<evolved.system, evolved.system>
local __group_subsystems = {} ---@type table<evolved.system, evolved.assoc_list<evolved.system>>
local __structural_changes = 0 ---@type integer
---
---
---
---
---
---@class evolved.chunk
---@field package __parent? evolved.chunk
---@field package __child_set table<evolved.chunk, integer>
@@ -202,6 +159,12 @@ local __structural_changes = 0 ---@type integer
---@field package __has_internal_minors boolean
---@field package __has_internal_fragments boolean
---@field package __has_required_fragments boolean
---@field package __insert_fragment_list? evolved.fragment[]
---@field package __insert_fragment_count integer
---@field package __remove_fragment_list? evolved.fragment[]
---@field package __remove_fragment_count integer
---@field package __explicit_fragment_list? evolved.fragment[]
---@field package __explicit_fragment_count integer
local __chunk_mt = {}
__chunk_mt.__index = __chunk_mt
@@ -642,6 +605,50 @@ end
---
---
local __debug_mode = false ---@type boolean
local __error_handler = __lua_debug_traceback ---@type fun(message: string): string
local __freelist_ids = {} ---@type integer[]
local __acquired_count = 0 ---@type integer
local __available_primary = 0 ---@type integer
local __defer_depth = 0 ---@type integer
local __defer_points = {} ---@type integer[]
local __defer_length = 0 ---@type integer
local __defer_bytecode = {} ---@type any[]
local __root_set = {} ---@type table<evolved.fragment, integer>
local __root_list = {} ---@type evolved.chunk[]
local __root_count = 0 ---@type integer
local __major_chunks = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.chunk>>
local __minor_chunks = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.chunk>>
local __query_chunks = {} ---@type table<evolved.query, evolved.assoc_list<evolved.chunk>>
local __major_queries = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.query>>
local __entity_chunks = {} ---@type (evolved.chunk|false)[]
local __entity_places = {} ---@type integer[]
local __named_entity = {} ---@type table<string, evolved.entity>
local __named_entities = {} ---@type table<string, evolved.assoc_list<evolved.entity>>
local __sorted_includes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_variants = {} ---@type table<evolved.query, evolved.assoc_list<evolved.fragment>>
local __sorted_requires = {} ---@type table<evolved.fragment, evolved.assoc_list<evolved.fragment>>
local __subsystem_groups = {} ---@type table<evolved.system, evolved.system>
local __group_subsystems = {} ---@type table<evolved.system, evolved.assoc_list<evolved.system>>
local __structural_changes = 0 ---@type integer
---
---
---
---
---
---@return evolved.id
---@nodiscard
local function __acquire_id()
@@ -819,12 +826,13 @@ end
---@param list V[]
---@param size? integer
---@return V[]
---@return integer dup_list_size
---@nodiscard
function __list_fns.dup(list, size)
local list_size = size or #list
if list_size == 0 then
return {}
return {}, 0
end
local dup_list = __list_fns.new(list_size)
@@ -833,7 +841,7 @@ function __list_fns.dup(list, size)
list, 1, list_size,
1, dup_list)
return dup_list
return dup_list, list_size
end
---@generic V
@@ -1226,6 +1234,7 @@ local __evolved_process
local __evolved_process_with
local __evolved_debug_mode
local __evolved_error_handler
local __evolved_collect_garbage
local __evolved_chunk
@@ -1408,6 +1417,12 @@ function __new_chunk(chunk_parent, chunk_fragment)
__has_internal_minors = false,
__has_internal_fragments = false,
__has_required_fragments = false,
__insert_fragment_list = nil,
__insert_fragment_count = 0,
__remove_fragment_list = nil,
__remove_fragment_count = 0,
__explicit_fragment_list = nil,
__explicit_fragment_count = 0,
}, __chunk_mt)
if not chunk_parent then
@@ -1590,17 +1605,22 @@ function __update_chunk_caches(chunk)
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local has_setup_hooks = chunk_parent ~= nil and chunk_parent.__has_setup_hooks
or __evolved_has_any(chunk_fragment, __DEFAULT, __DUPLICATE)
local has_setup_major = __evolved_has_any(chunk_fragment, __DEFAULT, __DUPLICATE)
local has_setup_minors = chunk_parent ~= nil and chunk_parent.__has_setup_hooks
local has_assign_hooks = chunk_parent ~= nil and chunk_parent.__has_assign_hooks
or __evolved_has_any(chunk_fragment, __ON_SET, __ON_ASSIGN)
local has_assign_major = __evolved_has_any(chunk_fragment, __ON_SET, __ON_ASSIGN)
local has_assign_minors = chunk_parent ~= nil and chunk_parent.__has_assign_hooks
local has_insert_hooks = chunk_parent ~= nil and chunk_parent.__has_insert_hooks
or __evolved_has_any(chunk_fragment, __ON_SET, __ON_INSERT)
local has_insert_major = __evolved_has_any(chunk_fragment, __ON_SET, __ON_INSERT)
local has_insert_minors = chunk_parent ~= nil and chunk_parent.__has_insert_hooks
local has_remove_hooks = chunk_parent ~= nil and chunk_parent.__has_remove_hooks
or __evolved_has(chunk_fragment, __ON_REMOVE)
local has_remove_major = __evolved_has(chunk_fragment, __ON_REMOVE)
local has_remove_minors = chunk_parent ~= nil and chunk_parent.__has_remove_hooks
local has_setup_hooks = has_setup_major or has_setup_minors
local has_assign_hooks = has_assign_major or has_assign_minors
local has_insert_hooks = has_insert_major or has_insert_minors
local has_remove_hooks = has_remove_major or has_remove_minors
local has_unique_major = __evolved_has(chunk_fragment, __UNIQUE)
local has_unique_minors = chunk_parent ~= nil and chunk_parent.__has_unique_fragments
@@ -1667,6 +1687,75 @@ function __update_chunk_caches(chunk)
else
chunk.__without_unique_fragments = chunk
end
if has_insert_hooks then
local parent_insert_fragment_list = chunk_parent and chunk_parent.__insert_fragment_list
local parent_insert_fragment_count = chunk_parent and chunk_parent.__insert_fragment_count or 0
if has_insert_major then
local insert_fragment_list, insert_fragment_count = parent_insert_fragment_list
and __list_fns.dup(parent_insert_fragment_list, parent_insert_fragment_count)
or __list_fns.new(1), 0
insert_fragment_count = parent_insert_fragment_count + 1
insert_fragment_list[insert_fragment_count] = chunk_fragment
chunk.__insert_fragment_list = insert_fragment_list
chunk.__insert_fragment_count = insert_fragment_count
else
chunk.__insert_fragment_list = parent_insert_fragment_list
chunk.__insert_fragment_count = parent_insert_fragment_count
end
else
chunk.__insert_fragment_list = nil
chunk.__insert_fragment_count = 0
end
if has_remove_hooks then
local parent_remove_fragment_list = chunk_parent and chunk_parent.__remove_fragment_list
local parent_remove_fragment_count = chunk_parent and chunk_parent.__remove_fragment_count or 0
if has_remove_major then
local remove_fragment_list, remove_fragment_count = parent_remove_fragment_list
and __list_fns.dup(parent_remove_fragment_list, parent_remove_fragment_count)
or __list_fns.new(1), 0
remove_fragment_count = parent_remove_fragment_count + 1
remove_fragment_list[remove_fragment_count] = chunk_fragment
chunk.__remove_fragment_list = remove_fragment_list
chunk.__remove_fragment_count = remove_fragment_count
else
chunk.__remove_fragment_list = parent_remove_fragment_list
chunk.__remove_fragment_count = parent_remove_fragment_count
end
else
chunk.__remove_fragment_list = nil
chunk.__remove_fragment_count = 0
end
if has_explicit_fragments then
local parent_explicit_fragment_list = chunk_parent and chunk_parent.__explicit_fragment_list
local parent_explicit_fragment_count = chunk_parent and chunk_parent.__explicit_fragment_count or 0
if has_explicit_major then
local explicit_fragment_list, explicit_fragment_count = parent_explicit_fragment_list
and __list_fns.dup(parent_explicit_fragment_list, parent_explicit_fragment_count)
or __list_fns.new(1), 0
explicit_fragment_count = parent_explicit_fragment_count + 1
explicit_fragment_list[explicit_fragment_count] = chunk_fragment
chunk.__explicit_fragment_list = explicit_fragment_list
chunk.__explicit_fragment_count = explicit_fragment_count
else
chunk.__explicit_fragment_list = parent_explicit_fragment_list
chunk.__explicit_fragment_count = parent_explicit_fragment_count
end
else
chunk.__explicit_fragment_list = nil
chunk.__explicit_fragment_count = 0
end
end
---@param chunk evolved.chunk
@@ -2063,17 +2152,15 @@ function __query_minor_matches(chunk, query)
end
end
if chunk.__has_explicit_fragments then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_explicit_fragment_list = chunk.__explicit_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local chunk_fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_explicit_fragment_list then
for chunk_explicit_fragment_index = 1, chunk.__explicit_fragment_count do
local fragment = chunk_explicit_fragment_list[chunk_explicit_fragment_index]
local is_chunk_fragment_matched =
(not __evolved_has(chunk_fragment, __EXPLICIT)) or
(query_variant_count > 0 and query_variant_set[chunk_fragment]) or
(query_include_count > 0 and query_include_set[chunk_fragment])
(query_variant_count > 0 and query_variant_set[fragment]) or
(query_include_count > 0 and query_include_set[fragment])
if not is_chunk_fragment_matched then
return false
@@ -2639,12 +2726,11 @@ function __spawn_entity(chunk, entity, component_table, component_mapper)
component_mapper(chunk, place, place)
end
if chunk.__has_insert_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_insert_fragment_list = chunk.__insert_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_insert_fragment_list then
for chunk_insert_fragment_index = 1, chunk.__insert_fragment_count do
local fragment = chunk_insert_fragment_list[chunk_insert_fragment_index]
---@type evolved.set_hook?, evolved.insert_hook?
local fragment_on_set, fragment_on_insert =
@@ -2778,12 +2864,11 @@ function __multi_spawn_entity(chunk, entity_list, entity_first, entity_count, co
component_mapper(chunk, b_place, e_place)
end
if chunk.__has_insert_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_insert_fragment_list = chunk.__insert_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_insert_fragment_list then
for chunk_insert_fragment_index = 1, chunk.__insert_fragment_count do
local fragment = chunk_insert_fragment_list[chunk_insert_fragment_index]
---@type evolved.set_hook?, evolved.insert_hook?
local fragment_on_set, fragment_on_insert =
@@ -2939,12 +3024,11 @@ function __clone_entity(prefab, entity, component_table, component_mapper)
component_mapper(chunk, place, place)
end
if chunk.__has_insert_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_insert_fragment_list = chunk.__insert_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_insert_fragment_list then
for chunk_insert_fragment_index = 1, chunk.__insert_fragment_count do
local fragment = chunk_insert_fragment_list[chunk_insert_fragment_index]
---@type evolved.set_hook?, evolved.insert_hook?
local fragment_on_set, fragment_on_insert =
@@ -3107,12 +3191,11 @@ function __multi_clone_entity(prefab, entity_list, entity_first, entity_count, c
component_mapper(chunk, b_place, e_place)
end
if chunk.__has_insert_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_insert_fragment_list = chunk.__insert_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_insert_fragment_list then
for chunk_insert_fragment_index = 1, chunk.__insert_fragment_count do
local fragment = chunk_insert_fragment_list[chunk_insert_fragment_index]
---@type evolved.set_hook?, evolved.insert_hook?
local fragment_on_set, fragment_on_insert =
@@ -3377,27 +3460,29 @@ function __clear_entity_one(entity)
local chunk = entity_chunks[entity_primary]
local place = entity_places[entity_primary]
if chunk and chunk.__has_remove_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_component_indices = chunk.__component_indices
local chunk_component_storages = chunk.__component_storages
if chunk then
local chunk_remove_fragment_list = chunk.__remove_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_remove_fragment_list then
local chunk_component_indices = chunk.__component_indices
local chunk_component_storages = chunk.__component_storages
---@type evolved.remove_hook?
local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE)
for chunk_remove_fragment_index = 1, chunk.__remove_fragment_count do
local fragment = chunk_remove_fragment_list[chunk_remove_fragment_index]
if fragment_on_remove then
local component_index = chunk_component_indices[fragment]
---@type evolved.remove_hook?
local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE)
if component_index then
local component_storage = chunk_component_storages[component_index]
local old_component = component_storage[place]
fragment_on_remove(entity, fragment, old_component)
else
fragment_on_remove(entity, fragment)
if fragment_on_remove then
local component_index = chunk_component_indices[fragment]
if component_index then
local component_storage = chunk_component_storages[component_index]
local old_component = component_storage[place]
fragment_on_remove(entity, fragment, old_component)
else
fragment_on_remove(entity, fragment)
end
end
end
end
@@ -3444,27 +3529,29 @@ function __destroy_entity_one(entity)
local chunk = entity_chunks[entity_primary]
local place = entity_places[entity_primary]
if chunk and chunk.__has_remove_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_component_indices = chunk.__component_indices
local chunk_component_storages = chunk.__component_storages
if chunk then
local chunk_remove_fragment_list = chunk.__remove_fragment_list
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
if chunk_remove_fragment_list then
local chunk_component_indices = chunk.__component_indices
local chunk_component_storages = chunk.__component_storages
---@type evolved.remove_hook?
local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE)
for chunk_remove_fragment_index = 1, chunk.__remove_fragment_count do
local fragment = chunk_remove_fragment_list[chunk_remove_fragment_index]
if fragment_on_remove then
local component_index = chunk_component_indices[fragment]
---@type evolved.remove_hook?
local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE)
if component_index then
local component_storage = chunk_component_storages[component_index]
local old_component = component_storage[place]
fragment_on_remove(entity, fragment, old_component)
else
fragment_on_remove(entity, fragment)
if fragment_on_remove then
local component_index = chunk_component_indices[fragment]
if component_index then
local component_storage = chunk_component_storages[component_index]
local old_component = component_storage[place]
fragment_on_remove(entity, fragment, old_component)
else
fragment_on_remove(entity, fragment)
end
end
end
end
@@ -4060,8 +4147,6 @@ function __chunk_remove(old_chunk, ...)
local old_entity_list = old_chunk.__entity_list
local old_entity_count = old_chunk.__entity_count
local old_fragment_list = old_chunk.__fragment_list
local old_fragment_count = old_chunk.__fragment_count
local old_component_indices = old_chunk.__component_indices
local old_component_storages = old_chunk.__component_storages
@@ -4075,12 +4160,14 @@ function __chunk_remove(old_chunk, ...)
return
end
if old_chunk.__has_remove_hooks then
local old_remove_fragment_list = old_chunk.__remove_fragment_list
if old_remove_fragment_list then
local new_fragment_set = new_chunk and new_chunk.__fragment_set
or __safe_tbls.__EMPTY_FRAGMENT_SET
for old_fragment_index = 1, old_fragment_count do
local fragment = old_fragment_list[old_fragment_index]
for old_remove_fragment_index = 1, old_chunk.__remove_fragment_count do
local fragment = old_remove_fragment_list[old_remove_fragment_index]
if not new_fragment_set[fragment] then
---@type evolved.remove_hook?
@@ -4209,14 +4296,14 @@ function __chunk_clear(chunk)
return
end
if chunk.__has_remove_hooks then
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
local chunk_remove_fragment_list = chunk.__remove_fragment_list
if chunk_remove_fragment_list then
local chunk_component_indices = chunk.__component_indices
local chunk_component_storages = chunk.__component_storages
for chunk_fragment_index = 1, chunk_fragment_count do
local fragment = chunk_fragment_list[chunk_fragment_index]
for chunk_remove_fragment_index = 1, chunk.__remove_fragment_count do
local fragment = chunk_remove_fragment_list[chunk_remove_fragment_index]
---@type evolved.remove_hook?
local fragment_on_remove = __evolved_get(fragment, __ON_REMOVE)
@@ -4683,7 +4770,7 @@ local function __system_process(system, ...)
__QUERY, __EXECUTE, __PROLOGUE, __EPILOGUE)
if prologue then
local success, result = __lua_xpcall(prologue, __lua_debug_traceback, ...)
local success, result = __lua_xpcall(prologue, __error_handler, ...)
if not success then
__error_fmt('system prologue failed: %s', result)
@@ -4693,7 +4780,7 @@ local function __system_process(system, ...)
if execute then
__evolved_defer()
do
local success, result = __lua_xpcall(__query_execute, __lua_debug_traceback, query or system, execute, ...)
local success, result = __lua_xpcall(__query_execute, __error_handler, query or system, execute, ...)
if not success then
__evolved_cancel()
@@ -4729,7 +4816,7 @@ local function __system_process(system, ...)
end
if epilogue then
local success, result = __lua_xpcall(epilogue, __lua_debug_traceback, ...)
local success, result = __lua_xpcall(epilogue, __error_handler, ...)
if not success then
__error_fmt('system epilogue failed: %s', result)
@@ -5610,17 +5697,17 @@ function __evolved_remove(entity, ...)
__evolved_defer()
if old_chunk and old_chunk ~= new_chunk then
local old_fragment_list = old_chunk.__fragment_list
local old_fragment_count = old_chunk.__fragment_count
local old_component_indices = old_chunk.__component_indices
local old_component_storages = old_chunk.__component_storages
if old_chunk.__has_remove_hooks then
local old_remove_fragment_list = old_chunk.__remove_fragment_list
if old_remove_fragment_list then
local new_fragment_set = new_chunk and new_chunk.__fragment_set
or __safe_tbls.__EMPTY_FRAGMENT_SET
for old_fragment_index = 1, old_fragment_count do
local fragment = old_fragment_list[old_fragment_index]
for old_remove_fragment_index = 1, old_chunk.__remove_fragment_count do
local fragment = old_remove_fragment_list[old_remove_fragment_index]
if not new_fragment_set[fragment] then
---@type evolved.remove_hook?
@@ -6328,6 +6415,11 @@ function __evolved_debug_mode(yesno)
__debug_mode = yesno
end
---@param handler? fun(message: string): string
function __evolved_error_handler(handler)
__error_handler = handler or __lua_debug_traceback
end
---@param no_shrink boolean?
function __evolved_collect_garbage(no_shrink)
if __defer_depth > 0 then
@@ -7516,8 +7608,10 @@ end)
---
---
local __query_hook_fns = {}
---@param query evolved.query
local function __insert_query(query)
function __query_hook_fns.insert_query(query)
local query_includes = __sorted_includes[query]
local query_include_list = query_includes and query_includes.__item_list
local query_include_count = query_includes and query_includes.__item_count or 0
@@ -7557,7 +7651,7 @@ local function __insert_query(query)
end
---@param query evolved.query
local function __remove_query(query)
function __query_hook_fns.remove_query(query)
local query_includes = __sorted_includes[query]
local query_include_list = query_includes and query_includes.__item_list
local query_include_count = query_includes and query_includes.__item_count or 0
@@ -7599,7 +7693,7 @@ end
---@param query evolved.query
---@param include_list evolved.fragment[]
__evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list)
__remove_query(query)
__query_hook_fns.remove_query(query)
local include_count = #include_list
@@ -7615,16 +7709,16 @@ __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list)
__sorted_includes[query] = nil
end
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
__evolved_set(__INCLUDES, __ON_REMOVE, function(query)
__remove_query(query)
__query_hook_fns.remove_query(query)
__sorted_includes[query] = nil
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
@@ -7637,7 +7731,7 @@ end)
---@param query evolved.query
---@param exclude_list evolved.fragment[]
__evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list)
__remove_query(query)
__query_hook_fns.remove_query(query)
local exclude_count = #exclude_list
@@ -7653,16 +7747,16 @@ __evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list)
__sorted_excludes[query] = nil
end
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
__evolved_set(__EXCLUDES, __ON_REMOVE, function(query)
__remove_query(query)
__query_hook_fns.remove_query(query)
__sorted_excludes[query] = nil
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
@@ -7675,7 +7769,7 @@ end)
---@param query evolved.query
---@param variant_list evolved.fragment[]
__evolved_set(__VARIANTS, __ON_SET, function(query, _, variant_list)
__remove_query(query)
__query_hook_fns.remove_query(query)
local variant_count = #variant_list
@@ -7691,16 +7785,16 @@ __evolved_set(__VARIANTS, __ON_SET, function(query, _, variant_list)
__sorted_variants[query] = nil
end
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
__evolved_set(__VARIANTS, __ON_REMOVE, function(query)
__remove_query(query)
__query_hook_fns.remove_query(query)
__sorted_variants[query] = nil
__insert_query(query)
__query_hook_fns.insert_query(query)
__update_major_chunks(query)
end)
@@ -7741,8 +7835,10 @@ end)
---
---
local __group_hook_fns = {}
---@param subsystem evolved.system
local function __add_subsystem(subsystem)
function __group_hook_fns.add_subsystem(subsystem)
local subsystem_group = __subsystem_groups[subsystem]
if subsystem_group then
@@ -7759,7 +7855,7 @@ local function __add_subsystem(subsystem)
end
---@param subsystem evolved.system
local function __remove_subsystem(subsystem)
function __group_hook_fns.remove_subsystem(subsystem)
local subsystem_group = __subsystem_groups[subsystem]
if subsystem_group then
@@ -7771,23 +7867,29 @@ local function __remove_subsystem(subsystem)
end
end
---
---
---
---
---
---@param system evolved.system
__evolved_set(__GROUP, __ON_SET, function(system, _, group)
__remove_subsystem(system)
__group_hook_fns.remove_subsystem(system)
__subsystem_groups[system] = group
__add_subsystem(system)
__group_hook_fns.add_subsystem(system)
__update_major_chunks(system)
end)
---@param system evolved.system
__evolved_set(__GROUP, __ON_REMOVE, function(system)
__remove_subsystem(system)
__group_hook_fns.remove_subsystem(system)
__subsystem_groups[system] = nil
__add_subsystem(system)
__group_hook_fns.add_subsystem(system)
__update_major_chunks(system)
end)
@@ -7899,6 +8001,7 @@ evolved.process = __evolved_process
evolved.process_with = __evolved_process_with
evolved.debug_mode = __evolved_debug_mode
evolved.error_handler = __evolved_error_handler
evolved.collect_garbage = __evolved_collect_garbage
evolved.chunk = __evolved_chunk

View File

@@ -1,5 +1,12 @@
if os.getenv('LOCAL_LUA_DEBUGGER_VSCODE') == '1' then
require('lldebugger').start()
local debugger = require 'lldebugger'
debugger.start()
local evolved = require 'evolved'
evolved.error_handler(function(err)
debugger.requestBreak()
return debug.traceback(err)
end)
end
---@type love.conf

View File

@@ -0,0 +1,34 @@
rockspec_format = "3.0"
package = "evolved.lua"
version = "1.11.0-0"
source = {
url = "git://github.com/BlackMATov/evolved.lua",
tag = "v1.11.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",
}
}