20 Commits

Author SHA1 Message Date
6bf13890ef Merge pull request #14 from BlackMATov/dev
Dev
2025-06-06 21:28:17 +07:00
BlackMATov
9a5ea20778 v1.1.0 2025-06-06 21:25:08 +07:00
BlackMATov
0112d0747a Merge branch 'feature/requires_fragment' into dev 2025-06-06 21:21:59 +07:00
BlackMATov
c222a49257 fix the cloning with requires behavior for removed required fragments 2025-06-06 21:20:38 +07:00
BlackMATov
fbd9f9f970 additional requries-fragment tests 2025-06-06 18:32:56 +07:00
BlackMATov
d8c9481f13 Merge branch 'dev' into feature/requires_fragment 2025-06-05 14:46:47 +07:00
BlackMATov
47fcc9cc13 additional requries-fragment test 2025-06-05 14:46:16 +07:00
BlackMATov
cb98a4e461 update README 2025-05-31 07:14:03 +07:00
BlackMATov
a559c73c35 update README to reflect the new REQUIRES trait feature 2025-05-30 03:41:42 +07:00
BlackMATov
ba5ee22f5d little improvements REQUIRES fragments 2025-05-30 01:26:56 +07:00
BlackMATov
50afb722d1 proof of concept REQUIRES fragment impl 2025-05-29 17:08:40 +07:00
BlackMATov
8eccd461fd add pack/unpack fuzz test 2025-05-28 23:34:14 +07:00
BlackMATov
c323131d1e add pack/unpack fuzz test 2025-05-28 23:33:57 +07:00
BlackMATov
8038031b51 has_required_fragments chunk flag 2025-05-28 22:44:18 +07:00
BlackMATov
1399820d71 new REQUIRES dummy fragment 2025-05-28 15:06:44 +07:00
BlackMATov
3a64bac8e2 systems can be queries themselves 2025-05-26 17:37:40 +07:00
BlackMATov
5ae0e77f20 update GUIDES 2025-05-25 22:30:45 +07:00
BlackMATov
e78c4e8d6b update README 2025-05-24 18:39:17 +07:00
db191b805f Merge pull request #13 from BlackMATov/dev
Dev
2025-05-23 20:07:43 +07:00
BlackMATov
9c6e53e610 update README 2025-05-23 19:57:54 +07:00
12 changed files with 1621 additions and 223 deletions

View File

@@ -4,5 +4,12 @@
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
},
"markdown.extension.toc.levels": "2..6",
"markdown.extension.toc.omittedFromToc": {
"README.md": [
"# Changelog",
"# API Reference"
]
}
}

22
GUIDES.md Normal file
View File

@@ -0,0 +1,22 @@
# Guidelines
## Checklists
### New Version Releasing
1. Ensure all tests pass on CI.
2. Update the version number in `evolved.lua`.
3. Update the **Changelog** section in `README.md`.
4. Create a new rockspec file in `rockspecs`.
5. Commit the changes with a message like `vX.Y.Z`.
6. Push and merge the changes to the `main` branch.
7. Create the release on GitHub.
8. Upload the new package to LuaRocks.
### Adding a New Top-Level Function
1. Insert the new function into the `evolved` table in `evolved.lua`.
2. Create tests for the function in `develop/testing/function_name_tests.lua`.
3. Add the new test to `develop/all.lua`.
4. Document the function in the **Cheat Sheet** and **API Reference** sections of `README.md`.
5. Provide a description in the **Overview** section of `README.md`.
6. Describe the update in the **Changelog** section of `README.md`.

510
README.md
View File

@@ -1,4 +1,4 @@
# evolved.lua (work in progress)
# evolved.lua
> Evolved ECS (Entity-Component-System) for Lua
@@ -19,6 +19,45 @@
[evolved]: https://github.com/BlackMATov/evolved.lua
- [Introduction](#introduction)
- [Performance](#performance)
- [Simplicity](#simplicity)
- [Flexibility](#flexibility)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Overview](#overview)
- [Identifiers](#identifiers)
- [Entities, Fragments, and Components](#entities-fragments-and-components)
- [Traits](#traits)
- [Singletons](#singletons)
- [Chunks](#chunks)
- [Structural Changes](#structural-changes)
- [Spawning Entities](#spawning-entities)
- [Entity Builders](#entity-builders)
- [Access Operations](#access-operations)
- [Modifying Operations](#modifying-operations)
- [Debug Mode](#debug-mode)
- [Queries](#queries)
- [Deferred Operations](#deferred-operations)
- [Batch Operations](#batch-operations)
- [Systems](#systems)
- [Predefined Traits](#predefined-traits)
- [Fragment Tags](#fragment-tags)
- [Fragment Hooks](#fragment-hooks)
- [Unique Fragments](#unique-fragments)
- [Explicit Fragments](#explicit-fragments)
- [Shared Components](#shared-components)
- [Fragment Requirements](#fragment-requirements)
- [Destruction Policies](#destruction-policies)
- [Cheat Sheet](#cheat-sheet)
- [Aliases](#aliases)
- [Predefs](#predefs)
- [Functions](#functions)
- [Classes](#classes)
- [Chunk](#chunk)
- [Builder](#builder)
- [License](#license)
## Introduction
`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. Before we start exploring the library, let's take a look at the main advantages of using `evolved.lua`:
@@ -41,211 +80,21 @@ And yes, the library has some unusual concepts at its core, but once you get the
On the other hand, `evolved.lua` tries to be minimalistic and does not provide features that can be implemented outside the library. I'm trying to find a balance between minimalism and the number of possibilities, which forces me to make flexible decisions in the library's design. I hope you will find this balance acceptable.
## Requirements
- [lua](https://www.lua.org/) **>= 5.1**
- [luajit](https://luajit.org/) **>= 2.0**
## Installation
You can install `evolved.lua` using [luarocks](https://luarocks.org/) with the following command:
`evolved.lua` is a single-file pure Lua library and does not require any external dependencies. It is designed to work with [Lua 5.1](https://www.lua.org/) and later, [LuaJIT](https://luajit.org/), and [Luau](https://luau.org/) (Roblox).
All you need to start using the library is the [evolved.lua](./evolved.lua) source file. You can download it from the [releases](https://github.com/BlackMATov/evolved.lua/releases) page or clone the [repository](https://github.com/BlackMATov/evolved.lua) and copy the file to your project.
If you are using [LuaRocks](https://luarocks.org/), you can install the library using the following command:
```bash
luarocks install evolved.lua
```
Or just clone the [repository](https://github.com/BlackMATov/evolved.lua) and copy the [evolved.lua](evolved.lua) file to your project.
## Quick Start
To start using `evolved.lua`, read the [Overview](#overview) section first. It will give you a basic understanding of how the library works and how to use it. After that, check the full-featured [Example](develop/example.lua), which demonstrates complex usage of the library. Finally, refer to the [Cheat Sheet](#cheat-sheet) for a quick reference of all the functions and classes provided by the library.
Enjoy! :suspect:
## Cheat Sheet
### Aliases
```
id :: implementation-specific
entity :: id
fragment :: id
query :: id
system :: id
component :: any
storage :: component[]
default :: component
duplicate :: {component -> component}
execute :: {chunk, entity[], integer}
prologue :: {}
epilogue :: {}
set_hook :: {entity, fragment, component, component?}
assign_hook :: {entity, fragment, component, component}
insert_hook :: {entity, fragment, component}
remove_hook :: {entity, fragment, component}
each_state :: implementation-specific
execute_state :: implementation-specific
each_iterator :: {each_state? -> fragment?, component?}
execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?}
```
### Predefs
```
TAG :: fragment
NAME :: fragment
UNIQUE :: fragment
EXPLICIT :: fragment
DEFAULT :: fragment
DUPLICATE :: fragment
PREFAB :: fragment
DISABLED :: fragment
INCLUDES :: fragment
EXCLUDES :: fragment
ON_SET :: fragment
ON_ASSIGN :: fragment
ON_INSERT :: fragment
ON_REMOVE :: fragment
GROUP :: fragment
QUERY :: fragment
EXECUTE :: fragment
PROLOGUE :: fragment
EPILOGUE :: fragment
DESTRUCTION_POLICY :: fragment
DESTRUCTION_POLICY_DESTROY_ENTITY :: id
DESTRUCTION_POLICY_REMOVE_FRAGMENT :: id
```
### Functions
```
id :: integer? -> id...
pack :: integer, integer -> id
unpack :: id -> integer, integer
defer :: boolean
commit :: boolean
spawn :: <fragment, component>? -> entity
clone :: entity -> <fragment, component>? -> entity
alive :: entity -> boolean
alive_all :: entity... -> boolean
alive_any :: entity... -> boolean
empty :: entity -> boolean
empty_all :: entity... -> boolean
empty_any :: entity... -> boolean
has :: entity, fragment -> boolean
has_all :: entity, fragment... -> boolean
has_any :: entity, fragment... -> boolean
get :: entity, fragment... -> component...
set :: entity, fragment, component -> ()
remove :: entity, fragment... -> ()
clear :: entity... -> ()
destroy :: entity... -> ()
batch_set :: query, fragment, component -> ()
batch_remove :: query, fragment... -> ()
batch_clear :: query... -> ()
batch_destroy :: query... -> ()
each :: entity -> {each_state? -> fragment?, component?}, each_state?
execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_state?
process :: system... -> ()
debug_mode :: boolean -> ()
collect_garbage :: ()
```
### Classes
#### Chunk
```
chunk :: fragment, fragment... -> chunk, entity[], integer
chunk_mt:alive :: boolean
chunk_mt:empty :: boolean
chunk_mt:has :: fragment -> boolean
chunk_mt:has_all :: fragment... -> boolean
chunk_mt:has_any :: fragment... -> boolean
chunk_mt:entities :: entity[], integer
chunk_mt:fragments :: fragment[], integer
chunk_mt:components :: fragment... -> storage...
```
#### Builder
```
builder :: builder
builder_mt:spawn :: entity
builder_mt:clone :: entity -> entity
builder_mt:has :: fragment -> boolean
builder_mt:has_all :: fragment... -> boolean
builder_mt:has_any :: fragment... -> boolean
builder_mt:get :: fragment... -> component...
builder_mt:set :: fragment, component -> builder
builder_mt:remove :: fragment... -> builder
builder_mt:clear :: builder
builder_mt:tag :: builder
builder_mt:name :: string -> builder
builder_mt:unique :: builder
builder_mt:explicit :: builder
builder_mt:default :: component -> builder
builder_mt:duplicate :: {component -> component} -> builder
builder_mt:prefab :: builder
builder_mt:disabled :: builder
builder_mt:include :: fragment... -> builder
builder_mt:exclude :: fragment... -> builder
builder_mt:on_set :: {entity, fragment, component, component?} -> builder
builder_mt:on_assign :: {entity, fragment, component, component} -> builder
builder_mt:on_insert :: {entity, fragment, component} -> builder
builder_mt:on_remove :: {entity, fragment} -> builder
builder_mt:group :: system -> builder
builder_mt:query :: query -> builder
builder_mt:execute :: {chunk, entity[], integer} -> builder
builder_mt:prologue :: {} -> builder
builder_mt:epilogue :: {} -> builder
builder_mt:destruction_policy :: id -> builder
```
To get started with `evolved.lua`, read the [Overview](#overview) section to understand the basic concepts and how to use the library. After that, check the [Example](develop/example.lua), which demonstrates complex usage of the library. Finally, refer to the [Cheat Sheet](#cheat-sheet) for a quick reference of all the functions and classes provided by the library.
## Overview
@@ -823,6 +672,28 @@ The [`evolved.process`](#evolvedprocess) function is used to process systems. It
function evolved.process(...) end
```
If you don't specify a query for the system, the system itself will be treated as a query. This means the system can contain `evolved.INCLUDES` and `evolved.EXCLUDES` fragments, and it will be processed according to them. This is useful for creating systems with unique queries that don't need to be reused in other systems.
```lua
local evolved = require 'evolved'
local health = evolved.id()
local system = evolved.builder()
:include(health)
:execute(function(chunk, entity_list, entity_count)
local health_components = chunk:components(health)
for i = 1, entity_count do
health_components[i] = math.max(
health_components[i] - 1,
0)
end
end):spawn()
evolved.process(system)
```
To group systems together, you can use the [`evolved.GROUP`](#evolvedgroup) fragment. Systems with a specified group will be processed when you call the [`evolved.process`](#evolvedprocess) function with this group. For example, you can group all physics systems together and process them in one [`evolved.process`](#evolvedprocess) call.
```lua
@@ -895,7 +766,7 @@ The prologue and epilogue fragments do not require an explicit query. They will
> [!NOTE]
> And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), which means that all modifying operations inside the callback will be queued and applied after the system has processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing.
### Advanced Topics
### Predefined Traits
#### Fragment Tags
@@ -1039,6 +910,35 @@ local enemy2 = evolved.builder()
assert(evolved.get(enemy1, position) ~= evolved.get(enemy2, position))
```
#### Fragment Requirements
Sometimes you want to add additional fragments to an entity when it receives a specific fragment. For example, you might want to add `position` and `velocity` fragments when an entity is given a `physical` fragment. This can be done using the [`evolved.REQUIRES`](#evolvedrequires) fragment trait. This trait expects a list of fragments that will be added to the entity when the fragment is inserted.
```lua
local evolved = require 'evolved'
local position = evolved.builder()
:default(vector2(0, 0))
:duplicate(vector2_duplicate)
:spawn()
local velocity = evolved.builder()
:default(vector2(0, 0))
:duplicate(vector2_duplicate)
:spawn()
local physical = evolved.builder()
:tag()
:require(position, velocity)
:spawn()
local enemy = evolved.builder()
:set(physical)
:spawn()
assert(evolved.has_all(enemy, position, velocity))
```
#### Destruction Policies
Typically, fragments remain alive for the entire lifetime of the program. However, in some cases, you might want to destroy fragments when they are no longer needed. For example, you can use some runtime entities as fragments for other entities. In this case, you might want to destroy such fragments even while they are still attached to other entities. Since entities cannot have destroyed fragments, a destruction policy must be applied to resolve this. By default, the library will remove the destroyed fragment from all entities that have it.
@@ -1086,6 +986,208 @@ evolved.destroy(world)
assert(not evolved.alive(entity))
```
## Cheat Sheet
### Aliases
```
id :: implementation-specific
entity :: id
fragment :: id
query :: id
system :: id
component :: any
storage :: component[]
default :: component
duplicate :: {component -> component}
execute :: {chunk, entity[], integer}
prologue :: {}
epilogue :: {}
set_hook :: {entity, fragment, component, component?}
assign_hook :: {entity, fragment, component, component}
insert_hook :: {entity, fragment, component}
remove_hook :: {entity, fragment, component}
each_state :: implementation-specific
execute_state :: implementation-specific
each_iterator :: {each_state? -> fragment?, component?}
execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?}
```
### Predefs
```
TAG :: fragment
NAME :: fragment
UNIQUE :: fragment
EXPLICIT :: fragment
DEFAULT :: fragment
DUPLICATE :: fragment
PREFAB :: fragment
DISABLED :: fragment
INCLUDES :: fragment
EXCLUDES :: fragment
REQUIRES :: fragment
ON_SET :: fragment
ON_ASSIGN :: fragment
ON_INSERT :: fragment
ON_REMOVE :: fragment
GROUP :: fragment
QUERY :: fragment
EXECUTE :: fragment
PROLOGUE :: fragment
EPILOGUE :: fragment
DESTRUCTION_POLICY :: fragment
DESTRUCTION_POLICY_DESTROY_ENTITY :: id
DESTRUCTION_POLICY_REMOVE_FRAGMENT :: id
```
### Functions
```
id :: integer? -> id...
pack :: integer, integer -> id
unpack :: id -> integer, integer
defer :: boolean
commit :: boolean
spawn :: <fragment, component>? -> entity
clone :: entity -> <fragment, component>? -> entity
alive :: entity -> boolean
alive_all :: entity... -> boolean
alive_any :: entity... -> boolean
empty :: entity -> boolean
empty_all :: entity... -> boolean
empty_any :: entity... -> boolean
has :: entity, fragment -> boolean
has_all :: entity, fragment... -> boolean
has_any :: entity, fragment... -> boolean
get :: entity, fragment... -> component...
set :: entity, fragment, component -> ()
remove :: entity, fragment... -> ()
clear :: entity... -> ()
destroy :: entity... -> ()
batch_set :: query, fragment, component -> ()
batch_remove :: query, fragment... -> ()
batch_clear :: query... -> ()
batch_destroy :: query... -> ()
each :: entity -> {each_state? -> fragment?, component?}, each_state?
execute :: query -> {execute_state? -> chunk?, entity[]?, integer?}, execute_state?
process :: system... -> ()
debug_mode :: boolean -> ()
collect_garbage :: ()
```
### Classes
#### Chunk
```
chunk :: fragment, fragment... -> chunk, entity[], integer
chunk_mt:alive :: boolean
chunk_mt:empty :: boolean
chunk_mt:has :: fragment -> boolean
chunk_mt:has_all :: fragment... -> boolean
chunk_mt:has_any :: fragment... -> boolean
chunk_mt:entities :: entity[], integer
chunk_mt:fragments :: fragment[], integer
chunk_mt:components :: fragment... -> storage...
```
#### Builder
```
builder :: builder
builder_mt:spawn :: entity
builder_mt:clone :: entity -> entity
builder_mt:has :: fragment -> boolean
builder_mt:has_all :: fragment... -> boolean
builder_mt:has_any :: fragment... -> boolean
builder_mt:get :: fragment... -> component...
builder_mt:set :: fragment, component -> builder
builder_mt:remove :: fragment... -> builder
builder_mt:clear :: builder
builder_mt:tag :: builder
builder_mt:name :: string -> builder
builder_mt:unique :: builder
builder_mt:explicit :: builder
builder_mt:default :: component -> builder
builder_mt:duplicate :: {component -> component} -> builder
builder_mt:prefab :: builder
builder_mt:disabled :: builder
builder_mt:include :: fragment... -> builder
builder_mt:exclude :: fragment... -> builder
builder_mt:require :: fragment... -> builder
builder_mt:on_set :: {entity, fragment, component, component?} -> builder
builder_mt:on_assign :: {entity, fragment, component, component} -> builder
builder_mt:on_insert :: {entity, fragment, component} -> builder
builder_mt:on_remove :: {entity, fragment} -> builder
builder_mt:group :: system -> builder
builder_mt:query :: query -> builder
builder_mt:execute :: {chunk, entity[], integer} -> builder
builder_mt:prologue :: {} -> builder
builder_mt:epilogue :: {} -> builder
builder_mt:destruction_policy :: id -> builder
```
## License
`evolved.lua` is licensed under the [MIT License][license]. For more details, see the [LICENSE.md](./LICENSE.md) file in the repository.
# Changelog
## v1.1.0
- [`Systems`](#systems) can be queries themselves now
- Added the new [`evolved.REQUIRES`](#evolvedrequires) fragment trait
## v1.0.0
- Initial release
# API Reference
## Predefs
@@ -1110,6 +1212,8 @@ assert(not evolved.alive(entity))
### `evolved.EXCLUDES`
### `evolved.REQUIRES`
### `evolved.ON_SET`
### `evolved.ON_ASSIGN`
@@ -1638,6 +1742,14 @@ function evolved.builder_mt:include(...) end
function evolved.builder_mt:exclude(...) end
```
### `evolved.builder_mt:require`
```lua
---@param ... evolved.fragment fragments
---@return evolved.builder builder
function evolved.builder_mt:require(...) end
```
#### `evolved.builder_mt:on_set`
```lua
@@ -1717,5 +1829,3 @@ function evolved.builder_mt:epilogue(epilogue) end
---@return evolved.builder builder
function evolved.builder_mt:destruction_policy(destruction_policy) end
```
## [License (MIT)](./LICENSE.md)

View File

@@ -7,6 +7,5 @@
- cached queries
- observers and events
- add INDEX fragment trait
- add REQUIRES fragment trait
- use compact prefix-tree for chunks
- optional ffi component storages

View File

@@ -1,6 +1,10 @@
require 'develop.example'
require 'develop.unbench'
require 'develop.untests'
require 'develop.testing.requires_fragment_tests'
require 'develop.testing.system_as_query_tests'
require 'develop.unbench'
require 'develop.usbench'
local basics = require 'develop.basics'
@@ -12,4 +16,8 @@ basics.describe_fuzz 'develop.fuzzing.batch_destroy_fuzz'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.explicit_fuzz'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.pack_unpack_fuzz'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.requires_fuzz'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.unique_fuzz'

View File

@@ -0,0 +1,20 @@
local evo = require 'evolved'
evo.debug_mode(true)
---
---
---
---
---
for _ = 1, 1000 do
local initial_index = math.random(1, 0xFFFFF)
local initial_version = math.random(1, 0xFFFFF)
local packed_id = evo.pack(initial_index, initial_version)
local unpacked_index, unpacked_version = evo.unpack(packed_id)
assert(initial_index == unpacked_index)
assert(initial_version == unpacked_version)
end

View File

@@ -0,0 +1,113 @@
local evo = require 'evolved'
evo.debug_mode(true)
---
---
---
---
---
local __table_unpack = (function()
---@diagnostic disable-next-line: deprecated
return table.unpack or unpack
end)()
---
---
---
---
---
local all_fragment_list = {} ---@type evolved.fragment[]
for i = 1, math.random(1, 10) do
local fragment = evo.builder()
:default(42)
:spawn()
all_fragment_list[i] = fragment
end
for _, fragment in ipairs(all_fragment_list) do
if math.random(1, 2) == 1 then
for _ = 0, math.random(0, #all_fragment_list) do
local require_list = evo.get(fragment, evo.REQUIRES) or {}
require_list[#require_list + 1] = all_fragment_list[math.random(1, #all_fragment_list)]
evo.set(fragment, evo.REQUIRES, require_list)
end
end
end
local all_entity_list = {} ---@type evolved.entity[]
for i = 1, math.random(1, 10) do
local entity = evo.id()
all_entity_list[i] = entity
for _ = 0, math.random(0, #all_fragment_list) do
local fragment = all_fragment_list[math.random(1, #all_fragment_list)]
if math.random(1, 2) == 1 then
evo.set(entity, fragment, 42)
else
local query = evo.builder()
:include(all_fragment_list[math.random(1, #all_fragment_list)])
:spawn()
evo.batch_set(query, fragment, 42)
evo.destroy(query)
end
end
end
for _ = 1, math.random(1, #all_entity_list) do
local components = {}
for _ = 1, math.random(1, #all_fragment_list) do
local fragment = all_fragment_list[math.random(1, #all_fragment_list)]
components[fragment] = 42
end
all_entity_list[#all_entity_list + 1] = evo.spawn(components)
end
for _ = 1, math.random(1, #all_entity_list) do
local prefab = all_entity_list[math.random(1, #all_entity_list)]
all_entity_list[#all_entity_list + 1] = evo.clone(prefab)
end
---
---
---
---
---
local function collect_required_fragments_for(fragment, req_fragment_set, req_fragment_list)
local fragment_requires = evo.get(fragment, evo.REQUIRES) or {}
for _, required_fragment in ipairs(fragment_requires) do
if not req_fragment_set[required_fragment] then
req_fragment_set[required_fragment] = true
req_fragment_list[#req_fragment_list + 1] = required_fragment
collect_required_fragments_for(required_fragment, req_fragment_set, req_fragment_list)
end
end
end
for _, entity in ipairs(all_entity_list) do
for fragment in evo.each(entity) do
local req_fragment_list = {}
collect_required_fragments_for(fragment, {}, req_fragment_list)
for _, required_fragment in ipairs(req_fragment_list) do
assert(evo.has(entity, required_fragment))
local required_component = evo.get(entity, required_fragment)
assert(required_component == 42)
end
end
end
---
---
---
---
---
evo.destroy(__table_unpack(all_entity_list))
evo.destroy(__table_unpack(all_fragment_list))
evo.collect_garbage()

View File

@@ -0,0 +1,245 @@
local evo = require 'evolved'
do
local f1, f2 = evo.id(2)
evo.set(f1, evo.REQUIRES)
evo.set(f2, evo.REQUIRES, evo.get(f1, evo.REQUIRES))
local f1_rs = evo.get(f1, evo.REQUIRES)
local f2_rs = evo.get(f2, evo.REQUIRES)
assert(f1_rs and f2_rs and #f1_rs == 0 and #f2_rs == 0 and f1_rs ~= f2_rs)
end
do
local f1, f2 = evo.id(2)
local f3 = evo.builder():require(f1):require(f2):spawn()
local f3_rs = evo.get(f3, evo.REQUIRES)
assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2)
end
do
local f1, f2 = evo.id(2)
local f3 = evo.builder():require(f1, f2):spawn()
local f3_rs = evo.get(f3, evo.REQUIRES)
assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2)
end
do
local f1, f2, f3 = evo.id(3)
evo.set(f1, evo.REQUIRES, { f2 })
evo.set(f3, evo.REQUIRES, { f1, f2 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
end
do
local e = evo.builder():set(f1):spawn()
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
end
do
local e = evo.spawn { [f1] = true }
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
evo.remove(e, f2)
assert(not evo.has(e, f2))
local e2 = evo.clone(e)
assert(evo.has(e2, f2))
assert(evo.get(e2, f2) == true)
local e3 = evo.clone(e, { [f3] = true })
assert(evo.has(e3, f2))
assert(evo.get(e3, f2) == true)
end
do
local f0 = evo.id()
local q0 = evo.builder():include(f0):spawn()
local e1 = evo.builder():set(f0):spawn()
local e2 = evo.builder():set(f0):spawn()
local e3 = evo.builder():set(f0):set(f2, false):spawn()
evo.batch_set(q0, f1)
assert(evo.has(e1, f2) and evo.get(e1, f2) == true)
assert(evo.has(e2, f2) and evo.get(e2, f2) == true)
assert(evo.has(e3, f2) and evo.get(e3, f2) == false)
end
end
do
local f1, f2, f3 = evo.id(3)
evo.set(f1, evo.REQUIRES, { f2 })
evo.set(f2, evo.DEFAULT, 42)
evo.set(f3, evo.REQUIRES, { f1, f2 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2))
assert(evo.get(e, f2) == 42)
end
do
local e = evo.builder():set(f1):spawn()
assert(evo.has(e, f2))
assert(evo.get(e, f2) == 42)
end
do
local e = evo.spawn { [f1] = true, }
assert(evo.has(e, f2))
assert(evo.get(e, f2) == 42)
evo.remove(e, f2)
assert(not evo.has(e, f2))
local e2 = evo.clone(e)
assert(evo.has(e2, f2))
assert(evo.get(e2, f2) == 42)
local e3 = evo.clone(e, { [f3] = true })
assert(evo.has(e3, f2))
assert(evo.get(e3, f2) == 42)
end
do
local f0 = evo.id()
local q0 = evo.builder():include(f0):spawn()
local e1 = evo.builder():set(f0):spawn()
local e2 = evo.builder():set(f0):spawn()
local e3 = evo.builder():set(f0):set(f2, 21):spawn()
evo.batch_set(q0, f1)
assert(evo.has(e1, f2) and evo.get(e1, f2) == 42)
assert(evo.has(e2, f2) and evo.get(e2, f2) == 42)
assert(evo.has(e3, f2) and evo.get(e3, f2) == 21)
end
end
do
local f1, f2, f3 = evo.id(3)
evo.set(f1, evo.REQUIRES, { f2 })
evo.set(f2, evo.REQUIRES, { f3 })
evo.set(f3, evo.DEFAULT, 42)
evo.set(f3, evo.REQUIRES, { f1, f2 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
assert(evo.has(e, f3))
assert(evo.get(e, f3) == 42)
end
do
local e = evo.builder():set(f1):spawn()
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
assert(evo.has(e, f3))
assert(evo.get(e, f3) == 42)
end
do
local e = evo.spawn { [f1] = true }
assert(evo.has(e, f2))
assert(evo.get(e, f2) == true)
assert(evo.has(e, f3))
assert(evo.get(e, f3) == 42)
evo.remove(e, f2, f3)
assert(not evo.has(e, f2))
assert(not evo.has(e, f3))
local e2 = evo.clone(e, { [f1] = 21 })
assert(evo.has(e2, f1) and evo.get(e2, f1) == 21)
assert(evo.has(e2, f2) and evo.get(e2, f2) == true)
assert(evo.has(e2, f3) and evo.get(e2, f3) == 42)
local e3 = evo.clone(e, { [f3] = 21 })
assert(evo.has(e3, f1) and evo.get(e3, f1) == true)
assert(evo.has(e3, f2) and evo.get(e3, f2) == true)
assert(evo.has(e3, f3) and evo.get(e3, f3) == 21)
end
end
do
local f1, f2, f3 = evo.id(3)
evo.set(f1, evo.REQUIRES, { f2 })
evo.set(f2, evo.REQUIRES, { f3 })
evo.set(f3, evo.REQUIRES, { f1, f2, f3 })
do
local e = evo.id()
evo.set(e, f1, 42)
assert(evo.has(e, f1) and evo.get(e, f1) == 42)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
assert(evo.has(e, f3) and evo.get(e, f3) == true)
end
do
local e = evo.builder():set(f1, 42):spawn()
assert(evo.has(e, f1) and evo.get(e, f1) == 42)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
assert(evo.has(e, f3) and evo.get(e, f3) == true)
end
do
local e = evo.spawn { [f1] = 42 }
assert(evo.has(e, f1) and evo.get(e, f1) == 42)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
assert(evo.has(e, f3) and evo.get(e, f3) == true)
evo.remove(e, f2, f3)
assert(not evo.has(e, f2))
assert(not evo.has(e, f3))
local e2 = evo.clone(e, { [f1] = 21 })
assert(evo.has(e2, f1) and evo.get(e2, f1) == 21)
assert(evo.has(e2, f2) and evo.get(e2, f2) == true)
assert(evo.has(e2, f3) and evo.get(e2, f3) == true)
local e3 = evo.clone(e, { [f3] = 21 })
assert(evo.has(e3, f1) and evo.get(e3, f1) == 42)
assert(evo.has(e3, f2) and evo.get(e3, f2) == true)
assert(evo.has(e3, f3) and evo.get(e3, f3) == 21)
end
end
do
local f1, f2, f3, f4 = evo.id(4)
do
evo.set(f1, evo.REQUIRES, { f2 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
end
evo.set(f1, evo.REQUIRES, { f2, f3 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
assert(evo.has(e, f3) and evo.get(e, f3) == true)
end
evo.set(f3, evo.REQUIRES, { f4 })
do
local e = evo.id()
evo.set(e, f1)
assert(evo.has(e, f2) and evo.get(e, f2) == true)
assert(evo.has(e, f3) and evo.get(e, f3) == true)
assert(evo.has(e, f4) and evo.get(e, f4) == true)
end
end
end

View File

@@ -0,0 +1,128 @@
local evo = require 'evolved'
do
local f1, f2, f3 = evo.id(3)
local q1e3 = evo.builder():include(f1):exclude(f3):spawn()
local q2e3 = evo.builder():include(f2):exclude(f3):spawn()
local e1 = evo.builder():set(f1, 1):spawn()
local e12 = evo.builder():set(f1, 11):set(f2, 12):spawn()
local e2 = evo.builder():set(f2, 2):spawn()
local e23 = evo.builder():set(f2, 23):set(f3, 3):spawn()
local c1 = evo.chunk(f1)
local c12 = evo.chunk(f1, f2)
local c2 = evo.chunk(f2)
do
local _, entity_list, entity_count = evo.chunk(f2, f3)
assert(entity_count == 1 and entity_list[1] == e23)
end
do
local entity_sum = 0
local s = evo.builder()
:query(q1e3)
:execute(function(chunk, entity_list, entity_count)
for i = 1, entity_count do
entity_sum = entity_sum + entity_list[i]
end
if chunk == c1 then
assert(entity_count == 1)
assert(entity_list[1] == e1)
elseif chunk == c12 then
assert(entity_count == 1)
assert(entity_list[1] == e12)
else
assert(false, "Unexpected chunk: " .. tostring(chunk))
end
end):spawn()
evo.process(s)
assert(entity_sum == e1 + e12)
end
do
local entity_sum = 0
local s = evo.builder()
:query(q2e3)
:execute(function(chunk, entity_list, entity_count)
for i = 1, entity_count do
entity_sum = entity_sum + entity_list[i]
end
if chunk == c12 then
assert(entity_count == 1)
assert(entity_list[1] == e12)
elseif chunk == c2 then
assert(entity_count == 1)
assert(entity_list[1] == e2)
else
assert(false, "Unexpected chunk: " .. tostring(chunk))
end
end):spawn()
evo.process(s)
assert(entity_sum == e12 + e2)
end
do
local entity_sum = 0
local s = evo.builder()
:include(f1)
:exclude(f3)
:execute(function(chunk, entity_list, entity_count)
for i = 1, entity_count do
entity_sum = entity_sum + entity_list[i]
end
if chunk == c1 then
assert(entity_count == 1)
assert(entity_list[1] == e1)
elseif chunk == c12 then
assert(entity_count == 1)
assert(entity_list[1] == e12)
else
assert(false, "Unexpected chunk: " .. tostring(chunk))
end
end):spawn()
evo.process(s)
assert(entity_sum == e1 + e12)
end
do
local entity_sum = 0
local s = evo.builder()
:include(f2)
:exclude(f3)
:execute(function(chunk, entity_list, entity_count)
for i = 1, entity_count do
entity_sum = entity_sum + entity_list[i]
end
if chunk == c12 then
assert(entity_count == 1)
assert(entity_list[1] == e12)
elseif chunk == c2 then
assert(entity_count == 1)
assert(entity_list[1] == e2)
else
assert(false, "Unexpected chunk: " .. tostring(chunk))
end
end):spawn()
evo.process(s)
assert(entity_sum == e12 + e2)
end
end

View File

@@ -7,6 +7,11 @@ local N = 1000
local B = evo.builder()
local F1, F2, F3, F4, F5 = evo.id(5)
local Q1 = evo.builder():include(F1):spawn()
local R1 = evo.builder():require(F1):spawn()
local R2 = evo.builder():require(F1, F2):spawn()
local R3 = evo.builder():require(F1, F2, F3):spawn()
local R4 = evo.builder():require(F1, F2, F3, F4):spawn()
local R5 = evo.builder():require(F1, F2, F3, F4, F5):spawn()
print '----------------------------------------'
@@ -610,7 +615,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo
print '----------------------------------------'
basics.describe_bench(string.format('create and destroy %d entities with 1 components / clone', N),
---@param entities evolved.id[]
function(entities)
@@ -690,3 +694,167 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo
end, function()
return {}
end)
print '----------------------------------------'
basics.describe_bench(string.format('create and destroy %d entities with 1 requires / spawn', N),
---@param entities evolved.id[]
function(entities)
local spawn = evo.spawn
local components = { [R1] = true }
for i = 1, N do
entities[i] = spawn(components)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 2 requires / spawn', N),
---@param entities evolved.id[]
function(entities)
local spawn = evo.spawn
local components = { [R2] = true }
for i = 1, N do
entities[i] = spawn(components)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 3 requires / spawn', N),
---@param entities evolved.id[]
function(entities)
local spawn = evo.spawn
local components = { [R3] = true }
for i = 1, N do
entities[i] = spawn(components)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 4 requires / spawn', N),
---@param entities evolved.id[]
function(entities)
local spawn = evo.spawn
local components = { [R4] = true }
for i = 1, N do
entities[i] = spawn(components)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 5 requires / spawn', N),
---@param entities evolved.id[]
function(entities)
local spawn = evo.spawn
local components = { [R5] = true }
for i = 1, N do
entities[i] = spawn(components)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
print '----------------------------------------'
basics.describe_bench(string.format('create and destroy %d entities with 1 requires / clone', N),
---@param entities evolved.id[]
function(entities)
local clone = evo.clone
local prefab = evo.spawn({ [R1] = true })
for i = 1, N do
entities[i] = clone(prefab)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 2 requires / clone', N),
---@param entities evolved.id[]
function(entities)
local clone = evo.clone
local prefab = evo.spawn({ [R2] = true })
for i = 1, N do
entities[i] = clone(prefab)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 3 requires / clone', N),
---@param entities evolved.id[]
function(entities)
local clone = evo.clone
local prefab = evo.spawn({ [R3] = true })
for i = 1, N do
entities[i] = clone(prefab)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 4 requires / clone', N),
---@param entities evolved.id[]
function(entities)
local clone = evo.clone
local prefab = evo.spawn({ [R4] = true })
for i = 1, N do
entities[i] = clone(prefab)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)
basics.describe_bench(string.format('create and destroy %d entities with 5 requires / clone', N),
---@param entities evolved.id[]
function(entities)
local clone = evo.clone
local prefab = evo.spawn({ [R5] = true })
for i = 1, N do
entities[i] = clone(prefab)
end
evo.batch_destroy(Q1)
end, function()
return {}
end)

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.0.0',
__VERSION = '1.1.0',
__LICENSE = [[
MIT License
@@ -117,10 +117,11 @@ local __entity_places = {} ---@type table<integer, integer>
local __structural_changes = 0 ---@type integer
local __group_subsystems = {} ---@type table<evolved.system, evolved.assoc_list>
local __sorted_includes = {} ---@type table<evolved.query, evolved.assoc_list>
local __sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_list>
local __sorted_requires = {} ---@type table<evolved.fragment, evolved.assoc_list>
local __query_sorted_includes = {} ---@type table<evolved.query, evolved.assoc_list>
local __query_sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_list>
local __group_subsystems = {} ---@type table<evolved.system, evolved.assoc_list>
---
---
@@ -156,6 +157,7 @@ local __query_sorted_excludes = {} ---@type table<evolved.query, evolved.assoc_l
---@field package __has_explicit_major boolean
---@field package __has_explicit_minors boolean
---@field package __has_explicit_fragments boolean
---@field package __has_required_fragments boolean
local __chunk_mt = {}
__chunk_mt.__index = __chunk_mt
@@ -701,6 +703,7 @@ local __DISABLED = __acquire_id()
local __INCLUDES = __acquire_id()
local __EXCLUDES = __acquire_id()
local __REQUIRES = __acquire_id()
local __ON_SET = __acquire_id()
local __ON_ASSIGN = __acquire_id()
@@ -1019,6 +1022,7 @@ function __new_chunk(chunk_parent, chunk_fragment)
__has_explicit_major = false,
__has_explicit_minors = false,
__has_explicit_fragments = false,
__has_required_fragments = false,
}, __chunk_mt)
if chunk_parent then
@@ -1156,6 +1160,9 @@ function __update_chunk_flags(chunk)
local has_explicit_minors = chunk_parent ~= nil and chunk_parent.__has_explicit_fragments
local has_explicit_fragments = has_explicit_major or has_explicit_minors
local has_required_fragments = (chunk_parent ~= nil and chunk_parent.__has_required_fragments)
or __evolved_has(chunk_fragment, __REQUIRES)
chunk.__has_setup_hooks = has_setup_hooks
chunk.__has_assign_hooks = has_assign_hooks
chunk.__has_insert_hooks = has_insert_hooks
@@ -1168,6 +1175,8 @@ function __update_chunk_flags(chunk)
chunk.__has_explicit_major = has_explicit_major
chunk.__has_explicit_minors = has_explicit_minors
chunk.__has_explicit_fragments = has_explicit_fragments
chunk.__has_required_fragments = has_required_fragments
end
---@param major evolved.fragment
@@ -1609,6 +1618,112 @@ end
---
---
---@param chunk evolved.chunk
---@param req_fragment_set table<evolved.fragment, integer>
---@param req_fragment_list evolved.fragment[]
---@param req_fragment_count integer
---@return integer
---@nodiscard
local function __chunk_required_fragments(chunk, req_fragment_set, req_fragment_list, req_fragment_count)
---@type evolved.fragment[]
local fragment_stack = __acquire_table(__table_pool_tag.fragment_list)
local fragment_stack_size = 0
do
local chunk_fragment_list = chunk.__fragment_list
local chunk_fragment_count = chunk.__fragment_count
__lua_table_move(
chunk_fragment_list, 1, chunk_fragment_count,
fragment_stack_size + 1, fragment_stack)
fragment_stack_size = fragment_stack_size + chunk_fragment_count
end
while fragment_stack_size > 0 do
local stack_fragment = fragment_stack[fragment_stack_size]
fragment_stack[fragment_stack_size] = nil
fragment_stack_size = fragment_stack_size - 1
local fragment_requires = __sorted_requires[stack_fragment]
local fragment_require_list = fragment_requires and fragment_requires.__item_list
local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0
for fragment_require_index = 1, fragment_require_count do
---@cast fragment_require_list -?
local required_fragment = fragment_require_list[fragment_require_index]
if req_fragment_set[required_fragment] then
-- this fragment has already been gathered
else
req_fragment_count = req_fragment_count + 1
req_fragment_set[required_fragment] = req_fragment_count
req_fragment_list[req_fragment_count] = required_fragment
fragment_stack_size = fragment_stack_size + 1
fragment_stack[fragment_stack_size] = required_fragment
end
end
end
__release_table(__table_pool_tag.fragment_list, fragment_stack, true)
return req_fragment_count
end
---@param fragment evolved.fragment
---@param req_fragment_set table<evolved.fragment, integer>
---@param req_fragment_list evolved.fragment[]
---@param req_fragment_count integer
---@return integer
---@nodiscard
local function __fragment_required_fragments(fragment, req_fragment_set, req_fragment_list, req_fragment_count)
---@type evolved.fragment[]
local fragment_stack = __acquire_table(__table_pool_tag.fragment_list)
local fragment_stack_size = 0
do
fragment_stack_size = fragment_stack_size + 1
fragment_stack[fragment_stack_size] = fragment
end
while fragment_stack_size > 0 do
local stack_fragment = fragment_stack[fragment_stack_size]
fragment_stack[fragment_stack_size] = nil
fragment_stack_size = fragment_stack_size - 1
local fragment_requires = __sorted_requires[stack_fragment]
local fragment_require_list = fragment_requires and fragment_requires.__item_list
local fragment_require_count = fragment_requires and fragment_requires.__item_count or 0
for fragment_require_index = 1, fragment_require_count do
---@cast fragment_require_list -?
local required_fragment = fragment_require_list[fragment_require_index]
if req_fragment_set[required_fragment] then
-- this fragment has already been gathered
else
req_fragment_count = req_fragment_count + 1
req_fragment_set[required_fragment] = req_fragment_count
req_fragment_list[req_fragment_count] = required_fragment
fragment_stack_size = fragment_stack_size + 1
fragment_stack[fragment_stack_size] = required_fragment
end
end
end
__release_table(__table_pool_tag.fragment_list, fragment_stack, true)
return req_fragment_count
end
---
---
---
---
---
local __defer_set
local __defer_remove
local __defer_clear
@@ -1694,6 +1809,29 @@ local function __spawn_entity(entity, components)
return
end
local req_fragment_set
local req_fragment_list
local req_fragment_count = 0
local ini_chunk = chunk
local ini_fragment_set = ini_chunk.__fragment_set
if chunk.__has_required_fragments then
---@type table<evolved.fragment, integer>
req_fragment_set = __acquire_table(__table_pool_tag.fragment_set)
---@type evolved.fragment[]
req_fragment_list = __acquire_table(__table_pool_tag.fragment_list)
req_fragment_count = __chunk_required_fragments(ini_chunk,
req_fragment_set, req_fragment_list, req_fragment_count)
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
chunk = __chunk_with_fragment(chunk, req_fragment)
end
end
local chunk_entity_list = chunk.__entity_list
local chunk_entity_count = chunk.__entity_count
@@ -1738,6 +1876,36 @@ local function __spawn_entity(entity, components)
component_storage[place] = new_component
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
local req_component_index = chunk_component_indices[req_fragment]
if req_component_index then
---@type evolved.default?, evolved.duplicate?
local req_fragment_default, req_fragment_duplicate =
__evolved_get(req_fragment, __DEFAULT, __DUPLICATE)
local req_component = req_fragment_default
if req_component ~= nil and req_fragment_duplicate then
req_component = req_fragment_duplicate(req_component)
end
if req_component == nil then
req_component = true
end
local req_component_storage = chunk_component_storages[req_component_index]
req_component_storage[place] = req_component
end
end
end
else
for fragment, component in __lua_next, components do
local component_index = chunk_component_indices[fragment]
@@ -1750,6 +1918,24 @@ local function __spawn_entity(entity, components)
component_storage[place] = new_component
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
local req_component_index = chunk_component_indices[req_fragment]
if req_component_index then
local req_component = true
local req_component_storage = chunk_component_storages[req_component_index]
req_component_storage[place] = req_component
end
end
end
end
if chunk.__has_insert_hooks then
@@ -1788,6 +1974,14 @@ local function __spawn_entity(entity, components)
end
end
end
if req_fragment_set then
__release_table(__table_pool_tag.fragment_set, req_fragment_set)
end
if req_fragment_list then
__release_table(__table_pool_tag.fragment_list, req_fragment_list)
end
end
---@param entity evolved.entity
@@ -1810,6 +2004,29 @@ local function __clone_entity(entity, prefab, components)
return
end
local req_fragment_set
local req_fragment_list
local req_fragment_count = 0
local ini_chunk = chunk
local ini_fragment_set = ini_chunk.__fragment_set
if chunk.__has_required_fragments then
---@type table<evolved.fragment, integer>
req_fragment_set = __acquire_table(__table_pool_tag.fragment_set)
---@type evolved.fragment[]
req_fragment_list = __acquire_table(__table_pool_tag.fragment_list)
req_fragment_count = __chunk_required_fragments(ini_chunk,
req_fragment_set, req_fragment_list, req_fragment_count)
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
chunk = __chunk_with_fragment(chunk, req_fragment)
end
end
local chunk_entity_list = chunk.__entity_list
local chunk_entity_count = chunk.__entity_count
@@ -1910,6 +2127,36 @@ local function __clone_entity(entity, prefab, components)
component_storage[place] = new_component
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
local req_component_index = chunk_component_indices[req_fragment]
if req_component_index then
---@type evolved.default?, evolved.duplicate?
local req_fragment_default, req_fragment_duplicate =
__evolved_get(req_fragment, __DEFAULT, __DUPLICATE)
local req_component = req_fragment_default
if req_component ~= nil and req_fragment_duplicate then
req_component = req_fragment_duplicate(req_component)
end
if req_component == nil then
req_component = true
end
local req_component_storage = chunk_component_storages[req_component_index]
req_component_storage[place] = req_component
end
end
end
else
for fragment, component in __lua_next, components do
local component_index = chunk_component_indices[fragment]
@@ -1922,6 +2169,24 @@ local function __clone_entity(entity, prefab, components)
component_storage[place] = new_component
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
local req_component_index = chunk_component_indices[req_fragment]
if req_component_index then
local req_component = true
local req_component_storage = chunk_component_storages[req_component_index]
req_component_storage[place] = req_component
end
end
end
end
if chunk.__has_insert_hooks then
@@ -1960,6 +2225,14 @@ local function __clone_entity(entity, prefab, components)
end
end
end
if req_fragment_set then
__release_table(__table_pool_tag.fragment_set, req_fragment_set)
end
if req_fragment_list then
__release_table(__table_pool_tag.fragment_list, req_fragment_list)
end
end
---
@@ -2372,6 +2645,29 @@ function __chunk_set(old_chunk, fragment, component)
end
end
else
local req_fragment_set
local req_fragment_list
local req_fragment_count = 0
local ini_new_chunk = new_chunk
local ini_fragment_set = ini_new_chunk.__fragment_set
if new_chunk.__has_required_fragments then
---@type table<evolved.fragment, integer>
req_fragment_set = __acquire_table(__table_pool_tag.fragment_set)
---@type evolved.fragment[]
req_fragment_list = __acquire_table(__table_pool_tag.fragment_list)
req_fragment_count = __fragment_required_fragments(fragment,
req_fragment_set, req_fragment_list, req_fragment_count)
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
new_chunk = __chunk_with_fragment(new_chunk, req_fragment)
end
end
local new_entity_list = new_chunk.__entity_list
local new_entity_count = new_chunk.__entity_count
@@ -2518,6 +2814,110 @@ function __chunk_set(old_chunk, fragment, component)
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
---@type evolved.default?, evolved.duplicate?, evolved.set_hook?, evolved.insert_hook?
local req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert
if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then
req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert =
__evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT)
end
if req_fragment_on_set or req_fragment_on_insert then
local req_component_index = new_component_indices[req_fragment]
if req_component_index then
local req_component_storage = new_component_storages[req_component_index]
if req_fragment_duplicate then
for new_place = new_entity_count + 1, new_entity_count + old_entity_count do
local entity = new_entity_list[new_place]
local req_component = req_fragment_default
if req_component ~= nil then req_component = req_fragment_duplicate(req_component) end
if req_component == nil then req_component = true end
req_component_storage[new_place] = req_component
if req_fragment_on_set then
__defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component)
end
if req_fragment_on_insert then
__defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component)
end
end
else
local req_component = req_fragment_default
if req_component == nil then req_component = true end
for new_place = new_entity_count + 1, new_entity_count + old_entity_count do
local entity = new_entity_list[new_place]
req_component_storage[new_place] = req_component
if req_fragment_on_set then
__defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component)
end
if req_fragment_on_insert then
__defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component)
end
end
end
else
for new_place = new_entity_count + 1, new_entity_count + old_entity_count do
local entity = new_entity_list[new_place]
if req_fragment_on_set then
__defer_call_hook(req_fragment_on_set, entity, req_fragment)
end
if req_fragment_on_insert then
__defer_call_hook(req_fragment_on_insert, entity, req_fragment)
end
end
end
else
local req_component_index = new_component_indices[req_fragment]
if req_component_index then
local req_component_storage = new_component_storages[req_component_index]
if req_fragment_duplicate then
for new_place = new_entity_count + 1, new_entity_count + old_entity_count do
local req_component = req_fragment_default
if req_component ~= nil then req_component = req_fragment_duplicate(req_component) end
if req_component == nil then req_component = true end
req_component_storage[new_place] = req_component
end
else
local req_component = req_fragment_default
if req_component == nil then req_component = true end
for new_place = new_entity_count + 1, new_entity_count + old_entity_count do
req_component_storage[new_place] = req_component
end
end
else
-- nothing
end
end
end
end
if req_fragment_set then
__release_table(__table_pool_tag.fragment_set, req_fragment_set)
end
if req_fragment_list then
__release_table(__table_pool_tag.fragment_list, req_fragment_list)
end
__structural_changes = __structural_changes + 1
end
end
@@ -2727,6 +3127,7 @@ end
---@param system evolved.system
local function __system_process(system)
---@type evolved.query?, evolved.execute?, evolved.prologue?, evolved.epilogue?
local query, execute, prologue, epilogue = __evolved_get(system,
__QUERY, __EXECUTE, __PROLOGUE, __EPILOGUE)
@@ -2738,16 +3139,14 @@ local function __system_process(system)
end
end
if query and execute then
if execute then
__evolved_defer()
do
for chunk, entity_list, entity_count in __evolved_execute(query) do
local success, result = __lua_pcall(execute, chunk, entity_list, entity_count)
for chunk, entity_list, entity_count in __evolved_execute(query or system) do
local success, result = __lua_pcall(execute, chunk, entity_list, entity_count)
if not success then
__evolved_commit()
__error_fmt('system execution failed: %s', result)
end
if not success then
__evolved_commit()
__error_fmt('system execution failed: %s', result)
end
end
__evolved_commit()
@@ -3875,6 +4274,29 @@ function __evolved_set(entity, fragment, component)
end
end
else
local req_fragment_set
local req_fragment_list
local req_fragment_count = 0
local ini_new_chunk = new_chunk
local ini_fragment_set = ini_new_chunk.__fragment_set
if new_chunk.__has_required_fragments then
---@type table<evolved.fragment, integer>
req_fragment_set = __acquire_table(__table_pool_tag.fragment_set)
---@type evolved.fragment[]
req_fragment_list = __acquire_table(__table_pool_tag.fragment_list)
req_fragment_count = __fragment_required_fragments(fragment,
req_fragment_set, req_fragment_list, req_fragment_count)
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
new_chunk = __chunk_with_fragment(new_chunk, req_fragment)
end
end
local new_entity_list = new_chunk.__entity_list
local new_entity_count = new_chunk.__entity_count
@@ -3950,6 +4372,64 @@ function __evolved_set(entity, fragment, component)
end
end
end
for i = 1, req_fragment_count do
local req_fragment = req_fragment_list[i]
if ini_fragment_set[req_fragment] then
-- this fragment has already been initialized
else
---@type evolved.default?, evolved.duplicate?, evolved.set_hook?, evolved.insert_hook?
local req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert
if new_chunk_has_setup_hooks or new_chunk_has_insert_hooks then
req_fragment_default, req_fragment_duplicate, req_fragment_on_set, req_fragment_on_insert =
__evolved_get(req_fragment, __DEFAULT, __DUPLICATE, __ON_SET, __ON_INSERT)
end
local req_component_index = new_component_indices[req_fragment]
if req_component_index then
local req_component_storage = new_component_storages[req_component_index]
local req_component = req_fragment_default
if req_component ~= nil and req_fragment_duplicate then
req_component = req_fragment_duplicate(req_component)
end
if req_component == nil then
req_component = true
end
req_component_storage[new_place] = req_component
if req_fragment_on_set then
__defer_call_hook(req_fragment_on_set, entity, req_fragment, req_component)
end
if req_fragment_on_insert then
__defer_call_hook(req_fragment_on_insert, entity, req_fragment, req_component)
end
else
if req_fragment_on_set then
__defer_call_hook(req_fragment_on_set, entity, req_fragment)
end
if req_fragment_on_insert then
__defer_call_hook(req_fragment_on_insert, entity, req_fragment)
end
end
end
end
if req_fragment_set then
__release_table(__table_pool_tag.fragment_set, req_fragment_set)
end
if req_fragment_list then
__release_table(__table_pool_tag.fragment_list, req_fragment_list)
end
end
__evolved_commit()
@@ -4438,12 +4918,12 @@ function __evolved_execute(query)
local chunk_stack = __acquire_table(__table_pool_tag.chunk_list)
local chunk_stack_size = 0
local query_includes = __query_sorted_includes[query]
local query_includes = __sorted_includes[query]
local query_include_set = query_includes and query_includes.__item_set --[[@as table<evolved.fragment, integer>]]
local query_include_list = query_includes and query_includes.__item_list --[=[@as evolved.fragment[]]=]
local query_include_count = query_includes and query_includes.__item_count or 0 --[[@as integer]]
local query_excludes = __query_sorted_excludes[query]
local query_excludes = __sorted_excludes[query]
local query_exclude_set = query_excludes and query_excludes.__item_set --[[@as table<evolved.fragment, integer>]]
local query_exclude_list = query_excludes and query_excludes.__item_list --[=[@as evolved.fragment[]]=]
local query_exclude_count = query_excludes and query_excludes.__item_count or 0 --[[@as integer]]
@@ -5116,6 +5596,31 @@ function __builder_mt:exclude(...)
return self:set(__EXCLUDES, exclude_list)
end
---@param ... evolved.fragment fragments
---@return evolved.builder builder
function __builder_mt:require(...)
local argument_count = __lua_select('#', ...)
if argument_count == 0 then
return self
end
local require_list = self:get(__REQUIRES)
local require_count = require_list and #require_list or 0
if require_count == 0 then
require_list = __lua_table_new(argument_count, 0)
end
for i = 1, argument_count do
---@type evolved.fragment
local fragment = __lua_select(i, ...)
require_list[require_count + i] = fragment
end
return self:set(__REQUIRES, require_list)
end
---@param on_set evolved.set_hook
---@return evolved.builder builder
function __builder_mt:on_set(on_set)
@@ -5213,6 +5718,9 @@ __evolved_set(__DEFAULT, __ON_REMOVE, __update_major_chunks_hook)
__evolved_set(__DUPLICATE, __ON_INSERT, __update_major_chunks_hook)
__evolved_set(__DUPLICATE, __ON_REMOVE, __update_major_chunks_hook)
__evolved_set(__REQUIRES, __ON_INSERT, __update_major_chunks_hook)
__evolved_set(__REQUIRES, __ON_REMOVE, __update_major_chunks_hook)
---
---
---
@@ -5233,6 +5741,7 @@ __evolved_set(__DISABLED, __NAME, 'DISABLED')
__evolved_set(__INCLUDES, __NAME, 'INCLUDES')
__evolved_set(__EXCLUDES, __NAME, 'EXCLUDES')
__evolved_set(__REQUIRES, __NAME, 'REQUIRES')
__evolved_set(__ON_SET, __NAME, 'ON_SET')
__evolved_set(__ON_ASSIGN, __NAME, 'ON_ASSIGN')
@@ -5277,6 +5786,9 @@ __evolved_set(__INCLUDES, __DUPLICATE, __list_copy)
__evolved_set(__EXCLUDES, __DEFAULT, {})
__evolved_set(__EXCLUDES, __DUPLICATE, __list_copy)
__evolved_set(__REQUIRES, __DEFAULT, {})
__evolved_set(__REQUIRES, __DUPLICATE, __list_copy)
__evolved_set(__ON_SET, __UNIQUE)
__evolved_set(__ON_ASSIGN, __UNIQUE)
__evolved_set(__ON_INSERT, __UNIQUE)
@@ -5294,7 +5806,7 @@ __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list)
local include_count = #include_list
if include_count == 0 then
__query_sorted_includes[query] = nil
__sorted_includes[query] = nil
return
end
@@ -5306,11 +5818,11 @@ __evolved_set(__INCLUDES, __ON_SET, function(query, _, include_list)
end
__assoc_list_sort(sorted_includes)
__query_sorted_includes[query] = sorted_includes
__sorted_includes[query] = sorted_includes
end)
__evolved_set(__INCLUDES, __ON_REMOVE, function(query)
__query_sorted_includes[query] = nil
__sorted_includes[query] = nil
end)
---
@@ -5325,7 +5837,7 @@ __evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list)
local exclude_count = #exclude_list
if exclude_count == 0 then
__query_sorted_excludes[query] = nil
__sorted_excludes[query] = nil
return
end
@@ -5337,11 +5849,42 @@ __evolved_set(__EXCLUDES, __ON_SET, function(query, _, exclude_list)
end
__assoc_list_sort(sorted_excludes)
__query_sorted_excludes[query] = sorted_excludes
__sorted_excludes[query] = sorted_excludes
end)
__evolved_set(__EXCLUDES, __ON_REMOVE, function(query)
__query_sorted_excludes[query] = nil
__sorted_excludes[query] = nil
end)
---
---
---
---
---
---@param fragment evolved.fragment
---@param require_list evolved.fragment[]
__evolved_set(__REQUIRES, __ON_SET, function(fragment, _, require_list)
local require_count = #require_list
if require_count == 0 then
__sorted_requires[fragment] = nil
return
end
local sorted_requires = __assoc_list_new(require_count)
for require_index = 1, require_count do
local require = require_list[require_index]
__assoc_list_insert(sorted_requires, require)
end
__assoc_list_sort(sorted_requires)
__sorted_requires[fragment] = sorted_requires
end)
__evolved_set(__REQUIRES, __ON_REMOVE, function(fragment)
__sorted_requires[fragment] = nil
end)
---
@@ -5414,6 +5957,7 @@ evolved.DISABLED = __DISABLED
evolved.INCLUDES = __INCLUDES
evolved.EXCLUDES = __EXCLUDES
evolved.REQUIRES = __REQUIRES
evolved.ON_SET = __ON_SET
evolved.ON_ASSIGN = __ON_ASSIGN

View File

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