31 Commits

Author SHA1 Message Date
5ab963022c Merge pull request #12 from BlackMATov/dev
Dev
2025-05-21 20:48:59 +07:00
7de95207d6 Merge pull request #11 from BlackMATov/dev
Dev
2025-05-21 17:34:13 +07:00
4d3b909efb Merge pull request #10 from BlackMATov/dev
Dev
2025-05-21 06:20:38 +07:00
52b3cbeb20 Merge pull request #9 from BlackMATov/dev
Dev
2025-05-21 03:38:16 +07:00
fec193f4f0 Merge pull request #8 from BlackMATov/dev
Dev
2025-05-21 02:11:39 +07:00
e672b0291a Merge pull request #7 from BlackMATov/dev
Dev
2025-05-02 07:28:38 +07:00
4624f2e603 Merge pull request #6 from BlackMATov/dev
Dev
2025-04-30 23:47:13 +07:00
c25769f64f Merge pull request #5 from BlackMATov/dev
Dev
2025-04-19 18:40:25 +07:00
d7274765cb Merge pull request #4 from BlackMATov/dev
Dev
2025-04-09 03:04:19 +07:00
ef033e7a0c Merge pull request #3 from BlackMATov/dev
Dev
2025-04-09 00:58:14 +07:00
3638dedca5 Merge pull request #2 from BlackMATov/dev
Dev
2025-04-06 21:27:33 +07:00
d025ea039b Merge pull request #1 from BlackMATov/dev
Dev
2025-04-05 02:12:18 +07:00
BlackMATov
6c0df71f17 Merge branch 'dev' 2025-03-27 15:36:58 +07:00
BlackMATov
730bffc3ad Merge branch 'dev' 2025-03-19 06:52:10 +07:00
BlackMATov
4bbd7ee60a Merge branch 'dev' 2025-03-18 07:08:27 +07:00
BlackMATov
79aecc8db3 Merge branch 'dev' 2025-03-17 00:23:36 +07:00
BlackMATov
b803faea63 Merge branch 'dev' 2025-03-16 07:46:26 +07:00
BlackMATov
722eda0b9b Merge branch 'dev' 2025-03-16 02:06:17 +07:00
BlackMATov
c6def51830 Merge branch 'dev' 2025-03-10 08:14:38 +07:00
BlackMATov
65312f79dd Merge branch 'dev' 2025-03-08 06:07:06 +07:00
BlackMATov
9877e41705 Merge branch 'dev' 2025-03-02 12:09:33 +07:00
BlackMATov
3b8367d5c4 Merge branch 'dev' 2025-02-24 06:06:26 +07:00
BlackMATov
419a6b6c45 Merge branch 'dev' 2025-02-23 03:54:56 +07:00
BlackMATov
368b50770c Merge branch 'dev' 2025-02-22 00:31:27 +07:00
BlackMATov
0d49802235 Merge branch 'dev' 2025-02-08 04:52:56 +07:00
BlackMATov
f096d747f3 Merge branch 'dev' 2025-01-31 22:14:13 +07:00
BlackMATov
4d88063c10 Merge branch 'dev' 2025-01-31 12:51:30 +07:00
BlackMATov
bf135534c0 Merge branch 'dev' 2025-01-25 08:37:55 +07:00
BlackMATov
177ea7f180 Merge branch 'dev' 2025-01-21 10:52:19 +07:00
BlackMATov
b98b5f9c42 Merge branch 'dev' 2025-01-18 01:39:27 +07:00
BlackMATov
c7120e8608 opt: get chunk by fragments without sorting 2025-01-16 06:42:03 +07:00
22 changed files with 851 additions and 5651 deletions

View File

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

View File

@@ -1,22 +0,0 @@
# 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`.

697
README.md
View File

@@ -1,4 +1,4 @@
# evolved.lua
# evolved.lua (work in progress)
> Evolved ECS (Entity-Component-System) for Lua
@@ -19,46 +19,6 @@
[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)
- [Iterating Over Fragments](#iterating-over-fragments)
- [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`:
@@ -81,21 +41,211 @@ 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
`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:
You can install `evolved.lua` using [luarocks](https://luarocks.org/) with 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 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.
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
```
## Overview
@@ -147,18 +297,14 @@ function evolved.alive_any(...) end
Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The [`evolved.pack`](#evolvedpack) and [`evolved.unpack`](#evolvedunpack) functions can be used for this purpose.
```lua
---@param primary integer
---@param secondary integer
---@param options? integer
---@param index integer
---@param version integer
---@return evolved.id id
---@nodiscard
function evolved.pack(primary, secondary, options) end
function evolved.pack(index, version) end
---@param id evolved.id
---@return integer primary
---@return integer secondary
---@return integer options
---@nodiscard
---@return integer index
---@return integer version
function evolved.unpack(id) end
```
@@ -410,7 +556,7 @@ evolved.set(enemy1, stamina, 42)
#### Entity Builders
Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluent interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without the necessity of setting them one by one with structural changes for each change.
Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without the necessity of setting them one by one with structural changes for each change.
```lua
local evolved = require 'evolved'
@@ -452,35 +598,6 @@ The [`evolved.alive`](#evolvedalive) function checks whether an entity is alive.
All of these functions can be safely called on non-alive entities and non-alive fragments. Also, they do not cause any structural changes, because they do not modify anything.
### Iterating Over Fragments
Sometimes, you may need to iterate over all fragments attached to an entity. You can use the [`evolved.each`](#evolvedeach) function for this purpose.
```lua
local evolved = require 'evolved'
local health = evolved.builder()
:name('health')
:spawn()
local stamina = evolved.builder()
:name('stamina')
:spawn()
local player = evolved.builder()
:set(health, 100)
:set(stamina, 50)
:spawn()
for fragment, component in evolved.each(player) do
print(string.format('Fragment (%s) has value %d',
evolved.name(fragment), component))
end
```
> [!NOTE]
> [Structural changes](#structural-changes) are not allowed during iteration. If you want to spawn new entities or insert/remove fragments while iterating, defer these operations until the iteration is complete. See the [Deferred Operations](#deferred-operations) section for more details.
### Modifying Operations
The library provides a classic set of functions for modifying entities. These functions are used to insert, override, and remove fragments from entities.
@@ -706,28 +823,6 @@ 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
@@ -800,7 +895,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.
### Predefined Traits
### Advanced Topics
#### Fragment Tags
@@ -944,35 +1039,6 @@ 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.
@@ -1020,235 +1086,10 @@ 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
primaries_state :: implementation-specific
secondaries_state :: implementation-specific
each_iterator :: {each_state? -> fragment?, component?}
execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?}
primaries_iterator :: {primaries_state? -> fragment?, component?}
secondaries_iterator :: {secondaries_state? -> fragment?, component?}
```
### Predefs
```
ANY :: fragment
TAG :: fragment
NAME :: fragment
UNIQUE :: fragment
EXPLICIT :: fragment
INTERNAL :: 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...
name :: id... -> string...
pack :: integer, integer, integer? -> id
unpack :: id -> integer, integer, integer
pair :: id, id -> id
unpair :: id -> id, id
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?
primary :: entity, fragment, integer? -> fragment?, component?
secondary :: entity, fragment, integer? -> fragment?, component?
primaries :: entity, fragment -> {primaries_state? -> fragment?, component?}, primaries_state?
secondaries :: entity, fragment -> {secondaries_state? -> fragment?, component?}, secondaries_state?
primary_count :: entity, fragment -> integer
secondary_count :: entity, fragment -> integer
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:internal :: 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
### `evolved.ANY`
### `evolved.TAG`
### `evolved.NAME`
@@ -1257,8 +1098,6 @@ builder_mt:destruction_policy :: id -> builder
### `evolved.EXPLICIT`
### `evolved.INTERNAL`
### `evolved.DEFAULT`
### `evolved.DUPLICATE`
@@ -1271,8 +1110,6 @@ builder_mt:destruction_policy :: id -> builder
### `evolved.EXCLUDES`
### `evolved.REQUIRES`
### `evolved.ON_SET`
### `evolved.ON_ASSIGN`
@@ -1308,57 +1145,26 @@ builder_mt:destruction_policy :: id -> builder
function evolved.id(count) end
```
### `evolved.name`
```lua
---@param ... evolved.id ids
---@return string... names
---@nodiscard
function evolved.name(...) end
```
### `evolved.pack`
```lua
---@param primary integer
---@param secondary integer
---@param options? integer
---@param index integer
---@param version integer
---@return evolved.id id
---@nodiscard
function evolved.pack(primary, secondary, options) end
function evolved.pack(index, version) end
```
### `evolved.unpack`
```lua
---@param id evolved.id
---@return integer primary
---@return integer secondary
---@return integer options
---@return integer index
---@return integer version
---@nodiscard
function evolved.unpack(id) end
```
### `evolved.pair`
```lua
---@param primary evolved.id
---@param secondary evolved.id
---@return evolved.id pair
---@nodiscard
function evolved.pair(primary, secondary) end
```
### `evolved.unpair`
```lua
---@param pair evolved.id
---@return evolved.id primary
---@return evolved.id secondary
---@nodiscard
function evolved.unpair(pair) end
```
### `evolved.defer`
```lua
@@ -1586,74 +1392,6 @@ function evolved.debug_mode(yesno) end
function evolved.collect_garbage() end
```
## Relations
### `evolved.primary`
```lua
---@param entity evolved.entity
---@param secondary evolved.fragment
---@param index? integer
---@return evolved.fragment? primary
---@return evolved.component? component
---@nodiscard
function evolved.primary(entity, secondary, index) end
```
### `evolved.secondary`
```lua
---@param entity evolved.entity
---@param primary evolved.fragment
---@param index? integer
---@return evolved.fragment? secondary
---@return evolved.component? component
---@nodiscard
function evolved.secondary(entity, primary, index) end
```
### `evolved.primaries`
```lua
---@param entity evolved.entity
---@param secondary evolved.fragment
---@return evolved.primaries_iterator iterator
---@return evolved.primaries_state? iterator_state
---@nodiscard
function evolved.primaries(entity, secondary) end
```
### `evolved.secondaries`
```lua
---@param entity evolved.entity
---@param primary evolved.fragment
---@return evolved.secondaries_iterator iterator
---@return evolved.secondaries_state? iterator_state
---@nodiscard
function evolved.secondaries(entity, primary) end
```
### `evolved.primary_count`
```lua
---@param entity evolved.entity
---@param secondary evolved.fragment
---@return integer
---@nodiscard
function evolved.primary_count(entity, secondary) end
```
### `evolved.secondary_count`
```lua
---@param entity evolved.entity
---@param primary evolved.fragment
---@return integer
---@nodiscard
function evolved.secondary_count(entity, primary) end
```
## Classes
### Chunk
@@ -1854,13 +1592,6 @@ function evolved.builder_mt:unique() end
function evolved.builder_mt:explicit() end
```
#### `evolved.builder_mt:internal`
```lua
---@return evolved.builder builder
function evolved.builder_mt:internal() end
```
#### `evolved.builder_mt:default`
```lua
@@ -1907,14 +1638,6 @@ 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
@@ -1994,3 +1717,5 @@ 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

@@ -2,15 +2,11 @@
## Backlog
- Improve the performance of required fragments by caching first-level required chunks.
- Improve the performance of builders that are used multiple times by caching hint chunks.
- Queries can cache major chunks to avoid finding them every time.
- Should we make one builder:build method instead of :spawn and :clone?
- Add multi-spawn to the builder to spawn multiple entities at once.
- Add a function to shrink storages to free unused memory.
- Should we cache the result of without_unique_fragments to clone faster?
## After first release
- cached queries
- observers and events
- add INDEX fragment trait
- add REQUIRES fragment trait
- use compact prefix-tree for chunks
- optional ffi component storages
- add EXCLUSIVE fragment trait

View File

@@ -1,14 +1,6 @@
require 'develop.example'
require 'develop.untests'
require 'develop.samples.relations'
require 'develop.testing.name_tests'
require 'develop.testing.pairs_tests'
require 'develop.testing.requires_fragment_tests'
require 'develop.testing.system_as_query_tests'
require 'develop.unbench'
require 'develop.untests'
require 'develop.usbench'
local basics = require 'develop.basics'
@@ -20,10 +12,4 @@ 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'
print '----------------------------------------'
basics.describe_fuzz 'develop.fuzzing.wildcard_fuzz'

View File

@@ -1,13 +1,5 @@
local basics = {}
local MIN_FUZZ_SECS = 0.5
local MIN_BENCH_SECS = 0.1
local MIN_WARMUP_SECS = 0.1
local MIN_FUZZ_ITERS = 100
local MIN_BENCH_ITERS = 100
local MIN_WARMUP_ITERS = 100
local __table_pack = (function()
---@diagnostic disable-next-line: deprecated
return table.pack or function(...)
@@ -31,13 +23,8 @@ end
---@param modname string
function basics.describe_fuzz(modname)
basics.unload('evolved')
print(string.format('| %s ... |', modname))
collectgarbage('collect')
collectgarbage('stop')
do
local iters = 0
@@ -49,7 +36,7 @@ function basics.describe_fuzz(modname)
iters = iters + 1
basics.unload(modname)
require(modname)
until iters >= MIN_FUZZ_ITERS and os.clock() - start_s >= MIN_FUZZ_SECS
until os.clock() - start_s > 0.5
end)
local finish_s = os.clock()
@@ -65,9 +52,6 @@ function basics.describe_fuzz(modname)
print('|-- FUZZ FAIL: ' .. result)
end
end
collectgarbage('restart')
collectgarbage('collect')
end
---@param name string
@@ -75,22 +59,17 @@ end
---@param init? fun(): ...
---@param fini? fun(...): ...
function basics.describe_bench(name, loop, init, fini)
basics.unload('evolved')
print(string.format('| %s ... |', name))
local state = init and __table_pack(init()) or {}
do
local iters = 0
local warmup_s = os.clock()
local success, result = pcall(function()
repeat
iters = iters + 1
loop(__table_unpack(state))
until iters >= MIN_WARMUP_ITERS and os.clock() - warmup_s > MIN_WARMUP_SECS
until os.clock() - warmup_s > 0.1
end)
if not success then
@@ -112,7 +91,7 @@ function basics.describe_bench(name, loop, init, fini)
repeat
iters = iters + 1
loop(__table_unpack(state))
until iters >= MIN_BENCH_ITERS and os.clock() - start_s > MIN_BENCH_SECS
until os.clock() - start_s > 0.1
end)
local finish_s = os.clock()

View File

@@ -117,12 +117,5 @@ end
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.collect_garbage()

View File

@@ -123,12 +123,5 @@ end
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.collect_garbage()

View File

@@ -77,12 +77,5 @@ end
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.collect_garbage()

View File

@@ -1,47 +0,0 @@
local evo = require 'evolved'
evo.debug_mode(true)
---
---
---
---
---
for _ = 1, 1000 do
local initial_primary = math.random(1, 2 ^ 20 - 1)
local initial_secondary = math.random(1, 2 ^ 20 - 1)
local packed_id = evo.pack(initial_primary, initial_secondary)
local unpacked_primary, unpacked_secondary, unpacked_options = evo.unpack(packed_id)
assert(initial_primary == unpacked_primary)
assert(initial_secondary == unpacked_secondary)
assert(0 == unpacked_options)
end
for _ = 1, 1000 do
local initial_primary = math.random(1, 2 ^ 20 - 1)
local initial_secondary = math.random(1, 2 ^ 20 - 1)
local initial_options = math.random(1, 2 ^ 12 - 1)
local packed_id = evo.pack(initial_primary, initial_secondary, initial_options)
local unpacked_primary, unpacked_secondary, unpacked_options = evo.unpack(packed_id)
assert(initial_primary == unpacked_primary)
assert(initial_secondary == unpacked_secondary)
assert(initial_options == unpacked_options)
end
for _ = 1, 1000 do
local initial_primary = math.random(1, 2 ^ 31 - 1)
local initial_secondary = math.random(1, 2 ^ 31 - 1)
local initial_options = math.random(1, 2 ^ 31 - 1)
local packed_id = evo.pack(initial_primary, initial_secondary, initial_options)
local unpacked_primary, unpacked_secondary, unpacked_options = evo.unpack(packed_id)
assert(initial_primary % 2 ^ 20 == unpacked_primary)
assert(initial_secondary % 2 ^ 20 == unpacked_secondary)
assert(initial_options % 2 ^ 12 == unpacked_options)
end

View File

@@ -1,131 +0,0 @@
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
---
---
---
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
if math.random(1, 2) == 1 then
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_fragment_list))
else
evo.destroy(__table_unpack(all_fragment_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
end
if math.random(1, 2) == 1 then
evo.collect_garbage()
end

View File

@@ -63,12 +63,5 @@ end
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.collect_garbage()

View File

@@ -1,254 +0,0 @@
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_entity_list = {} ---@type evolved.entity[]
local all_fragment_list = {} ---@type evolved.fragment[]
for i = 1, math.random(1, 5) do
local fragment_builder = evo.builder()
if math.random(1, 3) == 1 then
fragment_builder:explicit()
end
if math.random(1, 3) == 1 then
if math.random(1, 2) == 1 then
fragment_builder:destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY)
else
fragment_builder:destruction_policy(evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT)
end
end
all_fragment_list[i] = fragment_builder:spawn()
end
for i = 1, math.random(50, 100) do
local entity_builder = evo.builder()
for _ = 0, math.random(0, #all_fragment_list) do
if math.random(1, 2) == 1 then
local fragment = all_fragment_list[math.random(1, #all_fragment_list)]
entity_builder:set(fragment)
else
local primary = all_fragment_list[math.random(1, #all_fragment_list)]
local secondary = all_fragment_list[math.random(1, #all_fragment_list)]
entity_builder:set(evo.pair(primary, secondary))
end
end
all_entity_list[i] = entity_builder:spawn()
end
---
---
---
---
---
for _ = 1, math.random(1, 100) do
local query_builder = evo.builder()
local query_include_set = {} ---@type table<evolved.fragment, integer>
local query_include_list = {} ---@type evolved.entity[]
local query_include_count = 0 ---@type integer
local query_exclude_set = {} ---@type table<evolved.fragment, integer>
local query_exclude_list = {} ---@type evolved.entity[]
local query_exclude_count = 0 ---@type integer
for _ = 1, math.random(0, 2) do
if math.random(1, 2) == 1 then
local fragment = all_fragment_list[math.random(1, #all_fragment_list)]
query_builder:include(fragment)
if not query_include_set[fragment] then
query_include_count = query_include_count + 1
query_include_set[fragment] = query_include_count
query_include_list[query_include_count] = fragment
end
else
local primary = all_fragment_list[math.random(1, #all_fragment_list)]
local secondary = all_fragment_list[math.random(1, #all_fragment_list)]
if math.random(1, 3) == 1 then
primary = evo.ANY
end
if math.random(1, 3) == 1 then
secondary = evo.ANY
end
local pair = evo.pair(primary, secondary)
query_builder:include(pair)
if not query_include_set[pair] then
query_include_count = query_include_count + 1
query_include_set[pair] = query_include_count
query_include_list[query_include_count] = pair
end
end
end
for _ = 1, math.random(0, 2) do
if math.random(1, 2) == 1 then
local fragment = all_fragment_list[math.random(1, #all_fragment_list)]
query_builder:exclude(fragment)
if not query_exclude_set[fragment] then
query_exclude_count = query_exclude_count + 1
query_exclude_set[fragment] = query_exclude_count
query_exclude_list[query_exclude_count] = fragment
end
else
local primary = all_fragment_list[math.random(1, #all_fragment_list)]
local secondary = all_fragment_list[math.random(1, #all_fragment_list)]
if math.random(1, 3) == 1 then
primary = evo.ANY
end
if math.random(1, 3) == 1 then
secondary = evo.ANY
end
local pair = evo.pair(primary, secondary)
query_builder:exclude(pair)
if not query_exclude_set[pair] then
query_exclude_count = query_exclude_count + 1
query_exclude_set[pair] = query_exclude_count
query_exclude_list[query_exclude_count] = pair
end
end
end
local query_entity_set = {} ---@type table<evolved.entity, integer>
local query_entity_count = 0 ---@type integer
do
local query = query_builder:spawn()
for chunk, entity_list, entity_count in evo.execute(query) do
if not chunk:has(evo.INTERNAL) then
for i = 1, entity_count do
local entity = entity_list[i]
assert(not query_entity_set[entity])
query_entity_count = query_entity_count + 1
query_entity_set[entity] = query_entity_count
end
end
end
if query_entity_set[query] then
query_entity_set[query] = nil
query_entity_count = query_entity_count - 1
end
evo.destroy(query)
end
do
local expected_entity_count = 0
for _, entity in ipairs(all_entity_list) do
local is_entity_expected =
not evo.empty(entity) and
evo.has_all(entity, __table_unpack(query_include_list)) and
not evo.has_any(entity, __table_unpack(query_exclude_list))
for fragment in evo.each(entity) do
if evo.has(fragment, evo.EXPLICIT) then
local is_fragment_included =
query_include_set[fragment] ~= nil or
query_include_set[evo.pair(fragment, evo.ANY)] ~= nil
if not is_fragment_included then
is_entity_expected = false
break
end
end
end
if is_entity_expected then
assert(query_entity_set[entity])
expected_entity_count = expected_entity_count + 1
else
assert(not query_entity_set[entity])
end
end
for _, entity in ipairs(all_fragment_list) do
local is_entity_expected =
not evo.empty(entity) and
evo.has_all(entity, __table_unpack(query_include_list)) and
not evo.has_any(entity, __table_unpack(query_exclude_list))
for fragment in evo.each(entity) do
if evo.has(fragment, evo.EXPLICIT) then
is_entity_expected = is_entity_expected and
(query_include_set[fragment] ~= nil) or
(evo.is_pair(fragment) and query_include_set[evo.pair(fragment, evo.ANY)] ~= nil)
end
end
if is_entity_expected then
assert(query_entity_set[entity])
expected_entity_count = expected_entity_count + 1
else
assert(not query_entity_set[entity])
end
end
assert(query_entity_count == expected_entity_count)
end
end
---
---
---
---
---
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
if math.random(1, 2) == 1 then
evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_fragment_list))
else
evo.destroy(__table_unpack(all_fragment_list))
if math.random(1, 2) == 1 then
evo.collect_garbage()
end
evo.destroy(__table_unpack(all_entity_list))
end
if math.random(1, 2) == 1 then
evo.collect_garbage()
end

View File

@@ -1,64 +0,0 @@
---@diagnostic disable: unused-local
local evo = require 'evolved'
evo.debug_mode(true)
local fragments = {
planet = evo.builder():name('planet'):tag():spawn(),
spaceship = evo.builder():name('spaceship'):tag():spawn(),
}
local relations = {
docked_to = evo.builder():name('docked_to'):tag():explicit():spawn(),
}
local planets = {
mars = evo.builder():name('Mars'):set(fragments.planet):spawn(),
venus = evo.builder():name('Venus'):set(fragments.planet):spawn(),
}
local spaceships = {
falcon = evo.builder()
:name('Millennium Falcon')
:set(fragments.spaceship)
:set(evo.pair(relations.docked_to, planets.mars))
:spawn(),
enterprise = evo.builder()
:name('USS Enterprise')
:set(fragments.spaceship)
:set(evo.pair(relations.docked_to, planets.venus))
:spawn(),
}
local queries = {
all_docked_spaceships = evo.builder()
:include(fragments.spaceship)
:include(evo.pair(relations.docked_to, evo.ANY))
:spawn(),
docked_spaceships_to_mars = evo.builder()
:include(fragments.spaceship)
:include(evo.pair(relations.docked_to, planets.mars))
:spawn(),
}
print '-= | All Docked Spaceships | =-'
for chunk, entity_list, entity_count in evo.execute(queries.all_docked_spaceships) do
for i = 1, entity_count do
local entity = entity_list[i]
local planet = evo.secondary(entity, relations.docked_to)
print(string.format('%s is docked to %s', evo.name(entity), evo.name(planet)))
end
end
print '-= | Docked Spaceships to Mars | =-'
for chunk, entity_list, entity_count in evo.execute(queries.docked_spaceships_to_mars) do
for i = 1, entity_count do
local entity = entity_list[i]
local planet = evo.secondary(entity, relations.docked_to)
print(string.format('%s is docked to %s', evo.name(entity), evo.name(planet)))
end
end

View File

@@ -1,43 +0,0 @@
local evo = require 'evolved'
do
local id = evo.id()
local index, version, options = evo.unpack(id)
assert(evo.name(id) == string.format('$%d#%d:%d:%d', id, index, version, options))
evo.set(id, evo.NAME, 'hello')
assert(evo.name(id) == 'hello')
evo.set(id, evo.NAME, 'world')
assert(evo.name(id) == 'world')
evo.destroy(id)
assert(evo.name(id) == string.format('$%d#%d:%d:%d', id, index, version, options))
end
do
local id1, id2, id3, id4, id5 = evo.id(5)
evo.set(id1, evo.NAME, 'id1')
evo.set(id2, evo.NAME, 'id2')
evo.set(id3, evo.NAME, 'id3')
evo.set(id4, evo.NAME, 'id4')
evo.set(id5, evo.NAME, 'id5')
do
local id1_n, id3_n, id5_n = evo.name(id1, id3, id5)
assert(id1_n == 'id1')
assert(id3_n == 'id3')
assert(id5_n == 'id5')
end
do
local id1_n, id2_n, id3_n, id4_n, id5_n = evo.name(id1, id2, id3, id4, id5)
assert(id1_n == 'id1')
assert(id2_n == 'id2')
assert(id3_n == 'id3')
assert(id4_n == 'id4')
assert(id5_n == 'id5')
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,245 +0,0 @@
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

@@ -1,128 +0,0 @@
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,11 +7,6 @@ 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 '----------------------------------------'
@@ -615,6 +610,7 @@ 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)
@@ -694,167 +690,3 @@ 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

@@ -6325,36 +6325,3 @@ do
assert(not chunk and not entity_list and not entity_count)
end
end
do
local function v2(x, y) return { x = x or 0, y = y or 0 } end
local function v2_clone(v) return { x = v.x, y = v.y } end
local v2_default = v2(1, 2)
do
local f = evo.builder():default(v2_default):spawn()
local b = evo.builder()
b:set(f)
evo.remove(f, evo.DEFAULT)
local e = b:spawn()
assert(evo.has(e, f) and evo.get(e, f).x == 1 and evo.get(e, f).y == 2)
assert(evo.get(e, f) == v2_default)
end
do
local f = evo.builder():default(v2_default):duplicate(v2_clone):spawn()
local b = evo.builder()
b:set(f)
evo.remove(f, evo.DEFAULT, evo.DUPLICATE)
local e = b:spawn()
assert(evo.has(e, f) and evo.get(e, f).x == 1 and evo.get(e, f).y == 2)
assert(evo.get(e, f) ~= v2_default)
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
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",
}
}