├── .gitignore ├── .luacov ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── example ├── conf.lua └── main.lua ├── develop ├── ROADMAP.md ├── fuzzing │ ├── pack_unpack_fuzz.lua │ ├── unique_fuzz.lua │ ├── explicit_fuzz.lua │ ├── batch_destroy_fuzz.lua │ ├── destroy_fuzz.lua │ ├── requires_fuzz.lua │ └── execute_fuzz.lua ├── GUIDES.md ├── testing │ ├── name_tests.lua │ ├── build_tests.lua │ ├── locate_tests.lua │ ├── cancel_tests.lua │ ├── system_as_query_tests.lua │ ├── requires_fragment_tests.lua │ ├── spawn_tests.lua │ ├── clone_tests.lua │ └── multi_spawn_tests.lua ├── all.lua ├── benchmarks │ ├── migration_bmarks.lua │ ├── process_bmarks.lua │ ├── table_bmarks.lua │ ├── common_bmarks.lua │ ├── clone_bmarks.lua │ └── spawn_bmarks.lua ├── basics.lua └── 3rdparty │ └── tiny.lua ├── .github └── workflows │ ├── lua5.1.yml │ ├── lua5.4.yml │ └── luajit.yml ├── rockspecs ├── evolved.lua-1.0.0-0.rockspec ├── evolved.lua-1.1.0-0.rockspec ├── evolved.lua-1.2.0-0.rockspec ├── evolved.lua-1.3.0-0.rockspec ├── evolved.lua-1.4.0-0.rockspec ├── evolved.lua-1.5.0-0.rockspec └── evolved.lua-scm-0.rockspec ├── .luarc.json ├── LICENSE.md └── evolved.d.tl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | modules = { 2 | ['evolved'] = 'evolved.lua' 3 | } 4 | reporter = 'html' 5 | reportfile = 'luacov.report.html' 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "sumneko.lua", 4 | "tomblind.local-lua-debugger-vscode" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /example/conf.lua: -------------------------------------------------------------------------------- 1 | if os.getenv('LOCAL_LUA_DEBUGGER_VSCODE') == '1' then 2 | require('lldebugger').start() 3 | end 4 | 5 | ---@type love.conf 6 | function love.conf(t) 7 | t.window.title = 'Evolved Example' 8 | t.window.width = 640 9 | t.window.height = 480 10 | end 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[json][jsonc][lua][markdown][teal][yaml]": { 3 | "editor.formatOnSave": true, 4 | "files.insertFinalNewline": true, 5 | "files.trimFinalNewlines": true, 6 | "files.trimTrailingWhitespace": true 7 | }, 8 | "markdown.extension.toc.levels": "2..6", 9 | "markdown.extension.toc.omittedFromToc": { 10 | "README.md": [ 11 | "# API Reference" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /develop/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Backlog 4 | 5 | - observers and events 6 | - add INDEX fragment trait 7 | - use compact prefix-tree for chunks 8 | - optional ffi component storages 9 | 10 | ## Thoughts 11 | 12 | - We can return deferred status from modifying operations and spawn/clone methods. 13 | - We should have a way to not copy components on deferred spawn/clone. 14 | 15 | ## Known Issues 16 | 17 | - Required fragments are slower than they should be 18 | - Errors in hooks are cannot be handled properly right now 19 | -------------------------------------------------------------------------------- /develop/fuzzing/pack_unpack_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | for _ = 1, 1000 do 12 | local initial_primary = math.random(1, 2 ^ 20 - 1) 13 | local initial_secondary = math.random(1, 2 ^ 20 - 1) 14 | 15 | local packed_id = evo.pack(initial_primary, initial_secondary) 16 | local unpacked_primary, unpacked_secondary = evo.unpack(packed_id) 17 | 18 | assert(initial_primary == unpacked_primary) 19 | assert(initial_secondary == unpacked_secondary) 20 | end 21 | -------------------------------------------------------------------------------- /.github/workflows/lua5.1.yml: -------------------------------------------------------------------------------- 1 | name: lua5.1 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{matrix.operating_system}} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | lua_version: ["5.1"] 12 | operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"] 13 | name: ${{matrix.operating_system}}-${{matrix.lua_version}} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: ilammy/msvc-dev-cmd@v1 17 | - uses: leafo/gh-actions-lua@v11 18 | with: 19 | luaVersion: ${{matrix.lua_version}} 20 | - run: | 21 | lua ./develop/all.lua 22 | -------------------------------------------------------------------------------- /.github/workflows/lua5.4.yml: -------------------------------------------------------------------------------- 1 | name: lua5.4 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{matrix.operating_system}} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | lua_version: ["5.4"] 12 | operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"] 13 | name: ${{matrix.operating_system}}-${{matrix.lua_version}} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: ilammy/msvc-dev-cmd@v1 17 | - uses: leafo/gh-actions-lua@v11 18 | with: 19 | luaVersion: ${{matrix.lua_version}} 20 | - run: | 21 | lua ./develop/all.lua 22 | -------------------------------------------------------------------------------- /.github/workflows/luajit.yml: -------------------------------------------------------------------------------- 1 | name: luajit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{matrix.operating_system}} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | lua_version: ["luajit"] 12 | operating_system: ["ubuntu-latest", "macos-latest", "windows-latest"] 13 | name: ${{matrix.operating_system}}-${{matrix.lua_version}} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: ilammy/msvc-dev-cmd@v1 17 | - uses: leafo/gh-actions-lua@v11 18 | with: 19 | luaVersion: ${{matrix.lua_version}} 20 | - run: | 21 | lua ./develop/all.lua 22 | -------------------------------------------------------------------------------- /develop/GUIDES.md: -------------------------------------------------------------------------------- 1 | # Guidelines 2 | 3 | ## Checklists 4 | 5 | ### New Version Releasing 6 | 7 | 1. Ensure all tests pass on CI. 8 | 2. Update the version number in `evolved.lua`. 9 | 3. Update the **Changelog** section in `README.md`. 10 | 4. Create a new rockspec file in `rockspecs`. 11 | 5. Commit the changes with a message like `vX.Y.Z`. 12 | 6. Push and merge the changes to the `main` branch. 13 | 7. Create the release on GitHub. 14 | 8. Upload the new package to LuaRocks. 15 | 16 | ### Adding a New Top-Level Function 17 | 1. Insert the new function into the `evolved` table in `evolved.lua`. 18 | 2. Create tests for the function in `develop/testing/function_name_tests.lua`. 19 | 3. Add the new test to `develop/all.lua`. 20 | 4. Document the function in the **Cheat Sheet** and **API Reference** sections of `README.md`. 21 | 5. Provide a description in the **Overview** section of `README.md`. 22 | 6. Describe the update in the **Changelog** section of `README.md`. 23 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.0.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.0.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.0.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.1.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.1.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.2.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.2.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.2.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.3.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.3.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.3.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.4.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.4.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.4.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-1.5.0-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "1.5.0-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | tag = "v1.5.0", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rockspecs/evolved.lua-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "evolved.lua" 3 | version = "scm-0" 4 | source = { 5 | url = "git://github.com/BlackMATov/evolved.lua", 6 | branch = "main", 7 | } 8 | description = { 9 | homepage = "https://github.com/BlackMATov/evolved.lua", 10 | summary = "Evolved ECS (Entity-Component-System) for Lua", 11 | detailed = [[ 12 | `evolved.lua` is a fast and flexible ECS (Entity-Component-System) library for Lua. 13 | It is designed to be simple and easy to use, while providing all the features needed to create complex systems with blazing performance. 14 | ]], 15 | license = "MIT", 16 | labels = { 17 | "ecs", 18 | "entity", 19 | "entities", 20 | "component", 21 | "components", 22 | "entity-component", 23 | "entity-component-system", 24 | }, 25 | } 26 | dependencies = { 27 | "lua >= 5.1", 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | evolved = "evolved.lua", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "completion": { 4 | "autoRequire": false 5 | }, 6 | "diagnostics": { 7 | "groupSeverity": { 8 | "ambiguity": "Warning", 9 | "await": "Warning", 10 | "codestyle": "Warning", 11 | "conventions": "Warning", 12 | "duplicate": "Warning", 13 | "global": "Warning", 14 | "luadoc": "Warning", 15 | "redefined": "Warning", 16 | "strict": "Warning", 17 | "strong": "Warning", 18 | "type-check": "Warning", 19 | "unbalanced": "Warning", 20 | "unused": "Warning" 21 | } 22 | }, 23 | "runtime": { 24 | "version": "LuaJIT", 25 | "pathStrict": true 26 | }, 27 | "workspace": { 28 | "ignoreDir": [ 29 | ".vscode", 30 | "develop/3rdparty" 31 | ], 32 | "library": [ 33 | "${3rd}/love2d/library" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2024-2025, by Matvey Cherevko (blackmatov@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /develop/testing/name_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local id = evo.id() 5 | 6 | local index, version = evo.unpack(id) 7 | assert(evo.name(id) == string.format('$%d#%d:%d', id, index, version)) 8 | 9 | evo.set(id, evo.NAME, 'hello') 10 | assert(evo.name(id) == 'hello') 11 | 12 | evo.set(id, evo.NAME, 'world') 13 | assert(evo.name(id) == 'world') 14 | 15 | evo.destroy(id) 16 | assert(evo.name(id) == string.format('$%d#%d:%d', id, index, version)) 17 | end 18 | 19 | do 20 | local id1, id2, id3, id4, id5 = evo.id(5) 21 | 22 | evo.set(id1, evo.NAME, 'id1') 23 | evo.set(id2, evo.NAME, 'id2') 24 | evo.set(id3, evo.NAME, 'id3') 25 | evo.set(id4, evo.NAME, 'id4') 26 | evo.set(id5, evo.NAME, 'id5') 27 | 28 | do 29 | local id1_n, id3_n, id5_n = evo.name(id1, id3, id5) 30 | assert(id1_n == 'id1') 31 | assert(id3_n == 'id3') 32 | assert(id5_n == 'id5') 33 | end 34 | 35 | do 36 | local id1_n, id2_n, id3_n, id4_n, id5_n = evo.name(id1, id2, id3, id4, id5) 37 | assert(id1_n == 'id1') 38 | assert(id2_n == 'id2') 39 | assert(id3_n == 'id3') 40 | assert(id4_n == 'id4') 41 | assert(id5_n == 'id5') 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Evolved All (lua5.1)", 6 | "type": "lua-local", 7 | "request": "launch", 8 | "program": { 9 | "lua": "lua5.1", 10 | "file": "${workspaceFolder}/develop/all.lua" 11 | } 12 | }, 13 | { 14 | "name": "Launch Evolved All (lua5.4)", 15 | "type": "lua-local", 16 | "request": "launch", 17 | "program": { 18 | "lua": "lua5.4", 19 | "file": "${workspaceFolder}/develop/all.lua" 20 | } 21 | }, 22 | { 23 | "name": "Launch Evolved All (luajit)", 24 | "type": "lua-local", 25 | "request": "launch", 26 | "program": { 27 | "lua": "luajit", 28 | "file": "${workspaceFolder}/develop/all.lua" 29 | } 30 | }, 31 | { 32 | "name": "Launch Evolved Example (LÖVE)", 33 | "type": "lua-local", 34 | "request": "launch", 35 | "program": { 36 | "command": "love" 37 | }, 38 | "args": [ 39 | "${workspaceFolder}/example" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /develop/testing/build_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local f1, f2 = evo.id(2) 5 | 6 | do 7 | local e = evo.builder():set(f1, 42):set(f2, 'hello'):build() 8 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 9 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 10 | end 11 | 12 | do 13 | local p = evo.builder():set(f1, 42):build() 14 | local e = evo.builder():set(f2, 'hello'):build(p) 15 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 16 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 17 | end 18 | 19 | do 20 | local entity_list, entity_count = evo.builder():set(f1, 42):set(f2, 'hello'):multi_build(5) 21 | assert(entity_count == 5) 22 | 23 | for i = 1, entity_count do 24 | local e = entity_list[i] 25 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 26 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 27 | end 28 | end 29 | 30 | do 31 | local p = evo.builder():set(f1, 42):build() 32 | local entity_list, entity_count = evo.builder():set(f2, 'hello'):multi_build(5, p) 33 | assert(entity_count == 5) 34 | 35 | for i = 1, entity_count do 36 | local e = entity_list[i] 37 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 38 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /develop/testing/locate_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local e1, e2, f1, f2 = evo.id(4) 5 | 6 | do 7 | local chunk, place = evo.locate(e1) 8 | assert(chunk == nil and place == 0) 9 | end 10 | 11 | evo.set(e1, f1, 42) 12 | 13 | do 14 | local chunk, place = evo.locate(e1) 15 | assert(chunk and chunk == evo.chunk(f1) and place == 1) 16 | assert(chunk:components(f1)[place] == 42) 17 | 18 | chunk, place = evo.locate(e2) 19 | assert(chunk == nil and place == 0) 20 | end 21 | 22 | evo.set(e1, f2, 'hello') 23 | 24 | do 25 | local chunk, place = evo.locate(e1) 26 | assert(chunk and chunk == evo.chunk(f1, f2) and place == 1) 27 | assert(chunk:components(f1)[place] == 42) 28 | assert(chunk:components(f2)[place] == 'hello') 29 | 30 | chunk, place = evo.locate(e2) 31 | assert(chunk == nil and place == 0) 32 | end 33 | 34 | evo.set(e2, f1, 84) 35 | evo.set(e2, f2, 'world') 36 | 37 | do 38 | local chunk, place = evo.locate(e1) 39 | assert(chunk and chunk == evo.chunk(f1, f2) and place == 1) 40 | assert(chunk:components(f1)[place] == 42) 41 | assert(chunk:components(f2)[place] == 'hello') 42 | 43 | chunk, place = evo.locate(e2) 44 | assert(chunk and chunk == evo.chunk(f1, f2) and place == 2) 45 | assert(chunk:components(f1)[place] == 84) 46 | assert(chunk:components(f2)[place] == 'world') 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /develop/all.lua: -------------------------------------------------------------------------------- 1 | require 'develop.testing.build_tests' 2 | require 'develop.testing.cancel_tests' 3 | require 'develop.testing.clone_tests' 4 | require 'develop.testing.locate_tests' 5 | require 'develop.testing.main_tests' 6 | require 'develop.testing.multi_spawn_tests' 7 | require 'develop.testing.name_tests' 8 | require 'develop.testing.requires_fragment_tests' 9 | require 'develop.testing.spawn_tests' 10 | require 'develop.testing.system_as_query_tests' 11 | 12 | require 'develop.benchmarks.clone_bmarks' 13 | require 'develop.benchmarks.common_bmarks' 14 | require 'develop.benchmarks.migration_bmarks' 15 | require 'develop.benchmarks.process_bmarks' 16 | require 'develop.benchmarks.spawn_bmarks' 17 | require 'develop.benchmarks.table_bmarks' 18 | 19 | local basics = require 'develop.basics' 20 | 21 | print '----------------------------------------' 22 | basics.describe_fuzz 'develop.fuzzing.destroy_fuzz' 23 | print '----------------------------------------' 24 | basics.describe_fuzz 'develop.fuzzing.execute_fuzz' 25 | print '----------------------------------------' 26 | basics.describe_fuzz 'develop.fuzzing.batch_destroy_fuzz' 27 | print '----------------------------------------' 28 | basics.describe_fuzz 'develop.fuzzing.explicit_fuzz' 29 | print '----------------------------------------' 30 | basics.describe_fuzz 'develop.fuzzing.pack_unpack_fuzz' 31 | print '----------------------------------------' 32 | basics.describe_fuzz 'develop.fuzzing.requires_fuzz' 33 | print '----------------------------------------' 34 | basics.describe_fuzz 'develop.fuzzing.unique_fuzz' 35 | -------------------------------------------------------------------------------- /develop/fuzzing/unique_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_entity_list = {} ---@type evolved.entity[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local entity = evo.id() 26 | all_entity_list[i] = entity 27 | end 28 | 29 | for _, entity in ipairs(all_entity_list) do 30 | for _ = 0, math.random(0, #all_entity_list) do 31 | local fragment = all_entity_list[math.random(1, #all_entity_list)] 32 | evo.set(entity, fragment) 33 | end 34 | 35 | if math.random(1, 5) == 1 then 36 | evo.set(entity, evo.UNIQUE) 37 | end 38 | end 39 | 40 | --- 41 | --- 42 | --- 43 | --- 44 | --- 45 | 46 | for _, entity in ipairs(all_entity_list) do 47 | local entity_clone = evo.clone(entity) 48 | 49 | for fragment in evo.each(entity_clone) do 50 | assert(not evo.has(fragment, evo.UNIQUE)) 51 | end 52 | 53 | for fragment in evo.each(entity) do 54 | assert(evo.has(entity_clone, fragment) or evo.has(fragment, evo.UNIQUE)) 55 | end 56 | 57 | evo.destroy(entity_clone) 58 | end 59 | 60 | --- 61 | --- 62 | --- 63 | --- 64 | --- 65 | 66 | if math.random(1, 2) == 1 then 67 | evo.collect_garbage() 68 | end 69 | 70 | evo.destroy(__table_unpack(all_entity_list)) 71 | 72 | if math.random(1, 2) == 1 then 73 | evo.collect_garbage() 74 | end 75 | -------------------------------------------------------------------------------- /develop/fuzzing/explicit_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_entity_list = {} ---@type evolved.entity[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local entity = evo.id() 26 | all_entity_list[i] = entity 27 | end 28 | 29 | for _, entity in ipairs(all_entity_list) do 30 | for _ = 0, math.random(0, #all_entity_list) do 31 | local fragment = all_entity_list[math.random(1, #all_entity_list)] 32 | evo.set(entity, fragment) 33 | end 34 | 35 | if math.random(1, 5) == 1 then 36 | evo.set(entity, evo.EXPLICIT) 37 | end 38 | end 39 | 40 | --- 41 | --- 42 | --- 43 | --- 44 | --- 45 | 46 | for _ = 1, 100 do 47 | local include_set = {} ---@type table 48 | local include_list = {} ---@type evolved.entity[] 49 | local include_count = 0 50 | 51 | for _ = 1, math.random(1, #all_entity_list) do 52 | local include = all_entity_list[math.random(1, #all_entity_list)] 53 | 54 | if not include_set[include] then 55 | include_count = include_count + 1 56 | include_set[include] = include_count 57 | include_list[include_count] = include 58 | end 59 | end 60 | 61 | local q = evo.builder():include(__table_unpack(include_list)):spawn() 62 | 63 | for chunk in evo.execute(q) do 64 | local fragment_list, fragment_count = chunk:fragments() 65 | for i = 1, fragment_count do 66 | local fragment = fragment_list[i] 67 | assert(include_set[fragment] or not evo.has(fragment, evo.EXPLICIT)) 68 | end 69 | end 70 | 71 | evo.destroy(q) 72 | end 73 | 74 | --- 75 | --- 76 | --- 77 | --- 78 | --- 79 | 80 | if math.random(1, 2) == 1 then 81 | evo.collect_garbage() 82 | end 83 | 84 | evo.destroy(__table_unpack(all_entity_list)) 85 | 86 | if math.random(1, 2) == 1 then 87 | evo.collect_garbage() 88 | end 89 | -------------------------------------------------------------------------------- /develop/testing/cancel_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | assert(evo.defer()) 5 | assert(evo.cancel()) 6 | end 7 | 8 | do 9 | assert(evo.defer()) 10 | assert(not evo.defer()) 11 | assert(not evo.cancel()) 12 | assert(evo.commit()) 13 | end 14 | 15 | do 16 | assert(evo.defer()) 17 | assert(not evo.defer()) 18 | assert(not evo.cancel()) 19 | assert(evo.cancel()) 20 | end 21 | 22 | do 23 | assert(evo.defer()) 24 | assert(not evo.defer()) 25 | assert(not evo.cancel()) 26 | assert(not evo.defer()) 27 | assert(not evo.cancel()) 28 | assert(evo.commit()) 29 | end 30 | 31 | do 32 | local e, f = evo.id(2) 33 | 34 | assert(evo.defer()) 35 | do 36 | evo.set(e, f) 37 | assert(not evo.has(e, f)) 38 | end 39 | assert(evo.cancel()) 40 | 41 | assert(not evo.has(e, f)) 42 | end 43 | 44 | do 45 | local e, f1, f2 = evo.id(3) 46 | 47 | assert(evo.defer()) 48 | do 49 | evo.set(e, f1) 50 | assert(not evo.has(e, f1)) 51 | 52 | assert(not evo.defer()) 53 | do 54 | evo.set(e, f2) 55 | assert(not evo.has(e, f2)) 56 | end 57 | assert(not evo.cancel()) 58 | end 59 | assert(evo.commit()) 60 | 61 | assert(evo.has(e, f1)) 62 | assert(not evo.has(e, f2)) 63 | end 64 | 65 | do 66 | local e, f1, f2 = evo.id(3) 67 | 68 | assert(evo.defer()) 69 | do 70 | evo.set(e, f1) 71 | assert(not evo.has(e, f1)) 72 | 73 | assert(not evo.defer()) 74 | do 75 | evo.set(e, f2) 76 | assert(not evo.has(e, f2)) 77 | end 78 | assert(not evo.cancel()) 79 | end 80 | assert(evo.cancel()) 81 | 82 | assert(not evo.has(e, f1)) 83 | assert(not evo.has(e, f2)) 84 | end 85 | 86 | do 87 | local e, f1, f2 = evo.id(3) 88 | 89 | assert(evo.defer()) 90 | do 91 | evo.set(e, f1) 92 | assert(not evo.has(e, f1)) 93 | 94 | assert(not evo.defer()) 95 | do 96 | evo.set(e, f2) 97 | assert(not evo.has(e, f2)) 98 | end 99 | assert(not evo.commit()) 100 | end 101 | assert(evo.cancel()) 102 | 103 | assert(not evo.has(e, f1)) 104 | assert(not evo.has(e, f2)) 105 | end 106 | -------------------------------------------------------------------------------- /develop/benchmarks/migration_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | evo.debug_mode(false) 5 | 6 | local N = 1000 7 | 8 | local F1, F2, F3, F4, F5 = evo.id(5) 9 | 10 | local Q1 = evo.builder():include(F1):spawn() 11 | 12 | print '----------------------------------------' 13 | 14 | basics.describe_bench(string.format('Migration Benchmarks: Defer Set | %d entities with 1 component', N), 15 | function() 16 | local id, set = evo.id, evo.set 17 | 18 | evo.defer() 19 | for _ = 1, N do 20 | local e = id() 21 | set(e, F1) 22 | end 23 | evo.commit() 24 | 25 | evo.batch_destroy(Q1) 26 | end) 27 | 28 | basics.describe_bench(string.format('Migration Benchmarks: Defer Set | %d entities with 3 components', N), 29 | function() 30 | local id, set = evo.id, evo.set 31 | 32 | evo.defer() 33 | for _ = 1, N do 34 | local e = id() 35 | set(e, F1) 36 | set(e, F2) 37 | set(e, F3) 38 | end 39 | evo.commit() 40 | 41 | evo.batch_destroy(Q1) 42 | end) 43 | 44 | basics.describe_bench(string.format('Migration Benchmarks: Defer Set | %d entities with 5 components', N), 45 | function() 46 | local id, set = evo.id, evo.set 47 | 48 | evo.defer() 49 | for _ = 1, N do 50 | local e = id() 51 | set(e, F1) 52 | set(e, F2) 53 | set(e, F3) 54 | set(e, F4) 55 | set(e, F5) 56 | end 57 | evo.commit() 58 | 59 | evo.batch_destroy(Q1) 60 | end) 61 | 62 | print '----------------------------------------' 63 | 64 | basics.describe_bench(string.format('Migration Benchmarks: Simple Set | %d entities with 1 component', N), 65 | function() 66 | local id, set = evo.id, evo.set 67 | 68 | for _ = 1, N do 69 | local e = id() 70 | set(e, F1) 71 | end 72 | 73 | evo.batch_destroy(Q1) 74 | end) 75 | 76 | basics.describe_bench(string.format('Migration Benchmarks: Simple Set | %d entities with 3 components', N), 77 | function() 78 | local id, set = evo.id, evo.set 79 | 80 | for _ = 1, N do 81 | local e = id() 82 | set(e, F1) 83 | set(e, F2) 84 | set(e, F3) 85 | end 86 | 87 | evo.batch_destroy(Q1) 88 | end) 89 | 90 | basics.describe_bench(string.format('Migration Benchmarks: Simple Set | %d entities with 5 components', N), 91 | function() 92 | local id, set = evo.id, evo.set 93 | 94 | for _ = 1, N do 95 | local e = id() 96 | set(e, F1) 97 | set(e, F2) 98 | set(e, F3) 99 | set(e, F4) 100 | set(e, F5) 101 | end 102 | 103 | evo.batch_destroy(Q1) 104 | end) 105 | -------------------------------------------------------------------------------- /develop/benchmarks/process_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | evo.debug_mode(false) 5 | 6 | local N = 10000 7 | 8 | print '----------------------------------------' 9 | 10 | basics.describe_bench(string.format('Process Benchmarks: Evolved AoS Processing | %d entities', N), 11 | function(w) 12 | evo.process(w) 13 | end, 14 | 15 | function() 16 | local wf = evo.builder() 17 | :set(evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 18 | :spawn() 19 | 20 | local pf = evo.builder():set(wf):spawn() 21 | local vf = evo.builder():set(wf):spawn() 22 | 23 | evo.multi_spawn(N, { 24 | [wf] = true, 25 | [pf] = { x = 0, y = 0, z = 0, w = 0 }, 26 | [vf] = { x = 0, y = 0, z = 0, w = 0 }, 27 | }) 28 | 29 | evo.builder() 30 | :set(wf) 31 | :set(evo.GROUP, wf) 32 | :set(evo.QUERY, evo.builder():set(wf):include(pf, vf):spawn()) 33 | :set(evo.EXECUTE, function(chunk, _, entity_count) 34 | local ps, vs = chunk:components(pf, vf) 35 | 36 | for i = 1, entity_count do 37 | local p, s = ps[i], vs[i] 38 | p.x = p.x + s.x 39 | p.y = p.y + s.y 40 | end 41 | end) 42 | :spawn() 43 | 44 | return wf 45 | end, 46 | 47 | function(w) 48 | evo.destroy(w) 49 | end) 50 | 51 | basics.describe_bench(string.format('Process Benchmarks: Evolved SoA Processing | %d entities', N), 52 | function(w) 53 | evo.process(w) 54 | end, 55 | 56 | function() 57 | local wf = evo.builder() 58 | :set(evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 59 | :spawn() 60 | 61 | local pxf = evo.builder():set(wf):spawn() 62 | local pyf = evo.builder():set(wf):spawn() 63 | local pzf = evo.builder():set(wf):spawn() 64 | local pwf = evo.builder():set(wf):spawn() 65 | local vxf = evo.builder():set(wf):spawn() 66 | local vyf = evo.builder():set(wf):spawn() 67 | local vzf = evo.builder():set(wf):spawn() 68 | local vwf = evo.builder():set(wf):spawn() 69 | 70 | evo.multi_spawn(N, { 71 | [wf] = true, 72 | [pxf] = 0, 73 | [pyf] = 0, 74 | [pzf] = 0, 75 | [pwf] = 0, 76 | [vxf] = 0, 77 | [vyf] = 0, 78 | [vzf] = 0, 79 | [vwf] = 0, 80 | }) 81 | 82 | evo.builder() 83 | :set(wf) 84 | :set(evo.GROUP, wf) 85 | :set(evo.QUERY, evo.builder():set(wf):include(pxf, pyf, vxf, vyf):spawn()) 86 | :set(evo.EXECUTE, function(chunk, _, entity_count) 87 | local pxs, pys = chunk:components(pxf, pyf) 88 | local vxs, vys = chunk:components(vxf, vyf) 89 | 90 | for i = 1, entity_count do 91 | pxs[i] = pxs[i] + vxs[i] 92 | pys[i] = pys[i] + vys[i] 93 | end 94 | end) 95 | :spawn() 96 | 97 | return wf 98 | end, 99 | 100 | function(w) 101 | evo.destroy(w) 102 | end) 103 | -------------------------------------------------------------------------------- /develop/fuzzing/batch_destroy_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_entity_list = {} ---@type evolved.entity[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local entity = evo.id() 26 | all_entity_list[i] = entity 27 | end 28 | 29 | for _, entity in ipairs(all_entity_list) do 30 | for _ = 0, math.random(0, #all_entity_list) do 31 | local fragment = all_entity_list[math.random(1, #all_entity_list)] 32 | evo.set(entity, fragment) 33 | end 34 | 35 | if math.random(1, 5) == 1 then 36 | evo.set(entity, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 37 | end 38 | 39 | if math.random(1, 5) == 1 then 40 | evo.set(entity, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) 41 | end 42 | end 43 | 44 | --- 45 | --- 46 | --- 47 | --- 48 | --- 49 | 50 | local should_be_destroyed_entity_set = {} ---@type table 51 | local should_be_destroyed_entity_list = {} ---@type evolved.entity[] 52 | local should_be_destroyed_entity_count = 0 ---@type integer 53 | 54 | local function collect_destroyed_entities_with(entity) 55 | local entity_destruction_policy = evo.get(entity, evo.DESTRUCTION_POLICY) 56 | or evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT 57 | 58 | if entity_destruction_policy == evo.DESTRUCTION_POLICY_DESTROY_ENTITY then 59 | for _, other_entity in ipairs(all_entity_list) do 60 | if evo.has(other_entity, entity) and not should_be_destroyed_entity_set[other_entity] then 61 | should_be_destroyed_entity_count = should_be_destroyed_entity_count + 1 62 | should_be_destroyed_entity_list[should_be_destroyed_entity_count] = other_entity 63 | should_be_destroyed_entity_set[other_entity] = should_be_destroyed_entity_count 64 | end 65 | end 66 | end 67 | end 68 | 69 | local destroying_include_list = {} ---@type evolved.entity[] 70 | 71 | for i = 1, math.random(1, #all_entity_list) do 72 | local destroying_include = all_entity_list[math.random(1, #all_entity_list)] 73 | destroying_include_list[i] = destroying_include 74 | end 75 | 76 | for _, entity in ipairs(all_entity_list) do 77 | if evo.has_all(entity, __table_unpack(destroying_include_list)) then 78 | collect_destroyed_entities_with(entity) 79 | end 80 | end 81 | 82 | do 83 | local r = math.random(1, 2) 84 | local q = evo.builder():include(__table_unpack(destroying_include_list)):spawn() 85 | 86 | if r == 1 then 87 | evo.batch_destroy(q) 88 | elseif r == 2 then 89 | assert(evo.defer()) 90 | evo.batch_destroy(q) 91 | assert(evo.commit()) 92 | end 93 | end 94 | 95 | --- 96 | --- 97 | --- 98 | --- 99 | --- 100 | 101 | local all_chunk_query = evo.spawn() 102 | 103 | for chunk in evo.execute(all_chunk_query) do 104 | assert(not chunk:has_any(__table_unpack(should_be_destroyed_entity_list))) 105 | for _, fragment in ipairs(chunk:fragments()) do 106 | assert(not evo.has_all(fragment, __table_unpack(destroying_include_list))) 107 | end 108 | end 109 | 110 | for _, destroyed_entity in ipairs(should_be_destroyed_entity_list) do 111 | assert(not evo.alive(destroyed_entity)) 112 | end 113 | 114 | --- 115 | --- 116 | --- 117 | --- 118 | --- 119 | 120 | if math.random(1, 2) == 1 then 121 | evo.collect_garbage() 122 | end 123 | 124 | evo.destroy(__table_unpack(all_entity_list)) 125 | 126 | if math.random(1, 2) == 1 then 127 | evo.collect_garbage() 128 | end 129 | -------------------------------------------------------------------------------- /develop/benchmarks/table_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | evo.debug_mode(false) 5 | 6 | local N = 1000 7 | 8 | local F1, F2, F3, F4, F5 = evo.id(5) 9 | 10 | print '----------------------------------------' 11 | 12 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables', N), 13 | function(tables) 14 | for i = 1, N do 15 | local t = {} 16 | tables[i] = t 17 | end 18 | end, function() 19 | return {} 20 | end) 21 | 22 | basics.describe_bench(string.format('Table Benchmarks: Allocate and Collect %d tables', N), 23 | function(tables) 24 | for i = 1, N do 25 | local t = {} 26 | tables[i] = t 27 | end 28 | 29 | for i = 1, N do 30 | tables[i] = nil 31 | end 32 | 33 | collectgarbage('collect') 34 | end, function() 35 | return {} 36 | end) 37 | 38 | print '----------------------------------------' 39 | 40 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 1 component / AoS', N), 41 | function(tables) 42 | for i = 1, N do 43 | local e = {} 44 | e[F1] = true 45 | tables[i] = e 46 | end 47 | end, function() 48 | return {} 49 | end) 50 | 51 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 3 components / AoS', N), 52 | function(tables) 53 | for i = 1, N do 54 | local e = {} 55 | e[F1] = true 56 | e[F2] = true 57 | e[F3] = true 58 | tables[i] = e 59 | end 60 | end, function() 61 | return {} 62 | end) 63 | 64 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 5 components / AoS', N), 65 | function(tables) 66 | for i = 1, N do 67 | local e = {} 68 | e[F1] = true 69 | e[F2] = true 70 | e[F3] = true 71 | e[F4] = true 72 | e[F5] = true 73 | tables[i] = e 74 | end 75 | end, function() 76 | return {} 77 | end) 78 | 79 | print '----------------------------------------' 80 | 81 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 1 component / SoA', N), 82 | function(tables) 83 | local fs1 = {} 84 | for i = 1, N do 85 | local e = {} 86 | fs1[i] = true 87 | tables[i] = e 88 | end 89 | tables[F1] = fs1 90 | end, function() 91 | return {} 92 | end) 93 | 94 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 3 components / SoA', N), 95 | function(tables) 96 | local fs1 = {} 97 | local fs2 = {} 98 | local fs3 = {} 99 | for i = 1, N do 100 | local e = {} 101 | fs1[i] = true 102 | fs2[i] = true 103 | fs3[i] = true 104 | tables[i] = e 105 | end 106 | tables[F1] = fs1 107 | tables[F2] = fs2 108 | tables[F3] = fs3 109 | end, function() 110 | return {} 111 | end) 112 | 113 | basics.describe_bench(string.format('Table Benchmarks: Allocate %d tables with 5 components / SoA', N), 114 | function(tables) 115 | local fs1 = {} 116 | local fs2 = {} 117 | local fs3 = {} 118 | local fs4 = {} 119 | local fs5 = {} 120 | for i = 1, N do 121 | local e = {} 122 | fs1[i] = true 123 | fs2[i] = true 124 | fs3[i] = true 125 | fs4[i] = true 126 | fs5[i] = true 127 | tables[i] = e 128 | end 129 | tables[F1] = fs1 130 | tables[F2] = fs2 131 | tables[F3] = fs3 132 | tables[F4] = fs4 133 | tables[F5] = fs5 134 | end, function() 135 | return {} 136 | end) 137 | -------------------------------------------------------------------------------- /develop/fuzzing/destroy_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_entity_list = {} ---@type evolved.entity[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local entity = evo.id() 26 | all_entity_list[i] = entity 27 | end 28 | 29 | for _, entity in ipairs(all_entity_list) do 30 | for _ = 0, math.random(0, #all_entity_list) do 31 | local fragment = all_entity_list[math.random(1, #all_entity_list)] 32 | evo.set(entity, fragment) 33 | end 34 | 35 | if math.random(1, 5) == 1 then 36 | evo.set(entity, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 37 | end 38 | 39 | if math.random(1, 5) == 1 then 40 | evo.set(entity, evo.DESTRUCTION_POLICY, evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT) 41 | end 42 | end 43 | 44 | --- 45 | --- 46 | --- 47 | --- 48 | --- 49 | 50 | local should_be_destroyed_entity_set = {} ---@type table 51 | local should_be_destroyed_entity_list = {} ---@type evolved.entity[] 52 | local should_be_destroyed_entity_count = 0 ---@type integer 53 | 54 | local function collect_destroyed_entities_with(entity) 55 | local entity_destruction_policy = evo.get(entity, evo.DESTRUCTION_POLICY) 56 | or evo.DESTRUCTION_POLICY_REMOVE_FRAGMENT 57 | 58 | if entity_destruction_policy == evo.DESTRUCTION_POLICY_DESTROY_ENTITY then 59 | for _, other_entity in ipairs(all_entity_list) do 60 | if evo.has(other_entity, entity) and not should_be_destroyed_entity_set[other_entity] then 61 | should_be_destroyed_entity_count = should_be_destroyed_entity_count + 1 62 | should_be_destroyed_entity_list[should_be_destroyed_entity_count] = other_entity 63 | should_be_destroyed_entity_set[other_entity] = should_be_destroyed_entity_count 64 | end 65 | end 66 | end 67 | end 68 | 69 | local destroying_entity_list = {} ---@type evolved.entity[] 70 | 71 | for i = 1, math.random(1, #all_entity_list) do 72 | local destroying_entity = all_entity_list[math.random(1, #all_entity_list)] 73 | destroying_entity_list[i] = destroying_entity 74 | collect_destroyed_entities_with(destroying_entity) 75 | end 76 | 77 | do 78 | local r = math.random(1, 4) 79 | 80 | if r == 1 then 81 | evo.destroy(__table_unpack(destroying_entity_list)) 82 | elseif r == 2 then 83 | assert(evo.defer()) 84 | evo.destroy(__table_unpack(destroying_entity_list)) 85 | assert(evo.commit()) 86 | elseif r == 3 then 87 | for _, destroying_entity in ipairs(destroying_entity_list) do 88 | evo.destroy(destroying_entity) 89 | end 90 | elseif r == 4 then 91 | assert(evo.defer()) 92 | for _, destroying_entity in ipairs(destroying_entity_list) do 93 | evo.destroy(destroying_entity) 94 | end 95 | assert(evo.commit()) 96 | end 97 | end 98 | 99 | --- 100 | --- 101 | --- 102 | --- 103 | --- 104 | 105 | local all_chunk_query = evo.spawn() 106 | 107 | for chunk in evo.execute(all_chunk_query) do 108 | assert(not chunk:has_any(__table_unpack(destroying_entity_list))) 109 | assert(not chunk:has_any(__table_unpack(should_be_destroyed_entity_list))) 110 | end 111 | 112 | for _, destroying_entity in ipairs(destroying_entity_list) do 113 | assert(not evo.alive(destroying_entity)) 114 | end 115 | 116 | for _, destroyed_entity in ipairs(should_be_destroyed_entity_list) do 117 | assert(not evo.alive(destroyed_entity)) 118 | end 119 | 120 | --- 121 | --- 122 | --- 123 | --- 124 | --- 125 | 126 | if math.random(1, 2) == 1 then 127 | evo.collect_garbage() 128 | end 129 | 130 | evo.destroy(__table_unpack(all_entity_list)) 131 | 132 | if math.random(1, 2) == 1 then 133 | evo.collect_garbage() 134 | end 135 | -------------------------------------------------------------------------------- /develop/fuzzing/requires_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_fragment_list = {} ---@type evolved.fragment[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local fragment = evo.builder() 26 | :default(42) 27 | :spawn() 28 | all_fragment_list[i] = fragment 29 | end 30 | 31 | for _, fragment in ipairs(all_fragment_list) do 32 | if math.random(1, 2) == 1 then 33 | for _ = 0, math.random(0, #all_fragment_list) do 34 | local require_list = evo.get(fragment, evo.REQUIRES) or {} 35 | require_list[#require_list + 1] = all_fragment_list[math.random(1, #all_fragment_list)] 36 | evo.set(fragment, evo.REQUIRES, require_list) 37 | end 38 | end 39 | end 40 | 41 | local all_entity_list = {} ---@type evolved.entity[] 42 | 43 | for i = 1, math.random(1, 10) do 44 | local entity = evo.id() 45 | all_entity_list[i] = entity 46 | 47 | for _ = 0, math.random(0, #all_fragment_list) do 48 | local fragment = all_fragment_list[math.random(1, #all_fragment_list)] 49 | 50 | if math.random(1, 2) == 1 then 51 | evo.set(entity, fragment, 42) 52 | else 53 | local query = evo.builder() 54 | :include(all_fragment_list[math.random(1, #all_fragment_list)]) 55 | :spawn() 56 | evo.batch_set(query, fragment, 42) 57 | evo.destroy(query) 58 | end 59 | end 60 | end 61 | 62 | for _ = 1, math.random(1, #all_entity_list) do 63 | local components = {} 64 | for _ = 1, math.random(1, #all_fragment_list) do 65 | local fragment = all_fragment_list[math.random(1, #all_fragment_list)] 66 | components[fragment] = 42 67 | end 68 | all_entity_list[#all_entity_list + 1] = evo.spawn(components) 69 | end 70 | 71 | for _ = 1, math.random(1, #all_entity_list) do 72 | local prefab = all_entity_list[math.random(1, #all_entity_list)] 73 | all_entity_list[#all_entity_list + 1] = evo.clone(prefab) 74 | end 75 | 76 | --- 77 | --- 78 | --- 79 | --- 80 | --- 81 | 82 | local function collect_required_fragments_for(fragment, req_fragment_set, req_fragment_list) 83 | local fragment_requires = evo.get(fragment, evo.REQUIRES) or {} 84 | for _, required_fragment in ipairs(fragment_requires) do 85 | if not req_fragment_set[required_fragment] then 86 | req_fragment_set[required_fragment] = true 87 | req_fragment_list[#req_fragment_list + 1] = required_fragment 88 | collect_required_fragments_for(required_fragment, req_fragment_set, req_fragment_list) 89 | end 90 | end 91 | end 92 | 93 | for _, entity in ipairs(all_entity_list) do 94 | for fragment in evo.each(entity) do 95 | local req_fragment_list = {} 96 | collect_required_fragments_for(fragment, {}, req_fragment_list) 97 | for _, required_fragment in ipairs(req_fragment_list) do 98 | assert(evo.has(entity, required_fragment)) 99 | local required_component = evo.get(entity, required_fragment) 100 | assert(required_component == 42) 101 | end 102 | end 103 | end 104 | 105 | --- 106 | --- 107 | --- 108 | --- 109 | --- 110 | 111 | if math.random(1, 2) == 1 then 112 | evo.collect_garbage() 113 | end 114 | 115 | if math.random(1, 2) == 1 then 116 | evo.destroy(__table_unpack(all_entity_list)) 117 | if math.random(1, 2) == 1 then 118 | evo.collect_garbage() 119 | end 120 | evo.destroy(__table_unpack(all_fragment_list)) 121 | else 122 | evo.destroy(__table_unpack(all_fragment_list)) 123 | if math.random(1, 2) == 1 then 124 | evo.collect_garbage() 125 | end 126 | evo.destroy(__table_unpack(all_entity_list)) 127 | end 128 | 129 | if math.random(1, 2) == 1 then 130 | evo.collect_garbage() 131 | end 132 | -------------------------------------------------------------------------------- /develop/testing/system_as_query_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local f1, f2, f3 = evo.id(3) 5 | 6 | local q1e3 = evo.builder():include(f1):exclude(f3):spawn() 7 | local q2e3 = evo.builder():include(f2):exclude(f3):spawn() 8 | 9 | local e1 = evo.builder():set(f1, 1):spawn() 10 | local e12 = evo.builder():set(f1, 11):set(f2, 12):spawn() 11 | local e2 = evo.builder():set(f2, 2):spawn() 12 | local e23 = evo.builder():set(f2, 23):set(f3, 3):spawn() 13 | 14 | local c1 = evo.chunk(f1) 15 | local c12 = evo.chunk(f1, f2) 16 | local c2 = evo.chunk(f2) 17 | 18 | do 19 | local _, entity_list, entity_count = evo.chunk(f2, f3) 20 | assert(entity_count == 1 and entity_list[1] == e23) 21 | end 22 | 23 | do 24 | local entity_sum = 0 25 | 26 | local s = evo.builder() 27 | :query(q1e3) 28 | :execute(function(chunk, entity_list, entity_count) 29 | for i = 1, entity_count do 30 | entity_sum = entity_sum + entity_list[i] 31 | end 32 | 33 | if chunk == c1 then 34 | assert(entity_count == 1) 35 | assert(entity_list[1] == e1) 36 | elseif chunk == c12 then 37 | assert(entity_count == 1) 38 | assert(entity_list[1] == e12) 39 | else 40 | assert(false, "Unexpected chunk: " .. tostring(chunk)) 41 | end 42 | end):spawn() 43 | 44 | evo.process(s) 45 | 46 | assert(entity_sum == e1 + e12) 47 | end 48 | 49 | do 50 | local entity_sum = 0 51 | 52 | local s = evo.builder() 53 | :query(q2e3) 54 | :execute(function(chunk, entity_list, entity_count) 55 | for i = 1, entity_count do 56 | entity_sum = entity_sum + entity_list[i] 57 | end 58 | 59 | if chunk == c12 then 60 | assert(entity_count == 1) 61 | assert(entity_list[1] == e12) 62 | elseif chunk == c2 then 63 | assert(entity_count == 1) 64 | assert(entity_list[1] == e2) 65 | else 66 | assert(false, "Unexpected chunk: " .. tostring(chunk)) 67 | end 68 | end):spawn() 69 | 70 | evo.process(s) 71 | 72 | assert(entity_sum == e12 + e2) 73 | end 74 | 75 | do 76 | local entity_sum = 0 77 | 78 | local s = evo.builder() 79 | :include(f1) 80 | :exclude(f3) 81 | :execute(function(chunk, entity_list, entity_count) 82 | for i = 1, entity_count do 83 | entity_sum = entity_sum + entity_list[i] 84 | end 85 | 86 | if chunk == c1 then 87 | assert(entity_count == 1) 88 | assert(entity_list[1] == e1) 89 | elseif chunk == c12 then 90 | assert(entity_count == 1) 91 | assert(entity_list[1] == e12) 92 | else 93 | assert(false, "Unexpected chunk: " .. tostring(chunk)) 94 | end 95 | end):spawn() 96 | 97 | evo.process(s) 98 | 99 | assert(entity_sum == e1 + e12) 100 | end 101 | 102 | do 103 | local entity_sum = 0 104 | 105 | local s = evo.builder() 106 | :include(f2) 107 | :exclude(f3) 108 | :execute(function(chunk, entity_list, entity_count) 109 | for i = 1, entity_count do 110 | entity_sum = entity_sum + entity_list[i] 111 | end 112 | 113 | if chunk == c12 then 114 | assert(entity_count == 1) 115 | assert(entity_list[1] == e12) 116 | elseif chunk == c2 then 117 | assert(entity_count == 1) 118 | assert(entity_list[1] == e2) 119 | else 120 | assert(false, "Unexpected chunk: " .. tostring(chunk)) 121 | end 122 | end):spawn() 123 | 124 | evo.process(s) 125 | 126 | assert(entity_sum == e12 + e2) 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /develop/basics.lua: -------------------------------------------------------------------------------- 1 | local basics = {} 2 | 3 | local MIN_FUZZ_SECS = 0.5 4 | local MIN_BENCH_SECS = 0.1 5 | local MIN_WARMUP_SECS = 0.1 6 | 7 | local MIN_FUZZ_ITERS = 100 8 | local MIN_BENCH_ITERS = 100 9 | local MIN_WARMUP_ITERS = 100 10 | 11 | local __table_pack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.pack or function(...) 14 | return { n = select('#', ...), ... } 15 | end 16 | end)() 17 | 18 | local __table_unpack = (function() 19 | ---@diagnostic disable-next-line: deprecated 20 | return table.unpack or unpack 21 | end)() 22 | 23 | ---@param pattern string 24 | function basics.unload(pattern) 25 | for name, _ in pairs(package.loaded) do 26 | if name:match(pattern) then 27 | package.loaded[name] = nil 28 | end 29 | end 30 | end 31 | 32 | ---@param modname string 33 | function basics.describe_fuzz(modname) 34 | basics.unload('evolved') 35 | 36 | print(string.format('| %s ... |', modname)) 37 | 38 | collectgarbage('collect') 39 | collectgarbage('stop') 40 | 41 | do 42 | local iters = 0 43 | 44 | local start_s = os.clock() 45 | local start_kb = collectgarbage('count') 46 | 47 | local success, result = pcall(function() 48 | repeat 49 | iters = iters + 1 50 | basics.unload(modname) 51 | require(modname) 52 | until iters >= MIN_FUZZ_ITERS and os.clock() - start_s >= MIN_FUZZ_SECS 53 | end) 54 | 55 | local finish_s = os.clock() 56 | local finish_kb = collectgarbage('count') 57 | 58 | if success then 59 | print(string.format('|-- PASS | us: %.2f | op/s: %.2f | kb/i: %.2f | iters: %d', 60 | (finish_s - start_s) * 1e6 / iters, 61 | iters / (finish_s - start_s), 62 | (finish_kb - start_kb) / iters, 63 | iters)) 64 | else 65 | print('|-- FUZZ FAIL: ' .. result) 66 | end 67 | end 68 | 69 | collectgarbage('restart') 70 | collectgarbage('collect') 71 | end 72 | 73 | ---@param name string 74 | ---@param loop fun(...): ... 75 | ---@param init? fun(): ... 76 | ---@param fini? fun(...): ... 77 | function basics.describe_bench(name, loop, init, fini) 78 | basics.unload('evolved') 79 | 80 | print(string.format('| %s ... |', name)) 81 | 82 | local state = init and __table_pack(init()) or {} 83 | 84 | do 85 | local iters = 0 86 | 87 | local warmup_s = os.clock() 88 | 89 | local success, result = pcall(function() 90 | repeat 91 | iters = iters + 1 92 | loop(__table_unpack(state)) 93 | until iters >= MIN_WARMUP_ITERS and os.clock() - warmup_s > MIN_WARMUP_SECS 94 | end) 95 | 96 | if not success then 97 | print('|-- WARMUP FAIL: ' .. result) 98 | return 99 | end 100 | end 101 | 102 | collectgarbage('collect') 103 | collectgarbage('stop') 104 | 105 | do 106 | local iters = 0 107 | 108 | local start_s = os.clock() 109 | local start_kb = collectgarbage('count') 110 | 111 | local success, result = pcall(function() 112 | repeat 113 | iters = iters + 1 114 | loop(__table_unpack(state)) 115 | until iters >= MIN_BENCH_ITERS and os.clock() - start_s > MIN_BENCH_SECS 116 | end) 117 | 118 | local finish_s = os.clock() 119 | local finish_kb = collectgarbage('count') 120 | 121 | if success then 122 | print(string.format('|-- PASS | us: %.2f | op/s: %.2f | kb/i: %.2f | iters: %d', 123 | (finish_s - start_s) * 1e6 / iters, 124 | iters / (finish_s - start_s), 125 | (finish_kb - start_kb) / iters, 126 | iters)) 127 | else 128 | print('|-- LOOP FAIL: ' .. result) 129 | end 130 | end 131 | 132 | if fini then 133 | local success, result = pcall(function() 134 | fini(__table_unpack(state)) 135 | end) 136 | 137 | if not success then 138 | print('|-- FINI FAIL: ' .. result) 139 | end 140 | end 141 | 142 | collectgarbage('restart') 143 | collectgarbage('collect') 144 | end 145 | 146 | return basics 147 | -------------------------------------------------------------------------------- /example/main.lua: -------------------------------------------------------------------------------- 1 | local evolved = require 'evolved' 2 | 3 | local STAGES = { 4 | ON_SETUP = evolved.builder() 5 | :name('STAGES.ON_SETUP') 6 | :build(), 7 | ON_UPDATE = evolved.builder() 8 | :name('STAGES.ON_UPDATE') 9 | :build(), 10 | ON_RENDER = evolved.builder() 11 | :name('STAGES.ON_RENDER') 12 | :build(), 13 | } 14 | 15 | local UNIFORMS = { 16 | DELTA_TIME = 1.0 / 60.0, 17 | } 18 | 19 | local FRAGMENTS = { 20 | POSITION_X = evolved.builder() 21 | :name('FRAGMENTS.POSITION_X') 22 | :default(0) 23 | :build(), 24 | POSITION_Y = evolved.builder() 25 | :name('FRAGMENTS.POSITION_Y') 26 | :default(0) 27 | :build(), 28 | VELOCITY_X = evolved.builder() 29 | :name('FRAGMENTS.VELOCITY_X') 30 | :default(0) 31 | :build(), 32 | VELOCITY_Y = evolved.builder() 33 | :name('FRAGMENTS.VELOCITY_Y') 34 | :default(0) 35 | :build(), 36 | } 37 | 38 | local PREFABS = { 39 | CIRCLE = evolved.builder() 40 | :name('PREFABS.CIRCLE') 41 | :prefab() 42 | :set(FRAGMENTS.POSITION_X) 43 | :set(FRAGMENTS.POSITION_Y) 44 | :set(FRAGMENTS.VELOCITY_X) 45 | :set(FRAGMENTS.VELOCITY_Y) 46 | :build(), 47 | } 48 | 49 | --- 50 | --- 51 | --- 52 | --- 53 | --- 54 | 55 | evolved.builder() 56 | :name('SYSTEMS.STARTUP') 57 | :group(STAGES.ON_SETUP) 58 | :prologue(function() 59 | local screen_width, screen_height = love.graphics.getDimensions() 60 | 61 | local circle_list, circle_count = evolved.multi_clone(100, PREFABS.CIRCLE) 62 | 63 | for i = 1, circle_count do 64 | local circle = circle_list[i] 65 | 66 | local px = math.random() * screen_width 67 | local py = math.random() * screen_height 68 | 69 | local vx = math.random(-100, 100) 70 | local vy = math.random(-100, 100) 71 | 72 | evolved.set(circle, FRAGMENTS.POSITION_X, px) 73 | evolved.set(circle, FRAGMENTS.POSITION_Y, py) 74 | 75 | evolved.set(circle, FRAGMENTS.VELOCITY_X, vx) 76 | evolved.set(circle, FRAGMENTS.VELOCITY_Y, vy) 77 | end 78 | end):build() 79 | 80 | evolved.builder() 81 | :name('SYSTEMS.MOVEMENT') 82 | :group(STAGES.ON_UPDATE) 83 | :include(FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) 84 | :include(FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y) 85 | :execute(function(chunk, _, entity_count) 86 | local delta_time = UNIFORMS.DELTA_TIME 87 | local screen_width, screen_height = love.graphics.getDimensions() 88 | 89 | ---@type number[], number[] 90 | local position_xs, position_ys = chunk:components( 91 | FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) 92 | 93 | ---@type number[], number[] 94 | local velocity_xs, velocity_ys = chunk:components( 95 | FRAGMENTS.VELOCITY_X, FRAGMENTS.VELOCITY_Y) 96 | 97 | for i = 1, entity_count do 98 | local px, py = position_xs[i], position_ys[i] 99 | local vx, vy = velocity_xs[i], velocity_ys[i] 100 | 101 | px = px + vx * delta_time 102 | py = py + vy * delta_time 103 | 104 | if px < 0 and vx < 0 then 105 | vx = -vx 106 | elseif px > screen_width and vx > 0 then 107 | vx = -vx 108 | end 109 | 110 | if py < 0 and vy < 0 then 111 | vy = -vy 112 | elseif py > screen_height and vy > 0 then 113 | vy = -vy 114 | end 115 | 116 | position_xs[i], position_ys[i] = px, py 117 | velocity_xs[i], velocity_ys[i] = vx, vy 118 | end 119 | end):build() 120 | 121 | evolved.builder() 122 | :name('SYSTEMS.RENDERING') 123 | :group(STAGES.ON_RENDER) 124 | :include(FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) 125 | :execute(function(chunk, _, entity_count) 126 | ---@type number[], number[] 127 | local position_xs, position_ys = chunk:components( 128 | FRAGMENTS.POSITION_X, FRAGMENTS.POSITION_Y) 129 | 130 | for i = 1, entity_count do 131 | local x, y = position_xs[i], position_ys[i] 132 | love.graphics.circle('fill', x, y, 10) 133 | end 134 | end):build() 135 | 136 | evolved.builder() 137 | :name('SYSTEMS.DEBUGGING') 138 | :group(STAGES.ON_RENDER) 139 | :epilogue(function() 140 | local fps = love.timer.getFPS() 141 | local mem = collectgarbage('count') 142 | love.graphics.print(string.format('FPS: %d', fps), 10, 10) 143 | love.graphics.print(string.format('MEM: %d KB', mem), 10, 30) 144 | end):build() 145 | 146 | --- 147 | --- 148 | --- 149 | --- 150 | --- 151 | 152 | ---@type love.load 153 | function love.load() 154 | evolved.process(STAGES.ON_SETUP) 155 | end 156 | 157 | ---@type love.update 158 | function love.update(dt) 159 | UNIFORMS.DELTA_TIME = dt 160 | evolved.process(STAGES.ON_UPDATE) 161 | end 162 | 163 | ---@type love.draw 164 | function love.draw() 165 | evolved.process(STAGES.ON_RENDER) 166 | end 167 | 168 | ---@type love.keypressed 169 | function love.keypressed(key) 170 | if key == 'escape' then 171 | love.event.quit() 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /develop/fuzzing/execute_fuzz.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | evo.debug_mode(true) 4 | 5 | --- 6 | --- 7 | --- 8 | --- 9 | --- 10 | 11 | local __table_unpack = (function() 12 | ---@diagnostic disable-next-line: deprecated 13 | return table.unpack or unpack 14 | end)() 15 | 16 | --- 17 | --- 18 | --- 19 | --- 20 | --- 21 | 22 | local all_fragment_list = {} ---@type evolved.fragment[] 23 | 24 | for i = 1, math.random(1, 10) do 25 | local fragment = evo.id() 26 | all_fragment_list[i] = fragment 27 | end 28 | 29 | ---@param query evolved.query 30 | local function generate_query(query) 31 | local include_set = {} 32 | local include_list = {} 33 | local include_count = 0 34 | 35 | for _ = 1, math.random(0, #all_fragment_list) do 36 | local include = all_fragment_list[math.random(1, #all_fragment_list)] 37 | 38 | if not include_set[include] then 39 | include_count = include_count + 1 40 | include_set[include] = include_count 41 | include_list[include_count] = include 42 | end 43 | end 44 | 45 | local exclude_set = {} 46 | local exclude_list = {} 47 | local exclude_count = 0 48 | 49 | for _ = 1, math.random(0, #all_fragment_list) do 50 | local exclude = all_fragment_list[math.random(1, #all_fragment_list)] 51 | 52 | if not exclude_set[exclude] then 53 | exclude_count = exclude_count + 1 54 | exclude_set[exclude] = exclude_count 55 | exclude_list[exclude_count] = exclude 56 | end 57 | end 58 | 59 | if include_count > 0 then 60 | evo.set(query, evo.INCLUDES, include_list) 61 | end 62 | 63 | if exclude_count > 0 then 64 | evo.set(query, evo.EXCLUDES, exclude_list) 65 | end 66 | end 67 | 68 | ---@param query_count integer 69 | ---@return evolved.query[] query_list 70 | ---@return integer query_count 71 | ---@nodiscard 72 | local function generate_queries(query_count) 73 | local query_list = {} ---@type evolved.query[] 74 | 75 | for i = 1, query_count do 76 | local query = evo.id() 77 | query_list[i] = query 78 | generate_query(query) 79 | end 80 | 81 | return query_list, query_count 82 | end 83 | 84 | ---@param entity evolved.entity 85 | local function generate_entity(entity) 86 | for _ = 0, math.random(0, #all_fragment_list) do 87 | local fragment = all_fragment_list[math.random(1, #all_fragment_list)] 88 | evo.set(entity, fragment) 89 | end 90 | end 91 | 92 | ---@param entity_count integer 93 | ---@return evolved.entity[] entity_list 94 | ---@return integer entity_count 95 | local function generate_entities(entity_count) 96 | local entity_list = {} ---@type evolved.entity[] 97 | 98 | for i = 1, entity_count do 99 | local entity = evo.id() 100 | entity_list[i] = entity 101 | generate_entity(entity) 102 | end 103 | 104 | return entity_list, entity_count 105 | end 106 | 107 | local pre_query_list, pre_query_count = generate_queries(math.random(1, 10)) 108 | local pre_entity_list, pre_entity_count = generate_entities(math.random(1, 10)) 109 | 110 | for _ = 1, math.random(1, 5) do 111 | local fragment = all_fragment_list[math.random(1, #all_fragment_list)] 112 | 113 | evo.set(fragment, evo.EXPLICIT) 114 | end 115 | 116 | for _ = 1, math.random(1, 5) do 117 | local query = pre_query_list[math.random(1, pre_query_count)] 118 | 119 | if math.random(1, 2) == 1 then 120 | generate_query(query) 121 | else 122 | if math.random(1, 2) == 1 then 123 | evo.remove(query, evo.INCLUDES) 124 | else 125 | evo.remove(query, evo.EXCLUDES) 126 | end 127 | end 128 | end 129 | 130 | local post_query_list, post_query_count = generate_queries(math.random(1, 10)) 131 | local post_entity_list, post_entity_count = generate_entities(math.random(1, 10)) 132 | 133 | --- 134 | --- 135 | --- 136 | --- 137 | --- 138 | 139 | local all_query_list = {} 140 | local all_query_count = 0 141 | local all_entity_list = {} 142 | local all_entity_count = 0 143 | 144 | for i = 1, pre_query_count do 145 | all_query_count = all_query_count + 1 146 | all_query_list[all_query_count] = pre_query_list[i] 147 | end 148 | 149 | for i = 1, post_query_count do 150 | all_query_count = all_query_count + 1 151 | all_query_list[all_query_count] = post_query_list[i] 152 | end 153 | 154 | for i = 1, pre_entity_count do 155 | all_entity_count = all_entity_count + 1 156 | all_entity_list[all_entity_count] = pre_entity_list[i] 157 | end 158 | 159 | for i = 1, post_entity_count do 160 | all_entity_count = all_entity_count + 1 161 | all_entity_list[all_entity_count] = post_entity_list[i] 162 | end 163 | 164 | --- 165 | --- 166 | --- 167 | --- 168 | --- 169 | 170 | local function execute_query(query) 171 | local query_chunk_set = {} 172 | local query_entity_set = {} 173 | 174 | local query_include_list = evo.get(query, evo.INCLUDES) or {} 175 | local query_exclude_list = evo.get(query, evo.EXCLUDES) or {} 176 | 177 | local query_include_set = {} 178 | for _, include in ipairs(query_include_list) do 179 | query_include_set[include] = true 180 | end 181 | 182 | for chunk, entity_list, entity_count in evo.execute(query) do 183 | assert(not query_chunk_set[chunk]) 184 | query_chunk_set[chunk] = true 185 | 186 | for i = 1, entity_count do 187 | local entity = entity_list[i] 188 | assert(not query_entity_set[entity]) 189 | query_entity_set[entity] = true 190 | end 191 | 192 | assert(chunk:has_all(__table_unpack(query_include_list))) 193 | assert(not chunk:has_any(__table_unpack(query_exclude_list))) 194 | end 195 | 196 | for i = 1, all_entity_count do 197 | local entity = all_entity_list[i] 198 | 199 | local is_entity_matched = 200 | evo.has_all(entity, __table_unpack(query_include_list)) 201 | and not evo.has_any(entity, __table_unpack(query_exclude_list)) 202 | 203 | for fragment in evo.each(entity) do 204 | if evo.has(fragment, evo.EXPLICIT) and not query_include_set[fragment] then 205 | is_entity_matched = false 206 | end 207 | end 208 | 209 | if is_entity_matched then 210 | assert(query_entity_set[entity]) 211 | else 212 | assert(not query_entity_set[entity]) 213 | end 214 | end 215 | end 216 | 217 | for i = 1, all_query_count do 218 | execute_query(all_query_list[i]) 219 | end 220 | 221 | --- 222 | --- 223 | --- 224 | --- 225 | --- 226 | 227 | for _ = 1, math.random(1, 5) do 228 | local fragment = all_fragment_list[math.random(1, #all_fragment_list)] 229 | 230 | evo.set(fragment, evo.EXPLICIT) 231 | end 232 | 233 | for _ = 1, math.random(1, 5) do 234 | local query = pre_query_list[math.random(1, pre_query_count)] 235 | 236 | if math.random(1, 2) == 1 then 237 | generate_query(query) 238 | else 239 | if math.random(1, 2) == 1 then 240 | evo.remove(query, evo.INCLUDES) 241 | else 242 | evo.remove(query, evo.EXCLUDES) 243 | end 244 | end 245 | end 246 | 247 | for i = 1, all_query_count do 248 | execute_query(all_query_list[i]) 249 | end 250 | 251 | --- 252 | --- 253 | --- 254 | --- 255 | --- 256 | 257 | if math.random(1, 2) == 1 then 258 | evo.collect_garbage() 259 | end 260 | 261 | evo.destroy(__table_unpack(all_query_list)) 262 | evo.destroy(__table_unpack(all_entity_list)) 263 | evo.destroy(__table_unpack(all_fragment_list)) 264 | 265 | if math.random(1, 2) == 1 then 266 | evo.collect_garbage() 267 | end 268 | -------------------------------------------------------------------------------- /develop/testing/requires_fragment_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local f1, f2 = evo.id(2) 5 | evo.set(f1, evo.REQUIRES) 6 | evo.set(f2, evo.REQUIRES, evo.get(f1, evo.REQUIRES)) 7 | local f1_rs = evo.get(f1, evo.REQUIRES) 8 | local f2_rs = evo.get(f2, evo.REQUIRES) 9 | assert(f1_rs and f2_rs and #f1_rs == 0 and #f2_rs == 0 and f1_rs ~= f2_rs) 10 | end 11 | 12 | do 13 | local f1, f2 = evo.id(2) 14 | local f3 = evo.builder():require(f1):require(f2):spawn() 15 | local f3_rs = evo.get(f3, evo.REQUIRES) 16 | assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2) 17 | end 18 | 19 | do 20 | local f1, f2 = evo.id(2) 21 | local f3 = evo.builder():require(f1, f2):spawn() 22 | local f3_rs = evo.get(f3, evo.REQUIRES) 23 | assert(f3_rs and #f3_rs == 2 and f3_rs[1] == f1 and f3_rs[2] == f2) 24 | end 25 | 26 | do 27 | local f1, f2, f3 = evo.id(3) 28 | evo.set(f1, evo.REQUIRES, { f2 }) 29 | evo.set(f3, evo.REQUIRES, { f1, f2 }) 30 | 31 | do 32 | local e = evo.id() 33 | evo.set(e, f1) 34 | assert(evo.has(e, f2)) 35 | assert(evo.get(e, f2) == true) 36 | end 37 | 38 | do 39 | local e = evo.builder():set(f1):spawn() 40 | assert(evo.has(e, f2)) 41 | assert(evo.get(e, f2) == true) 42 | end 43 | 44 | do 45 | local e = evo.spawn { [f1] = true } 46 | assert(evo.has(e, f2)) 47 | assert(evo.get(e, f2) == true) 48 | 49 | evo.remove(e, f2) 50 | assert(not evo.has(e, f2)) 51 | 52 | local e2 = evo.clone(e) 53 | assert(evo.has(e2, f2)) 54 | assert(evo.get(e2, f2) == true) 55 | 56 | local e3 = evo.clone(e, { [f3] = true }) 57 | assert(evo.has(e3, f2)) 58 | assert(evo.get(e3, f2) == true) 59 | end 60 | 61 | do 62 | local f0 = evo.id() 63 | local q0 = evo.builder():include(f0):spawn() 64 | 65 | local e1 = evo.builder():set(f0):spawn() 66 | local e2 = evo.builder():set(f0):spawn() 67 | local e3 = evo.builder():set(f0):set(f2, false):spawn() 68 | 69 | evo.batch_set(q0, f1) 70 | 71 | assert(evo.has(e1, f2) and evo.get(e1, f2) == true) 72 | assert(evo.has(e2, f2) and evo.get(e2, f2) == true) 73 | assert(evo.has(e3, f2) and evo.get(e3, f2) == false) 74 | end 75 | end 76 | 77 | do 78 | local f1, f2, f3 = evo.id(3) 79 | evo.set(f1, evo.REQUIRES, { f2 }) 80 | evo.set(f2, evo.DEFAULT, 42) 81 | evo.set(f3, evo.REQUIRES, { f1, f2 }) 82 | 83 | do 84 | local e = evo.id() 85 | evo.set(e, f1) 86 | assert(evo.has(e, f2)) 87 | assert(evo.get(e, f2) == 42) 88 | end 89 | 90 | do 91 | local e = evo.builder():set(f1):spawn() 92 | assert(evo.has(e, f2)) 93 | assert(evo.get(e, f2) == 42) 94 | end 95 | 96 | do 97 | local e = evo.spawn { [f1] = true, } 98 | assert(evo.has(e, f2)) 99 | assert(evo.get(e, f2) == 42) 100 | 101 | evo.remove(e, f2) 102 | assert(not evo.has(e, f2)) 103 | 104 | local e2 = evo.clone(e) 105 | assert(evo.has(e2, f2)) 106 | assert(evo.get(e2, f2) == 42) 107 | 108 | local e3 = evo.clone(e, { [f3] = true }) 109 | assert(evo.has(e3, f2)) 110 | assert(evo.get(e3, f2) == 42) 111 | end 112 | 113 | do 114 | local f0 = evo.id() 115 | local q0 = evo.builder():include(f0):spawn() 116 | 117 | local e1 = evo.builder():set(f0):spawn() 118 | local e2 = evo.builder():set(f0):spawn() 119 | local e3 = evo.builder():set(f0):set(f2, 21):spawn() 120 | 121 | evo.batch_set(q0, f1) 122 | 123 | assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) 124 | assert(evo.has(e2, f2) and evo.get(e2, f2) == 42) 125 | assert(evo.has(e3, f2) and evo.get(e3, f2) == 21) 126 | end 127 | end 128 | 129 | do 130 | local f1, f2, f3 = evo.id(3) 131 | evo.set(f1, evo.REQUIRES, { f2 }) 132 | evo.set(f2, evo.REQUIRES, { f3 }) 133 | evo.set(f3, evo.DEFAULT, 42) 134 | evo.set(f3, evo.REQUIRES, { f1, f2 }) 135 | 136 | do 137 | local e = evo.id() 138 | evo.set(e, f1) 139 | assert(evo.has(e, f2)) 140 | assert(evo.get(e, f2) == true) 141 | assert(evo.has(e, f3)) 142 | assert(evo.get(e, f3) == 42) 143 | end 144 | 145 | do 146 | local e = evo.builder():set(f1):spawn() 147 | assert(evo.has(e, f2)) 148 | assert(evo.get(e, f2) == true) 149 | assert(evo.has(e, f3)) 150 | assert(evo.get(e, f3) == 42) 151 | end 152 | 153 | do 154 | local e = evo.spawn { [f1] = true } 155 | assert(evo.has(e, f2)) 156 | assert(evo.get(e, f2) == true) 157 | assert(evo.has(e, f3)) 158 | assert(evo.get(e, f3) == 42) 159 | 160 | evo.remove(e, f2, f3) 161 | assert(not evo.has(e, f2)) 162 | assert(not evo.has(e, f3)) 163 | 164 | local e2 = evo.clone(e, { [f1] = 21 }) 165 | assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) 166 | assert(evo.has(e2, f2) and evo.get(e2, f2) == true) 167 | assert(evo.has(e2, f3) and evo.get(e2, f3) == 42) 168 | 169 | local e3 = evo.clone(e, { [f3] = 21 }) 170 | assert(evo.has(e3, f1) and evo.get(e3, f1) == true) 171 | assert(evo.has(e3, f2) and evo.get(e3, f2) == true) 172 | assert(evo.has(e3, f3) and evo.get(e3, f3) == 21) 173 | end 174 | end 175 | 176 | do 177 | local f1, f2, f3 = evo.id(3) 178 | evo.set(f1, evo.REQUIRES, { f2 }) 179 | evo.set(f2, evo.REQUIRES, { f3 }) 180 | evo.set(f3, evo.REQUIRES, { f1, f2, f3 }) 181 | 182 | do 183 | local e = evo.id() 184 | evo.set(e, f1, 42) 185 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 186 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 187 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 188 | end 189 | 190 | do 191 | local e = evo.builder():set(f1, 42):spawn() 192 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 193 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 194 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 195 | end 196 | 197 | do 198 | local e = evo.spawn { [f1] = 42 } 199 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 200 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 201 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 202 | 203 | evo.remove(e, f2, f3) 204 | assert(not evo.has(e, f2)) 205 | assert(not evo.has(e, f3)) 206 | 207 | local e2 = evo.clone(e, { [f1] = 21 }) 208 | assert(evo.has(e2, f1) and evo.get(e2, f1) == 21) 209 | assert(evo.has(e2, f2) and evo.get(e2, f2) == true) 210 | assert(evo.has(e2, f3) and evo.get(e2, f3) == true) 211 | 212 | local e3 = evo.clone(e, { [f3] = 21 }) 213 | assert(evo.has(e3, f1) and evo.get(e3, f1) == 42) 214 | assert(evo.has(e3, f2) and evo.get(e3, f2) == true) 215 | assert(evo.has(e3, f3) and evo.get(e3, f3) == 21) 216 | end 217 | end 218 | 219 | do 220 | local f1, f2, f3, f4 = evo.id(4) 221 | 222 | do 223 | evo.set(f1, evo.REQUIRES, { f2 }) 224 | do 225 | local e = evo.id() 226 | evo.set(e, f1) 227 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 228 | end 229 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 230 | do 231 | local e = evo.id() 232 | evo.set(e, f1) 233 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 234 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 235 | end 236 | evo.set(f3, evo.REQUIRES, { f4 }) 237 | do 238 | local e = evo.id() 239 | evo.set(e, f1) 240 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 241 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 242 | assert(evo.has(e, f4) and evo.get(e, f4) == true) 243 | end 244 | end 245 | end 246 | -------------------------------------------------------------------------------- /evolved.d.tl: -------------------------------------------------------------------------------- 1 | local record Evolved 2 | interface Id end 3 | 4 | type Entity = Id 5 | type Fragment = Id 6 | type Query = Id 7 | type System = Id 8 | 9 | interface EachState end 10 | interface ExecuteState end 11 | 12 | type EachIterator = function(state?: EachState): Fragment, any 13 | type ExecuteIterator = function(state?: ExecuteState): Chunk, { Entity }, integer 14 | 15 | interface Chunk 16 | alive: function(self: Chunk): boolean 17 | empty: function(self: Chunk): boolean 18 | 19 | has: function(self: Chunk, fragment: Fragment): boolean 20 | has_all: function(self: Chunk, ...: Fragment): boolean 21 | has_any: function(self: Chunk, ...: Fragment): boolean 22 | 23 | entities: function(self: Chunk): { Entity }, integer 24 | fragments: function(self: Chunk): { Fragment }, integer 25 | 26 | components: function(self: Chunk) 27 | components: function(self: Chunk, f1: Fragment): { C1 } 28 | components: function(self: Chunk, f1: Fragment, f2: Fragment): { C1 }, { C2 } 29 | components: function(self: Chunk, f1: Fragment, f2: Fragment, f3: Fragment): { C1 }, { C2 }, { C3 } 30 | components: function(self: Chunk, f1: Fragment, f2: Fragment, f3: Fragment, f4: Fragment): { C1 }, { C2 }, { C3 }, { C4 } 31 | end 32 | 33 | interface Builder 34 | build: function(self: Builder, prefab?: Entity): Entity 35 | multi_build: function(self: Builder, entity_count: integer, prefab?: Entity): { Entity }, integer 36 | 37 | spawn: function(self: Builder): Entity 38 | multi_spawn: function(self: Builder, entity_count: integer): { Entity }, integer 39 | 40 | clone: function(self: Builder, prefab: Entity): Entity 41 | multi_clone: function(self: Builder, entity_count: integer, prefab: Entity): { Entity }, integer 42 | 43 | has: function(self: Builder, fragment: Fragment): boolean 44 | has_all: function(self: Builder, ...: Fragment): boolean 45 | has_any: function(self: Builder, ...: Fragment): boolean 46 | 47 | get: function(self: Builder) 48 | get: function(self: Builder, f1: Fragment): C1 | nil 49 | get: function(self: Builder, f1: Fragment, f2: Fragment): C1 | nil, C2 | nil 50 | get: function(self: Builder, f1: Fragment, f2: Fragment, f3: Fragment): C1 | nil, C2 | nil, C3 | nil 51 | get: function(self: Builder, f1: Fragment, f2: Fragment, f3: Fragment, f4: Fragment): C1 | nil, C2 | nil, C3 | nil, C4 | nil 52 | 53 | set: function(self: Builder, fragment: Fragment, component?: Component): Builder 54 | remove: function(self: Builder, ...: Fragment): Builder 55 | clear: function(self: Builder): Builder 56 | 57 | tag: function(self: Builder): Builder 58 | name: function(self: Builder, name: string): Builder 59 | 60 | unique: function(self: Builder): Builder 61 | explicit: function(self: Builder): Builder 62 | internal: function(self: Builder): Builder 63 | 64 | default: function(self: Builder, default: Component): Builder 65 | duplicate: function(self: Builder, duplicate: function(Component): Component): Builder 66 | 67 | prefab: function(self: Builder): Builder 68 | disabled: function(self: Builder): Builder 69 | 70 | include: function(self: Builder, ...: Fragment): Builder 71 | exclude: function(self: Builder, ...: Fragment): Builder 72 | require: function(self: Builder, ...: Fragment): Builder 73 | 74 | on_set: function(self: Builder, on_set: function(Entity, Fragment, ? Component, ? Component)): Builder 75 | on_assign: function(self: Builder, on_assign: function(Entity, Fragment, ? Component, ? Component)): Builder 76 | on_insert: function(self: Builder, on_insert: function(Entity, Fragment, ? Component)): Builder 77 | on_remove: function(self: Builder, on_remove: function(Entity, Fragment, ? Component)): Builder 78 | 79 | group: function(self: Builder, group: System): Builder 80 | 81 | query: function(self: Builder, query: Query): Builder 82 | execute: function(self: Builder, execute: function(Chunk, {Entity}, integer)): Builder 83 | 84 | prologue: function(self: Builder, prologue: function()): Builder 85 | epilogue: function(self: Builder, epilogue: function()): Builder 86 | 87 | destruction_policy: function(self: Builder, destruction_policy: Id): Builder 88 | end 89 | 90 | TAG: Fragment 91 | NAME: Fragment 92 | 93 | UNIQUE: Fragment 94 | EXPLICIT: Fragment 95 | INTERNAL: Fragment 96 | 97 | DEFAULT: Fragment 98 | DUPLICATE: Fragment 99 | 100 | PREFAB: Fragment 101 | DISABLED: Fragment 102 | 103 | INCLUDES: Fragment 104 | EXCLUDES: Fragment 105 | REQUIRES: Fragment 106 | 107 | ON_SET: Fragment 108 | ON_ASSIGN: Fragment 109 | ON_INSERT: Fragment 110 | ON_REMOVE: Fragment 111 | 112 | GROUP: Fragment 113 | 114 | QUERY: Fragment 115 | EXECUTE: Fragment 116 | PROLOGUE: Fragment 117 | EPILOGUE: Fragment 118 | 119 | DESTRUCTION_POLICY: Fragment 120 | DESTRUCTION_POLICY_DESTROY_ENTITY: Id 121 | DESTRUCTION_POLICY_REMOVE_FRAGMENT: Id 122 | 123 | id: function(count?: integer): Id... 124 | name: function(...: Id): string... 125 | 126 | pack: function(primary: integer, secondary: integer): Id 127 | unpack: function(id: Id): integer, integer 128 | 129 | defer: function(): boolean 130 | commit: function(): boolean 131 | cancel: function(): boolean 132 | 133 | spawn: function(components?: { Fragment: any }): Entity 134 | multi_spawn: function(entity_count: integer, components?: { Fragment: any }): { Entity }, integer 135 | 136 | clone: function(prefab: Entity, components?: { Fragment: any }): Entity 137 | multi_clone: function(entity_count: integer, prefab: Entity, components?: { Fragment: any }): { Entity }, integer 138 | 139 | alive: function(entity: Entity): boolean 140 | alive_all: function(...: Entity): boolean 141 | alive_any: function(...: Entity): boolean 142 | 143 | empty: function(entity: Entity): boolean 144 | empty_all: function(...: Entity): boolean 145 | empty_any: function(...: Entity): boolean 146 | 147 | has: function(entity: Entity, fragment: Fragment): boolean 148 | has_all: function(entity: Entity, ...: Fragment): boolean 149 | has_any: function(entity: Entity, ...: Fragment): boolean 150 | 151 | get: function(entity: Entity) 152 | get: function(entity: Entity, f1: Fragment): C1 | nil 153 | get: function(entity: Entity, f1: Fragment, f2: Fragment): C1 | nil, C2 | nil 154 | get: function(entity: Entity, f1: Fragment, f2: Fragment, f3: Fragment): C1 | nil, C2 | nil, C3 | nil 155 | get: function(entity: Entity, f1: Fragment, f2: Fragment, f3: Fragment, f4: Fragment): C1 | nil, C2 | nil, C3 | nil, C4 | nil 156 | 157 | set: function(entity: Entity, fragment: Fragment, component?: Component) 158 | remove: function(entity: Entity, ...: Fragment) 159 | clear: function(...: Entity) 160 | destroy: function(...: Entity) 161 | 162 | batch_set: function(query: Query, fragment: Fragment, component?: Component) 163 | batch_remove: function(query: Query, ...: Fragment) 164 | batch_clear: function(...: Query) 165 | batch_destroy: function(...: Query) 166 | 167 | each: function(entity: Entity): EachIterator, EachState | nil 168 | execute: function(query: Query): ExecuteIterator, ExecuteState | nil 169 | 170 | locate: function(entity: Entity): Chunk | nil, integer 171 | 172 | process: function(...: System) 173 | 174 | debug_mode: function(yesno: boolean) 175 | collect_garbage: function() 176 | 177 | chunk: function(fragment: Fragment, ...: Fragment): Chunk, { Entity }, integer 178 | builder: function(): Builder 179 | end 180 | 181 | return Evolved 182 | -------------------------------------------------------------------------------- /develop/testing/spawn_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | do 5 | local e = evo.spawn() 6 | assert(evo.alive(e) and evo.empty(e)) 7 | end 8 | 9 | do 10 | local e = evo.spawn({}) 11 | assert(evo.alive(e) and evo.empty(e)) 12 | end 13 | end 14 | 15 | do 16 | local f1, f2, f3 = evo.id(3) 17 | evo.set(f2, evo.REQUIRES, { f1, f3 }) 18 | 19 | do 20 | local e = evo.spawn({ [f1] = 42 }) 21 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1)) 22 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 23 | end 24 | 25 | do 26 | local e = evo.spawn({ [f1] = 42, [f2] = 'hello' }) 27 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 28 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 29 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 30 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 31 | end 32 | end 33 | 34 | do 35 | local f1, f2, f3 = evo.id(3) 36 | evo.set(f2, evo.REQUIRES, { f1, f3 }) 37 | evo.set(f3, evo.DEFAULT, 21) 38 | evo.set(f3, evo.REQUIRES, { f2 }) 39 | 40 | do 41 | local e = evo.spawn({ [f1] = 42 }) 42 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1)) 43 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 44 | end 45 | 46 | do 47 | local e = evo.spawn({ [f1] = 42, [f2] = 'hello' }) 48 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 49 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 50 | assert(evo.has(e, f2) and evo.get(e, f2) == 'hello') 51 | assert(evo.has(e, f3) and evo.get(e, f3) == 21) 52 | end 53 | end 54 | 55 | do 56 | local f1, f2, f3, f4 = evo.id(4) 57 | evo.set(f2, evo.DUPLICATE, function() return nil end) 58 | evo.set(f2, evo.REQUIRES, { f1, f3, f4 }) 59 | evo.set(f3, evo.DEFAULT, 21) 60 | evo.set(f3, evo.DUPLICATE, function(v) return v * 3 end) 61 | evo.set(f3, evo.REQUIRES, { f2 }) 62 | 63 | do 64 | local e = evo.spawn({ [f1] = 42 }) 65 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1)) 66 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 67 | end 68 | 69 | do 70 | local e = evo.spawn({ [f1] = 42, [f2] = true }) 71 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3, f4)) 72 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 73 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 74 | assert(evo.has(e, f3) and evo.get(e, f3) == 21 * 3) 75 | assert(evo.has(e, f4) and evo.get(e, f4) == true) 76 | end 77 | end 78 | 79 | do 80 | local f1, f2, f3 = evo.id(3) 81 | evo.set(f2, evo.REQUIRES, { f3 }) 82 | evo.set(f3, evo.TAG) 83 | 84 | local f2_set_sum, f2_inserted_sum = 0, 0 85 | local f3_set_count, f3_inserted_count = 0, 0 86 | 87 | evo.set(f2, evo.ON_SET, function(e, f, c) 88 | assert(c == 42) 89 | assert(evo.get(e, f) == c) 90 | assert(f == f2) 91 | f2_set_sum = f2_set_sum + c 92 | end) 93 | 94 | evo.set(f2, evo.ON_INSERT, function(e, f, c) 95 | assert(c == 42) 96 | assert(evo.get(e, f) == c) 97 | assert(f == f2) 98 | f2_inserted_sum = f2_inserted_sum + c 99 | end) 100 | 101 | evo.set(f3, evo.ON_SET, function(e, f, c) 102 | assert(c == nil) 103 | assert(evo.get(e, f) == c) 104 | assert(f == f3) 105 | f3_set_count = f3_set_count + 1 106 | end) 107 | 108 | evo.set(f3, evo.ON_INSERT, function(e, f, c) 109 | assert(c == nil) 110 | assert(evo.get(e, f) == c) 111 | assert(f == f3) 112 | f3_inserted_count = f3_inserted_count + 1 113 | end) 114 | 115 | do 116 | f3_set_count, f3_inserted_count = 0, 0 117 | local e = evo.spawn({ [f1] = 'hello', [f2] = 42 }) 118 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 119 | assert(f2_set_sum == 42 and f2_inserted_sum == 42) 120 | assert(f3_set_count == 1 and f3_inserted_count == 1) 121 | end 122 | end 123 | 124 | do 125 | do 126 | local es, ec = evo.multi_spawn(2) 127 | assert(#es == 2 and ec == 2) 128 | 129 | for i = 1, ec do 130 | assert(evo.alive(es[i]) and evo.empty(es[i])) 131 | end 132 | end 133 | 134 | do 135 | local es, ec = evo.multi_spawn(2, {}) 136 | assert(#es == 2 and ec == 2) 137 | 138 | for i = 1, ec do 139 | assert(evo.alive(es[i]) and evo.empty(es[i])) 140 | end 141 | end 142 | end 143 | 144 | do 145 | local f1, f2 = evo.id(2) 146 | 147 | do 148 | local es, ec = evo.multi_spawn(3, { [f1] = 42 }) 149 | assert(#es == 3 and ec == 3) 150 | 151 | for i = 1, ec do 152 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1)) 153 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 154 | end 155 | end 156 | 157 | do 158 | local es, ec = evo.multi_spawn(3, { [f1] = 42, [f2] = 'hello' }) 159 | assert(#es == 3 and ec == 3) 160 | 161 | for i = 1, ec do 162 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2)) 163 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 164 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'hello') 165 | end 166 | end 167 | end 168 | 169 | do 170 | local f1, f2 = evo.id(2) 171 | evo.set(f1, evo.REQUIRES, { f2 }) 172 | 173 | do 174 | local es, ec = evo.multi_spawn(3, { [f1] = 42 }) 175 | assert(#es == 3 and ec == 3) 176 | 177 | for i = 1, ec do 178 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2)) 179 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 180 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == true) 181 | end 182 | end 183 | end 184 | 185 | do 186 | local f1, f2 = evo.id(2) 187 | evo.set(f1, evo.REQUIRES, { f2 }) 188 | 189 | do 190 | local es, ec = evo.multi_spawn(1, { [f1] = 42 }) 191 | assert(#es == 1 and ec == 1) 192 | 193 | for i = 1, ec do 194 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2)) 195 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 196 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == true) 197 | end 198 | end 199 | end 200 | 201 | do 202 | local f1, f2, f3 = evo.id(3) 203 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 204 | evo.set(f2, evo.DEFAULT, 'hello') 205 | 206 | do 207 | local es, ec = evo.multi_spawn(4, { [f1] = 42 }) 208 | assert(#es == 4 and ec == 4) 209 | 210 | for i = 1, ec do 211 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 212 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 213 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'hello') 214 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == true) 215 | end 216 | end 217 | 218 | do 219 | local es, ec = evo.multi_spawn(4, { [f1] = 42, [f2] = 'world' }) 220 | assert(#es == 4 and ec == 4) 221 | 222 | for i = 1, ec do 223 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 224 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 225 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'world') 226 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == true) 227 | end 228 | end 229 | 230 | do 231 | local es, ec = evo.multi_spawn(4, { [f1] = 42, [f2] = 'world', [f3] = false }) 232 | assert(#es == 4 and ec == 4) 233 | 234 | for i = 1, ec do 235 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 236 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 42) 237 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'world') 238 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == false) 239 | end 240 | end 241 | end 242 | 243 | do 244 | local f1, f2 = evo.id(2) 245 | evo.set(f1, evo.REQUIRES, { f2 }) 246 | evo.set(f1, evo.DUPLICATE, function() return nil end) 247 | evo.set(f2, evo.DEFAULT, 'hello') 248 | evo.set(f2, evo.DUPLICATE, function(v) return v .. '!' end) 249 | 250 | do 251 | local es, ec = evo.multi_spawn(4, { [f1] = 42 }) 252 | assert(#es == 4 and ec == 4) 253 | 254 | for i = 1, ec do 255 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2)) 256 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == true) 257 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'hello!') 258 | end 259 | end 260 | 261 | do 262 | local es, ec = evo.multi_spawn(4, { [f1] = 42, [f2] = 'world' }) 263 | assert(#es == 4 and ec == 4) 264 | 265 | for i = 1, ec do 266 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2)) 267 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == true) 268 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'world!') 269 | end 270 | end 271 | 272 | do 273 | local es, ec = evo.multi_spawn(4, { [f2] = 'hello world' }) 274 | assert(#es == 4 and ec == 4) 275 | 276 | for i = 1, ec do 277 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f2)) 278 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 'hello world!') 279 | end 280 | end 281 | end 282 | 283 | do 284 | local f1, f2, f3 = evo.id(3) 285 | evo.set(f2, evo.REQUIRES, { f3 }) 286 | evo.set(f3, evo.TAG) 287 | 288 | local f2_set_sum, f2_inserted_sum = 0, 0 289 | local f3_set_count, f3_inserted_count = 0, 0 290 | 291 | evo.set(f2, evo.ON_SET, function(e, f, c) 292 | assert(c == 42) 293 | assert(evo.get(e, f) == c) 294 | assert(f == f2) 295 | f2_set_sum = f2_set_sum + c 296 | end) 297 | 298 | evo.set(f2, evo.ON_INSERT, function(e, f, c) 299 | assert(c == 42) 300 | assert(evo.get(e, f) == c) 301 | assert(f == f2) 302 | f2_inserted_sum = f2_inserted_sum + c 303 | end) 304 | 305 | evo.set(f3, evo.ON_SET, function(e, f, c) 306 | assert(c == nil) 307 | assert(evo.get(e, f) == c) 308 | assert(f == f3) 309 | f3_set_count = f3_set_count + 1 310 | end) 311 | 312 | evo.set(f3, evo.ON_INSERT, function(e, f, c) 313 | assert(c == nil) 314 | assert(evo.get(e, f) == c) 315 | assert(f == f3) 316 | f3_inserted_count = f3_inserted_count + 1 317 | end) 318 | 319 | do 320 | local es, ec = evo.multi_spawn(3, { [f1] = 'hello', [f2] = 42 }) 321 | assert(#es == 3 and ec == 3) 322 | 323 | for i = 1, ec do 324 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 325 | assert(f2_set_sum == 42 * 3 and f2_inserted_sum == 42 * 3) 326 | assert(f3_set_count == 3 and f3_inserted_count == 3) 327 | end 328 | end 329 | end 330 | -------------------------------------------------------------------------------- /develop/benchmarks/common_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | local tiny = require 'develop.3rdparty.tiny' 5 | 6 | evo.debug_mode(false) 7 | 8 | local N = 1000 9 | 10 | print '----------------------------------------' 11 | 12 | basics.describe_bench(string.format('Common Benchmarks: Tiny Entity Cycle | %d entities', N), 13 | function(world) 14 | world:update() 15 | end, function() 16 | local world = tiny.world() 17 | 18 | for _ = 1, N do 19 | world:addEntity({ a = 0 }) 20 | end 21 | 22 | local A = tiny.processingSystem() 23 | A.filter = tiny.requireAll('a') 24 | A.process = function(_, e) world:addEntity({ b = e.a }) end 25 | A.postProcess = function(_) world:refresh() end 26 | 27 | local B = tiny.processingSystem() 28 | B.filter = tiny.requireAll('b') 29 | B.process = function(_, e) world:removeEntity(e) end 30 | B.postProcess = function(_) world:refresh() end 31 | 32 | world:addSystem(A) 33 | world:addSystem(B) 34 | 35 | world:refresh() 36 | 37 | return world 38 | end) 39 | 40 | basics.describe_bench(string.format('Common Benchmarks: Evolved Entity Cycle | %d entities', N), 41 | function(world) 42 | evo.process(world) 43 | end, function() 44 | local world = evo.builder() 45 | :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 46 | :spawn() 47 | 48 | local a = evo.builder():set(world):spawn() 49 | local b = evo.builder():set(world):spawn() 50 | 51 | local query_a = evo.builder():set(world):include(a):spawn() 52 | local query_b = evo.builder():set(world):include(b):spawn() 53 | 54 | local prefab_a = evo.builder():prefab():set(world):set(a, 0):spawn() 55 | local prefab_b = evo.builder():prefab():set(world):set(b, 0):spawn() 56 | 57 | evo.multi_clone(N, prefab_a) 58 | 59 | evo.builder() 60 | :set(world):group(world):query(query_a) 61 | :execute(function(chunk, _, entity_count) 62 | local as = chunk:components(a) 63 | local entity_bs = evo.multi_clone(entity_count, prefab_b) 64 | for i = 1, entity_count do evo.set(entity_bs[i], b, as[i]) end 65 | end):spawn() 66 | 67 | evo.builder() 68 | :set(world):group(world):query(query_b) 69 | :prologue(function() 70 | evo.batch_destroy(query_b) 71 | end):spawn() 72 | 73 | return world 74 | end, function(world) 75 | evo.destroy(world) 76 | end) 77 | 78 | print '----------------------------------------' 79 | 80 | basics.describe_bench(string.format('Common Benchmarks: Tiny Simple Iteration | %d entities', N), 81 | function(world) 82 | world:update() 83 | end, function() 84 | local world = tiny.world() 85 | 86 | for _ = 1, N do 87 | world:addEntity({ a = 0, b = 0 }) 88 | world:addEntity({ a = 0, b = 0, c = 0 }) 89 | world:addEntity({ a = 0, b = 0, c = 0, d = 0 }) 90 | world:addEntity({ a = 0, b = 0, c = 0, e = 0 }) 91 | end 92 | 93 | local AB = tiny.processingSystem() 94 | AB.filter = tiny.requireAll('a', 'b') 95 | AB.process = function(_, e) e.a, e.b = e.b, e.a end 96 | 97 | local CD = tiny.processingSystem() 98 | CD.filter = tiny.requireAll('c', 'd') 99 | CD.process = function(_, e) e.c, e.d = e.d, e.c end 100 | 101 | local CE = tiny.processingSystem() 102 | CE.filter = tiny.requireAll('c', 'e') 103 | CE.process = function(_, e) e.c, e.e = e.e, e.c end 104 | 105 | world:addSystem(AB) 106 | world:addSystem(CD) 107 | world:addSystem(CE) 108 | 109 | world:refresh() 110 | 111 | return world 112 | end) 113 | 114 | basics.describe_bench(string.format('Common Benchmarks: Evolved Simple Iteration | %d entities', N), 115 | function(world) 116 | evo.process(world) 117 | end, function() 118 | local world = evo.builder() 119 | :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 120 | :spawn() 121 | 122 | local a = evo.builder():set(world):spawn() 123 | local b = evo.builder():set(world):spawn() 124 | local c = evo.builder():set(world):spawn() 125 | local d = evo.builder():set(world):spawn() 126 | local e = evo.builder():set(world):spawn() 127 | 128 | local query_ab = evo.builder():set(world):include(a, b):spawn() 129 | local query_cd = evo.builder():set(world):include(c, d):spawn() 130 | local query_ce = evo.builder():set(world):include(c, e):spawn() 131 | 132 | evo.multi_spawn(N, { [world] = true, [a] = 0, [b] = 0 }) 133 | evo.multi_spawn(N, { [world] = true, [a] = 0, [b] = 0, [c] = 0 }) 134 | evo.multi_spawn(N, { [world] = true, [a] = 0, [b] = 0, [c] = 0, [d] = 0 }) 135 | evo.multi_spawn(N, { [world] = true, [a] = 0, [b] = 0, [c] = 0, [e] = 0 }) 136 | 137 | evo.builder() 138 | :set(world):group(world):query(query_ab) 139 | :execute(function(chunk, _, entity_count) 140 | local as, bs = chunk:components(a, b) 141 | for i = 1, entity_count do as[i], bs[i] = bs[i], as[i] end 142 | end):spawn() 143 | 144 | evo.builder() 145 | :set(world):group(world):query(query_cd) 146 | :execute(function(chunk, _, entity_count) 147 | local cs, ds = chunk:components(c, d) 148 | for i = 1, entity_count do cs[i], ds[i] = ds[i], cs[i] end 149 | end):spawn() 150 | 151 | evo.builder() 152 | :set(world):group(world):query(query_ce) 153 | :execute(function(chunk, _, entity_count) 154 | local cs, es = chunk:components(c, e) 155 | for i = 1, entity_count do cs[i], es[i] = es[i], cs[i] end 156 | end):spawn() 157 | 158 | return world 159 | end, function(world) 160 | evo.destroy(world) 161 | end) 162 | 163 | print '----------------------------------------' 164 | 165 | basics.describe_bench(string.format('Common Benchmarks: Tiny Packed Iteration | %d entities', N), 166 | function(world) 167 | world:update() 168 | end, function() 169 | local world = tiny.world() 170 | 171 | for _ = 1, N do 172 | world:addEntity({ a = 0, b = 0, c = 0, d = 0, e = 0 }) 173 | end 174 | 175 | local A = tiny.processingSystem() 176 | A.filter = tiny.requireAll('a') 177 | A.process = function(_, e) e.a = e.a * 2 end 178 | 179 | local B = tiny.processingSystem() 180 | B.filter = tiny.requireAll('b') 181 | B.process = function(_, e) e.b = e.b * 2 end 182 | 183 | local C = tiny.processingSystem() 184 | C.filter = tiny.requireAll('c') 185 | C.process = function(_, e) e.c = e.c * 2 end 186 | 187 | local D = tiny.processingSystem() 188 | D.filter = tiny.requireAll('d') 189 | D.process = function(_, e) e.d = e.d * 2 end 190 | 191 | local E = tiny.processingSystem() 192 | E.filter = tiny.requireAll('e') 193 | E.process = function(_, e) e.e = e.e * 2 end 194 | 195 | world:addSystem(A) 196 | world:addSystem(B) 197 | world:addSystem(C) 198 | world:addSystem(D) 199 | world:addSystem(E) 200 | 201 | world:refresh() 202 | 203 | return world 204 | end) 205 | 206 | basics.describe_bench(string.format('Common Benchmarks: Evolved Packed Iteration | %d entities', N), 207 | function(world) 208 | evo.process(world) 209 | end, function() 210 | local world = evo.builder() 211 | :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 212 | :spawn() 213 | 214 | local a = evo.builder():set(world):spawn() 215 | local b = evo.builder():set(world):spawn() 216 | local c = evo.builder():set(world):spawn() 217 | local d = evo.builder():set(world):spawn() 218 | local e = evo.builder():set(world):spawn() 219 | 220 | local query_a = evo.builder():set(world):include(a):spawn() 221 | local query_b = evo.builder():set(world):include(b):spawn() 222 | local query_c = evo.builder():set(world):include(c):spawn() 223 | local query_d = evo.builder():set(world):include(d):spawn() 224 | local query_e = evo.builder():set(world):include(e):spawn() 225 | 226 | evo.multi_spawn(N, { [world] = true, [a] = 0, [b] = 0, [c] = 0, [d] = 0, [e] = 0 }) 227 | 228 | evo.builder() 229 | :set(world):group(world):query(query_a) 230 | :execute(function(chunk, _, entity_count) 231 | local as = chunk:components(a) 232 | for i = 1, entity_count do as[i] = as[i] * 2 end 233 | end):spawn() 234 | 235 | evo.builder() 236 | :set(world):group(world):query(query_b) 237 | :execute(function(chunk, _, entity_count) 238 | local bs = chunk:components(b) 239 | for i = 1, entity_count do bs[i] = bs[i] * 2 end 240 | end):spawn() 241 | 242 | evo.builder() 243 | :set(world):group(world):query(query_c) 244 | :execute(function(chunk, _, entity_count) 245 | local cs = chunk:components(c) 246 | for i = 1, entity_count do cs[i] = cs[i] * 2 end 247 | end):spawn() 248 | 249 | evo.builder() 250 | :set(world):group(world):query(query_d) 251 | :execute(function(chunk, _, entity_count) 252 | local ds = chunk:components(d) 253 | for i = 1, entity_count do ds[i] = ds[i] * 2 end 254 | end):spawn() 255 | 256 | evo.builder() 257 | :set(world):group(world):query(query_e) 258 | :execute(function(chunk, _, entity_count) 259 | local es = chunk:components(e) 260 | for i = 1, entity_count do es[i] = es[i] * 2 end 261 | end):spawn() 262 | 263 | return world 264 | end, function(world) 265 | evo.destroy(world) 266 | end) 267 | 268 | print '----------------------------------------' 269 | 270 | basics.describe_bench(string.format('Common Benchmarks: Tiny Fragmented Iteration | %d entities', N), 271 | function(world) 272 | world:update() 273 | end, function() 274 | local world = tiny.world() 275 | 276 | ---@type string[] 277 | local chars = {} 278 | 279 | for i = 1, 26 do 280 | chars[i] = string.char(string.byte('a') + i - 1) 281 | end 282 | 283 | for i, char in ipairs(chars) do 284 | for _ = 1, N do 285 | world:addEntity({ [char] = i, data = i }) 286 | end 287 | end 288 | 289 | local Data = tiny.processingSystem() 290 | Data.filter = tiny.requireAll('data') 291 | Data.process = function(_, e) e.data = e.data * 2 end 292 | 293 | local Last = tiny.processingSystem() 294 | Last.filter = tiny.requireAll('z') 295 | Last.process = function(_, e) e.z = e.z * 2 end 296 | 297 | world:addSystem(Data) 298 | world:addSystem(Last) 299 | 300 | world:refresh() 301 | 302 | return world 303 | end) 304 | 305 | basics.describe_bench(string.format('Common Benchmarks: Evolved Fragmented Iteration | %d entities', N), 306 | function(world) 307 | evo.process(world) 308 | end, function() 309 | local world = evo.builder() 310 | :destruction_policy(evo.DESTRUCTION_POLICY_DESTROY_ENTITY) 311 | :spawn() 312 | 313 | local data = evo.spawn { [world] = true } 314 | local chars = evo.multi_spawn(26, { [world] = true }) 315 | 316 | local query_data = evo.builder():set(world):include(data):spawn() 317 | local query_z = evo.builder():set(world):include(chars[#chars]):spawn() 318 | 319 | for i = 1, #chars do 320 | evo.multi_spawn(N, { [world] = true, [chars[i]] = i, [data] = i }) 321 | end 322 | 323 | evo.builder() 324 | :set(world):group(world):query(query_data) 325 | :execute(function(chunk, _, entity_count) 326 | local datas = chunk:components(data) 327 | for i = 1, entity_count do datas[i] = datas[i] * 2 end 328 | end):spawn() 329 | 330 | evo.builder() 331 | :set(world):group(world):query(query_z) 332 | :execute(function(chunk, _, entity_count) 333 | local zs = chunk:components(chars[#chars]) 334 | for i = 1, entity_count do zs[i] = zs[i] * 2 end 335 | end):spawn() 336 | 337 | return world 338 | end, function(world) 339 | evo.destroy(world) 340 | end) 341 | -------------------------------------------------------------------------------- /develop/testing/clone_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | do 5 | local p = evo.spawn() 6 | local e = evo.clone(p) 7 | assert(evo.alive(e) and evo.empty(e)) 8 | end 9 | 10 | do 11 | local p = evo.spawn() 12 | local e = evo.clone(p, {}) 13 | assert(evo.alive(e) and evo.empty(e)) 14 | end 15 | 16 | do 17 | local f1, f2 = evo.id(2) 18 | evo.set(f1, evo.REQUIRES, { f2 }) 19 | evo.set(f2, evo.DEFAULT, 42) 20 | 21 | local p = evo.spawn() 22 | local e = evo.clone(p, { [f1] = 'hello' }) 23 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2)) 24 | assert(evo.has(e, f1) and evo.get(e, f1) == 'hello') 25 | assert(evo.has(e, f2) and evo.get(e, f2) == 42) 26 | end 27 | end 28 | 29 | do 30 | local f1, f2, f3, f4 = evo.id(4) 31 | evo.set(f1, evo.TAG) 32 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 33 | evo.set(f4, evo.UNIQUE) 34 | 35 | do 36 | local p = evo.spawn { [f4] = 'unique' } 37 | local e = evo.clone(p) 38 | assert(evo.alive(e) and evo.empty(e)) 39 | end 40 | 41 | do 42 | local p = evo.spawn { [f4] = 'unique' } 43 | local e = evo.clone(p, {}) 44 | assert(evo.alive(e) and evo.empty(e)) 45 | end 46 | 47 | do 48 | local p = evo.spawn { [f4] = 'unique' } 49 | local e = evo.clone(p, { [f4] = 'another' }) 50 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f4)) 51 | assert(evo.has(e, f4) and evo.get(e, f4) == 'another') 52 | end 53 | 54 | do 55 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 56 | local e = evo.clone(p, { [f4] = 'another' }) 57 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f2, f4)) 58 | assert(evo.has(e, f2) and evo.get(e, f2) == 100) 59 | assert(evo.has(e, f4) and evo.get(e, f4) == 'another') 60 | end 61 | 62 | do 63 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 64 | local e = evo.clone(p, { [f1] = 'hello', [f3] = 10 }) 65 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 66 | assert(evo.has(e, f1) and evo.get(e, f1) == nil) 67 | assert(evo.has(e, f2) and evo.get(e, f2) == 100) 68 | assert(evo.has(e, f3) and evo.get(e, f3) == 10) 69 | end 70 | end 71 | 72 | do 73 | do 74 | local f1, f2, f3, f4 = evo.id(4) 75 | evo.set(f4, evo.TAG) 76 | 77 | do 78 | local p = evo.spawn { [f2] = 21, [f3] = 'hello', [f4] = true } 79 | local e = evo.clone(p, { [f1] = 'world', [f2] = 10 }) 80 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3, f4)) 81 | assert(evo.has(e, f1) and evo.get(e, f1) == 'world') 82 | assert(evo.has(e, f2) and evo.get(e, f2) == 10) 83 | assert(evo.has(e, f3) and evo.get(e, f3) == 'hello') 84 | assert(evo.has(e, f4) and evo.get(e, f4) == nil) 85 | end 86 | end 87 | 88 | do 89 | local f1, f2, f3, f4 = evo.id(4) 90 | evo.set(f2, evo.DEFAULT, 42) 91 | evo.set(f3, evo.DUPLICATE, function() return nil end) 92 | evo.set(f4, evo.TAG) 93 | 94 | do 95 | local p = evo.spawn { [f2] = 21, [f3] = 'hello', [f4] = true } 96 | local e = evo.clone(p, { [f1] = 'world', [f2] = 10 }) 97 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3, f4)) 98 | assert(evo.has(e, f1) and evo.get(e, f1) == 'world') 99 | assert(evo.has(e, f2) and evo.get(e, f2) == 10) 100 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 101 | assert(evo.has(e, f4) and evo.get(e, f4) == nil) 102 | end 103 | end 104 | end 105 | 106 | do 107 | local f1, f2, f3, f4 = evo.id(4) 108 | evo.set(f1, evo.TAG) 109 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 110 | evo.set(f2, evo.DEFAULT, 42) 111 | evo.set(f2, evo.DUPLICATE, function(v) return v * 2 end) 112 | evo.set(f3, evo.DUPLICATE, function() return nil end) 113 | evo.set(f4, evo.UNIQUE) 114 | 115 | do 116 | local p = evo.spawn { [f4] = 'unique' } 117 | local e = evo.clone(p) 118 | assert(evo.alive(e) and evo.empty(e)) 119 | end 120 | 121 | do 122 | local p = evo.spawn { [f4] = 'unique' } 123 | local e = evo.clone(p, {}) 124 | assert(evo.alive(e) and evo.empty(e)) 125 | end 126 | 127 | do 128 | local p = evo.spawn { [f4] = 'unique' } 129 | local e = evo.clone(p, { [f4] = 'another' }) 130 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f4)) 131 | assert(evo.has(e, f4) and evo.get(e, f4) == 'another') 132 | end 133 | 134 | do 135 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 136 | local e = evo.clone(p, { [f4] = 'another' }) 137 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f2, f4)) 138 | assert(evo.has(e, f2) and evo.get(e, f2) == 200 * 2) 139 | assert(evo.has(e, f4) and evo.get(e, f4) == 'another') 140 | end 141 | 142 | do 143 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 144 | local e = evo.clone(p, { [f1] = 'hello', [f2] = 10 }) 145 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 146 | assert(evo.has(e, f1) and evo.get(e, f1) == nil) 147 | assert(evo.has(e, f2) and evo.get(e, f2) == 10 * 2) 148 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 149 | end 150 | 151 | local f1_set_count, f1_inserted_count = 0, 0 152 | local f2_set_sum, f3_inserted_count = 0, 0 153 | 154 | evo.set(f1, evo.ON_SET, function(e, f, c) 155 | assert(evo.get(e, f) == c) 156 | f1_set_count = f1_set_count + 1 157 | end) 158 | 159 | evo.set(f1, evo.ON_INSERT, function(e, f, c) 160 | assert(evo.get(e, f) == c) 161 | f1_inserted_count = f1_inserted_count + 1 162 | end) 163 | 164 | evo.set(f2, evo.ON_SET, function(e, f, c) 165 | assert(evo.get(e, f) == c) 166 | f2_set_sum = f2_set_sum + c 167 | end) 168 | 169 | evo.set(f3, evo.ON_INSERT, function(e, f, c) 170 | assert(evo.get(e, f) == c) 171 | f3_inserted_count = f3_inserted_count + 1 172 | end) 173 | 174 | do 175 | f1_set_count, f1_inserted_count = 0, 0 176 | f2_set_sum, f3_inserted_count = 0, 0 177 | 178 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 179 | local e = evo.clone(p, { [f1] = 'hello', [f2] = 10 }) 180 | assert(evo.alive(e) and not evo.empty(e) and evo.locate(e) == evo.chunk(f1, f2, f3)) 181 | assert(evo.has(e, f1) and evo.get(e, f1) == nil) 182 | assert(evo.has(e, f2) and evo.get(e, f2) == 10 * 2) 183 | assert(evo.has(e, f3) and evo.get(e, f3) == true) 184 | 185 | assert(f1_set_count == 1) 186 | assert(f1_inserted_count == 1) 187 | assert(f2_set_sum == 100 * 2 + 10 * 2) 188 | assert(f3_inserted_count == 1) 189 | end 190 | end 191 | 192 | do 193 | local f1, f2, f3, f4 = evo.id(4) 194 | evo.set(f1, evo.TAG) 195 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 196 | evo.set(f4, evo.UNIQUE) 197 | 198 | do 199 | local p = evo.spawn { [f4] = 'unique' } 200 | local es, ec = evo.multi_clone(1, p) 201 | assert(#es == 1 and ec == 1) 202 | for i = 1, ec do 203 | assert(evo.alive(es[i]) and evo.empty(es[i])) 204 | end 205 | end 206 | 207 | do 208 | local p = evo.spawn { [f4] = 'unique' } 209 | local es, ec = evo.multi_clone(2, p, {}) 210 | assert(#es == 2 and ec == 2) 211 | for i = 1, ec do 212 | assert(evo.alive(es[i]) and evo.empty(es[i])) 213 | end 214 | end 215 | 216 | do 217 | local p = evo.spawn { [f4] = 'unique' } 218 | local es, ec = evo.multi_clone(3, p, { [f4] = 'another' }) 219 | assert(#es == 3 and ec == 3) 220 | for i = 1, ec do 221 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f4)) 222 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == 'another') 223 | end 224 | end 225 | 226 | do 227 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 228 | local es, ec = evo.multi_clone(4, p, { [f4] = 'another' }) 229 | assert(#es == 4 and ec == 4) 230 | for i = 1, ec do 231 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f2, f4)) 232 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 100) 233 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == 'another') 234 | end 235 | end 236 | 237 | do 238 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 239 | local es, ec = evo.multi_clone(5, p, { [f1] = 'hello', [f3] = 10 }) 240 | assert(#es == 5 and ec == 5) 241 | for i = 1, ec do 242 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 243 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) 244 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 100) 245 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == 10) 246 | end 247 | end 248 | end 249 | 250 | do 251 | do 252 | local f1, f2, f3, f4 = evo.id(4) 253 | evo.set(f4, evo.TAG) 254 | 255 | do 256 | local p = evo.spawn { [f2] = 21, [f3] = 'hello', [f4] = true } 257 | local es, ec = evo.multi_clone(2, p, { [f1] = 'world', [f2] = 10 }) 258 | assert(#es == 2 and ec == 2) 259 | for i = 1, ec do 260 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3, f4)) 261 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 'world') 262 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 10) 263 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == 'hello') 264 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == nil) 265 | end 266 | end 267 | end 268 | 269 | do 270 | local f1, f2, f3, f4 = evo.id(4) 271 | evo.set(f2, evo.DEFAULT, 42) 272 | evo.set(f3, evo.DUPLICATE, function() return nil end) 273 | evo.set(f4, evo.TAG) 274 | 275 | do 276 | local p = evo.spawn { [f2] = 21, [f3] = 'hello', [f4] = true } 277 | local es, ec = evo.multi_clone(2, p, { [f1] = 'world', [f2] = 10 }) 278 | assert(#es == 2 and ec == 2) 279 | for i = 1, ec do 280 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3, f4)) 281 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 'world') 282 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 10) 283 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == true) 284 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == nil) 285 | end 286 | end 287 | end 288 | end 289 | 290 | do 291 | local f1, f2, f3, f4 = evo.id(4) 292 | evo.set(f1, evo.TAG) 293 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 294 | evo.set(f2, evo.DEFAULT, 42) 295 | evo.set(f2, evo.DUPLICATE, function(v) return v * 2 end) 296 | evo.set(f3, evo.DUPLICATE, function() return nil end) 297 | evo.set(f4, evo.UNIQUE) 298 | 299 | do 300 | local p = evo.spawn { [f4] = 'unique' } 301 | local es, ec = evo.multi_clone(3, p) 302 | assert(#es == 3 and ec == 3) 303 | 304 | for i = 1, ec do 305 | assert(evo.alive(es[i]) and evo.empty(es[i])) 306 | end 307 | end 308 | 309 | do 310 | local p = evo.spawn { [f4] = 'unique' } 311 | local es, ec = evo.multi_clone(3, p, {}) 312 | assert(#es == 3 and ec == 3) 313 | 314 | for i = 1, ec do 315 | assert(evo.alive(es[i]) and evo.empty(es[i])) 316 | end 317 | end 318 | 319 | do 320 | local p = evo.spawn { [f4] = 'unique' } 321 | local es, ec = evo.multi_clone(2, p, { [f4] = 'another' }) 322 | assert(#es == 2 and ec == 2) 323 | 324 | for i = 1, ec do 325 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f4)) 326 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == 'another') 327 | end 328 | end 329 | 330 | do 331 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 332 | local es, ec = evo.multi_clone(4, p, { [f4] = 'another' }) 333 | assert(#es == 4 and ec == 4) 334 | 335 | for i = 1, ec do 336 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f2, f4)) 337 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 200 * 2) 338 | assert(evo.has(es[i], f4) and evo.get(es[i], f4) == 'another') 339 | end 340 | end 341 | 342 | do 343 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 344 | local es, ec = evo.multi_clone(5, p, { [f1] = 'hello', [f2] = 10 }) 345 | assert(#es == 5 and ec == 5) 346 | 347 | for i = 1, ec do 348 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 349 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) 350 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 10 * 2) 351 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == true) 352 | end 353 | end 354 | 355 | local f1_set_count, f1_inserted_count = 0, 0 356 | local f2_set_sum, f3_inserted_count = 0, 0 357 | 358 | evo.set(f1, evo.ON_SET, function(e, f, c) 359 | assert(evo.get(e, f) == c) 360 | f1_set_count = f1_set_count + 1 361 | end) 362 | 363 | evo.set(f1, evo.ON_INSERT, function(e, f, c) 364 | assert(evo.get(e, f) == c) 365 | f1_inserted_count = f1_inserted_count + 1 366 | end) 367 | 368 | evo.set(f2, evo.ON_SET, function(e, f, c) 369 | assert(evo.get(e, f) == c) 370 | f2_set_sum = f2_set_sum + c 371 | end) 372 | 373 | evo.set(f3, evo.ON_INSERT, function(e, f, c) 374 | assert(evo.get(e, f) == c) 375 | f3_inserted_count = f3_inserted_count + 1 376 | end) 377 | 378 | do 379 | f1_set_count, f1_inserted_count = 0, 0 380 | f2_set_sum, f3_inserted_count = 0, 0 381 | 382 | local p = evo.spawn { [f2] = 100, [f4] = 'unique' } 383 | local es, ec = evo.multi_clone(1, p, { [f1] = 'hello', [f2] = 10 }) 384 | 385 | for i = 1, ec do 386 | assert(evo.alive(es[i]) and not evo.empty(es[i]) and evo.locate(es[i]) == evo.chunk(f1, f2, f3)) 387 | assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) 388 | assert(evo.has(es[i], f2) and evo.get(es[i], f2) == 10 * 2) 389 | assert(evo.has(es[i], f3) and evo.get(es[i], f3) == true) 390 | 391 | assert(f1_set_count == 1) 392 | assert(f1_inserted_count == 1) 393 | assert(f2_set_sum == 100 * 2 + 10 * 2) 394 | assert(f3_inserted_count == 1) 395 | end 396 | end 397 | end 398 | -------------------------------------------------------------------------------- /develop/benchmarks/clone_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | evo.debug_mode(false) 5 | 6 | local N = 1000 7 | 8 | local F1, F2, F3, F4, F5 = evo.id(5) 9 | local D1, D2, D3, D4, D5 = evo.id(5) 10 | 11 | evo.set(D1, evo.DEFAULT, true) 12 | evo.set(D2, evo.DEFAULT, true) 13 | evo.set(D3, evo.DEFAULT, true) 14 | evo.set(D4, evo.DEFAULT, true) 15 | evo.set(D5, evo.DEFAULT, true) 16 | 17 | evo.set(D1, evo.DUPLICATE, function(v) return not v end) 18 | evo.set(D2, evo.DUPLICATE, function(v) return not v end) 19 | evo.set(D3, evo.DUPLICATE, function(v) return not v end) 20 | evo.set(D4, evo.DUPLICATE, function(v) return not v end) 21 | evo.set(D5, evo.DUPLICATE, function(v) return not v end) 22 | 23 | local QF1 = evo.builder():include(F1):spawn() 24 | local QD1 = evo.builder():include(D1):spawn() 25 | 26 | local RF1 = evo.builder():require(F1):spawn() 27 | local RF123 = evo.builder():require(F1, F2, F3):spawn() 28 | local RF12345 = evo.builder():require(F1, F2, F3, F4, F5):spawn() 29 | 30 | local RD1 = evo.builder():require(D1):spawn() 31 | local RD123 = evo.builder():require(D1, D2, D3):spawn() 32 | local RD12345 = evo.builder():require(D1, D2, D3, D4, D5):spawn() 33 | 34 | print '----------------------------------------' 35 | 36 | basics.describe_bench( 37 | string.format('Clone Benchmarks: Simple Clone | %d entities with 1 component', N), 38 | function() 39 | local clone = evo.clone 40 | 41 | local prefab = evo.spawn { [F1] = true } 42 | 43 | for _ = 1, N do 44 | clone(prefab) 45 | end 46 | 47 | evo.batch_destroy(QF1) 48 | end) 49 | 50 | basics.describe_bench( 51 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 1 component', N), 52 | function() 53 | local clone = evo.clone 54 | 55 | local prefab = evo.spawn { [F1] = true } 56 | 57 | evo.defer() 58 | for _ = 1, N do 59 | clone(prefab) 60 | end 61 | evo.commit() 62 | 63 | evo.batch_destroy(QF1) 64 | end) 65 | 66 | basics.describe_bench( 67 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 1 component', N), 68 | function() 69 | local clone = evo.clone 70 | 71 | local prefab = evo.spawn { [D1] = true } 72 | 73 | for _ = 1, N do 74 | clone(prefab) 75 | end 76 | 77 | evo.batch_destroy(QD1) 78 | end) 79 | 80 | basics.describe_bench( 81 | string.format('Clone Benchmarks: Simple Clone | %d entities with 3 components', N), 82 | function() 83 | local clone = evo.clone 84 | 85 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true } 86 | 87 | for _ = 1, N do 88 | clone(prefab) 89 | end 90 | 91 | evo.batch_destroy(QF1) 92 | end) 93 | 94 | basics.describe_bench( 95 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 3 components', N), 96 | function() 97 | local clone = evo.clone 98 | 99 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true } 100 | 101 | evo.defer() 102 | for _ = 1, N do 103 | clone(prefab) 104 | end 105 | evo.commit() 106 | 107 | evo.batch_destroy(QF1) 108 | end) 109 | 110 | basics.describe_bench( 111 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 3 components', N), 112 | function() 113 | local clone = evo.clone 114 | 115 | local prefab = evo.spawn { [D1] = true, [D2] = true, [D3] = true } 116 | 117 | for _ = 1, N do 118 | clone(prefab) 119 | end 120 | 121 | evo.batch_destroy(QD1) 122 | end) 123 | 124 | basics.describe_bench( 125 | string.format('Clone Benchmarks: Simple Clone | %d entities with 5 components', N), 126 | function() 127 | local clone = evo.clone 128 | 129 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 130 | 131 | for _ = 1, N do 132 | clone(prefab) 133 | end 134 | 135 | evo.batch_destroy(QF1) 136 | end) 137 | 138 | basics.describe_bench( 139 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 5 components', N), 140 | function() 141 | local clone = evo.clone 142 | 143 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 144 | 145 | evo.defer() 146 | for _ = 1, N do 147 | clone(prefab) 148 | end 149 | evo.commit() 150 | 151 | evo.batch_destroy(QF1) 152 | end) 153 | 154 | basics.describe_bench( 155 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 5 components', N), 156 | function() 157 | local clone = evo.clone 158 | 159 | local prefab = evo.spawn { [D1] = true, [D2] = true, [D3] = true, [D4] = true, [D5] = true } 160 | 161 | for _ = 1, N do 162 | clone(prefab) 163 | end 164 | 165 | evo.batch_destroy(QD1) 166 | end) 167 | 168 | print '----------------------------------------' 169 | 170 | basics.describe_bench( 171 | string.format('Clone Benchmarks: Simple Clone | %d entities with 1 required component', N), 172 | function() 173 | local clone = evo.clone 174 | 175 | local prefab = evo.spawn { [RF1] = true } 176 | 177 | for _ = 1, N do 178 | clone(prefab) 179 | end 180 | 181 | evo.batch_destroy(QF1) 182 | end) 183 | 184 | basics.describe_bench( 185 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 1 required component', N), 186 | function() 187 | local clone = evo.clone 188 | 189 | local prefab = evo.spawn { [RF1] = true } 190 | 191 | evo.defer() 192 | for _ = 1, N do 193 | clone(prefab) 194 | end 195 | evo.commit() 196 | 197 | evo.batch_destroy(QF1) 198 | end) 199 | 200 | basics.describe_bench( 201 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 1 required component', N), 202 | function() 203 | local clone = evo.clone 204 | 205 | local prefab = evo.spawn { [RD1] = true } 206 | 207 | for _ = 1, N do 208 | clone(prefab) 209 | end 210 | 211 | evo.batch_destroy(QD1) 212 | end) 213 | 214 | basics.describe_bench( 215 | string.format('Clone Benchmarks: Simple Clone | %d entities with 3 required components', N), 216 | function() 217 | local clone = evo.clone 218 | 219 | local prefab = evo.spawn { [RF123] = true } 220 | 221 | for _ = 1, N do 222 | clone(prefab) 223 | end 224 | 225 | evo.batch_destroy(QF1) 226 | end) 227 | 228 | basics.describe_bench( 229 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 3 required components', N), 230 | function() 231 | local clone = evo.clone 232 | 233 | local prefab = evo.spawn { [RF123] = true } 234 | 235 | evo.defer() 236 | for _ = 1, N do 237 | clone(prefab) 238 | end 239 | evo.commit() 240 | 241 | evo.batch_destroy(QF1) 242 | end) 243 | 244 | basics.describe_bench( 245 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 3 required components', N), 246 | function() 247 | local clone = evo.clone 248 | 249 | local prefab = evo.spawn { [RD123] = true } 250 | 251 | for _ = 1, N do 252 | clone(prefab) 253 | end 254 | 255 | evo.batch_destroy(QD1) 256 | end) 257 | 258 | basics.describe_bench( 259 | string.format('Clone Benchmarks: Simple Clone | %d entities with 5 required components', N), 260 | function() 261 | local clone = evo.clone 262 | 263 | local prefab = evo.spawn { [RF12345] = true } 264 | 265 | for _ = 1, N do 266 | clone(prefab) 267 | end 268 | 269 | evo.batch_destroy(QF1) 270 | end) 271 | 272 | basics.describe_bench( 273 | string.format('Clone Benchmarks: Simple Defer Clone | %d entities with 5 required components', N), 274 | function() 275 | local clone = evo.clone 276 | 277 | local prefab = evo.spawn { [RF12345] = true } 278 | 279 | evo.defer() 280 | for _ = 1, N do 281 | clone(prefab) 282 | end 283 | evo.commit() 284 | 285 | evo.batch_destroy(QF1) 286 | end) 287 | 288 | basics.describe_bench( 289 | string.format('Clone Benchmarks: Simple Clone With Defaults | %d entities with 5 required components', N), 290 | function() 291 | local clone = evo.clone 292 | 293 | local prefab = evo.spawn { [RD12345] = true } 294 | 295 | for _ = 1, N do 296 | clone(prefab) 297 | end 298 | 299 | evo.batch_destroy(QD1) 300 | end) 301 | 302 | print '----------------------------------------' 303 | 304 | basics.describe_bench( 305 | string.format('Clone Benchmarks: Multi Clone | %d entities with 1 component', N), 306 | function() 307 | local multi_clone = evo.multi_clone 308 | 309 | local prefab = evo.spawn { [F1] = true } 310 | 311 | multi_clone(N, prefab) 312 | 313 | evo.batch_destroy(QF1) 314 | end) 315 | 316 | basics.describe_bench( 317 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 1 component', N), 318 | function() 319 | local multi_clone = evo.multi_clone 320 | 321 | local prefab = evo.spawn { [F1] = true } 322 | 323 | evo.defer() 324 | multi_clone(N, prefab) 325 | evo.commit() 326 | 327 | evo.batch_destroy(QF1) 328 | end) 329 | 330 | basics.describe_bench( 331 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 1 component', N), 332 | function() 333 | local multi_clone = evo.multi_clone 334 | 335 | local prefab = evo.spawn { [D1] = true } 336 | 337 | multi_clone(N, prefab) 338 | 339 | evo.batch_destroy(QD1) 340 | end) 341 | 342 | basics.describe_bench( 343 | string.format('Clone Benchmarks: Multi Clone | %d entities with 3 components', N), 344 | function() 345 | local multi_clone = evo.multi_clone 346 | 347 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true } 348 | 349 | multi_clone(N, prefab) 350 | 351 | evo.batch_destroy(QF1) 352 | end) 353 | 354 | basics.describe_bench( 355 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 3 components', N), 356 | function() 357 | local multi_clone = evo.multi_clone 358 | 359 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true } 360 | 361 | evo.defer() 362 | multi_clone(N, prefab) 363 | evo.commit() 364 | 365 | evo.batch_destroy(QF1) 366 | end) 367 | 368 | basics.describe_bench( 369 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 3 components', N), 370 | function() 371 | local multi_clone = evo.multi_clone 372 | 373 | local prefab = evo.spawn { [D1] = true, [D2] = true, [D3] = true } 374 | 375 | multi_clone(N, prefab) 376 | 377 | evo.batch_destroy(QD1) 378 | end) 379 | 380 | basics.describe_bench( 381 | string.format('Clone Benchmarks: Multi Clone | %d entities with 5 components', N), 382 | function() 383 | local multi_clone = evo.multi_clone 384 | 385 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 386 | 387 | multi_clone(N, prefab) 388 | 389 | evo.batch_destroy(QF1) 390 | end) 391 | 392 | basics.describe_bench( 393 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 5 components', N), 394 | function() 395 | local multi_clone = evo.multi_clone 396 | 397 | local prefab = evo.spawn { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 398 | 399 | evo.defer() 400 | multi_clone(N, prefab) 401 | evo.commit() 402 | 403 | evo.batch_destroy(QF1) 404 | end) 405 | 406 | basics.describe_bench( 407 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 5 components', N), 408 | function() 409 | local multi_clone = evo.multi_clone 410 | 411 | local prefab = evo.spawn { [D1] = true, [D2] = true, [D3] = true, [D4] = true, [D5] = true } 412 | 413 | multi_clone(N, prefab) 414 | 415 | evo.batch_destroy(QD1) 416 | end) 417 | 418 | print '----------------------------------------' 419 | 420 | basics.describe_bench( 421 | string.format('Clone Benchmarks: Multi Clone | %d entities with 1 required component', N), 422 | function() 423 | local multi_clone = evo.multi_clone 424 | 425 | local prefab = evo.spawn { [RF1] = true } 426 | 427 | multi_clone(N, prefab) 428 | 429 | evo.batch_destroy(QF1) 430 | end) 431 | 432 | basics.describe_bench( 433 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 1 required component', N), 434 | function() 435 | local multi_clone = evo.multi_clone 436 | 437 | local prefab = evo.spawn { [RF1] = true } 438 | 439 | evo.defer() 440 | multi_clone(N, prefab) 441 | evo.commit() 442 | 443 | evo.batch_destroy(QF1) 444 | end) 445 | 446 | basics.describe_bench( 447 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 1 required component', N), 448 | function() 449 | local multi_clone = evo.multi_clone 450 | 451 | local prefab = evo.spawn { [RD1] = true } 452 | 453 | multi_clone(N, prefab) 454 | 455 | evo.batch_destroy(QD1) 456 | end) 457 | 458 | basics.describe_bench( 459 | string.format('Clone Benchmarks: Multi Clone | %d entities with 3 required components', N), 460 | function() 461 | local multi_clone = evo.multi_clone 462 | 463 | local prefab = evo.spawn { [RF123] = true } 464 | 465 | multi_clone(N, prefab) 466 | 467 | evo.batch_destroy(QF1) 468 | end) 469 | 470 | basics.describe_bench( 471 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 3 required components', N), 472 | function() 473 | local multi_clone = evo.multi_clone 474 | 475 | local prefab = evo.spawn { [RF123] = true } 476 | 477 | evo.defer() 478 | multi_clone(N, prefab) 479 | evo.commit() 480 | 481 | evo.batch_destroy(QF1) 482 | end) 483 | 484 | basics.describe_bench( 485 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 3 required components', N), 486 | function() 487 | local multi_clone = evo.multi_clone 488 | 489 | local prefab = evo.spawn { [RD123] = true } 490 | 491 | multi_clone(N, prefab) 492 | 493 | evo.batch_destroy(QD1) 494 | end) 495 | 496 | basics.describe_bench( 497 | string.format('Clone Benchmarks: Multi Clone | %d entities with 5 required components', N), 498 | function() 499 | local multi_clone = evo.multi_clone 500 | 501 | local prefab = evo.spawn { [RF12345] = true } 502 | 503 | multi_clone(N, prefab) 504 | 505 | evo.batch_destroy(QF1) 506 | end) 507 | 508 | basics.describe_bench( 509 | string.format('Clone Benchmarks: Multi Defer Clone | %d entities with 5 required components', N), 510 | function() 511 | local multi_clone = evo.multi_clone 512 | 513 | local prefab = evo.spawn { [RF12345] = true } 514 | 515 | evo.defer() 516 | multi_clone(N, prefab) 517 | evo.commit() 518 | 519 | evo.batch_destroy(QF1) 520 | end) 521 | 522 | basics.describe_bench( 523 | string.format('Clone Benchmarks: Multi Clone With Defaults | %d entities with 5 required components', N), 524 | function() 525 | local multi_clone = evo.multi_clone 526 | 527 | local prefab = evo.spawn { [RD12345] = true } 528 | 529 | multi_clone(N, prefab) 530 | 531 | evo.batch_destroy(QD1) 532 | end) 533 | -------------------------------------------------------------------------------- /develop/benchmarks/spawn_bmarks.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | local basics = require 'develop.basics' 3 | 4 | evo.debug_mode(false) 5 | 6 | local N = 1000 7 | 8 | local F1, F2, F3, F4, F5 = evo.id(5) 9 | local D1, D2, D3, D4, D5 = evo.id(5) 10 | 11 | evo.set(D1, evo.DEFAULT, true) 12 | evo.set(D2, evo.DEFAULT, true) 13 | evo.set(D3, evo.DEFAULT, true) 14 | evo.set(D4, evo.DEFAULT, true) 15 | evo.set(D5, evo.DEFAULT, true) 16 | 17 | evo.set(D1, evo.DUPLICATE, function(v) return not v end) 18 | evo.set(D2, evo.DUPLICATE, function(v) return not v end) 19 | evo.set(D3, evo.DUPLICATE, function(v) return not v end) 20 | evo.set(D4, evo.DUPLICATE, function(v) return not v end) 21 | evo.set(D5, evo.DUPLICATE, function(v) return not v end) 22 | 23 | local QF1 = evo.builder():include(F1):spawn() 24 | local QD1 = evo.builder():include(D1):spawn() 25 | 26 | local RF1 = evo.builder():require(F1):spawn() 27 | local RF123 = evo.builder():require(F1, F2, F3):spawn() 28 | local RF12345 = evo.builder():require(F1, F2, F3, F4, F5):spawn() 29 | 30 | local RD1 = evo.builder():require(D1):spawn() 31 | local RD123 = evo.builder():require(D1, D2, D3):spawn() 32 | local RD12345 = evo.builder():require(D1, D2, D3, D4, D5):spawn() 33 | 34 | print '----------------------------------------' 35 | 36 | basics.describe_bench( 37 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 1 component', N), 38 | function() 39 | local spawn = evo.spawn 40 | 41 | local components = { [F1] = true } 42 | 43 | for _ = 1, N do 44 | spawn(components) 45 | end 46 | 47 | evo.batch_destroy(QF1) 48 | end) 49 | 50 | basics.describe_bench( 51 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 1 component', N), 52 | function() 53 | local spawn = evo.spawn 54 | 55 | local components = { [F1] = true } 56 | 57 | evo.defer() 58 | for _ = 1, N do 59 | spawn(components) 60 | end 61 | evo.commit() 62 | 63 | evo.batch_destroy(QF1) 64 | end) 65 | 66 | basics.describe_bench( 67 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 1 component', N), 68 | function() 69 | local spawn = evo.spawn 70 | 71 | local components = { [D1] = true } 72 | 73 | for _ = 1, N do 74 | spawn(components) 75 | end 76 | 77 | evo.batch_destroy(QD1) 78 | end) 79 | 80 | basics.describe_bench( 81 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 3 components', N), 82 | function() 83 | local spawn = evo.spawn 84 | 85 | local components = { [F1] = true, [F2] = true, [F3] = true } 86 | 87 | for _ = 1, N do 88 | spawn(components) 89 | end 90 | 91 | evo.batch_destroy(QF1) 92 | end) 93 | 94 | basics.describe_bench( 95 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 3 components', N), 96 | function() 97 | local spawn = evo.spawn 98 | 99 | local components = { [F1] = true, [F2] = true, [F3] = true } 100 | 101 | evo.defer() 102 | for _ = 1, N do 103 | spawn(components) 104 | end 105 | evo.commit() 106 | 107 | evo.batch_destroy(QF1) 108 | end) 109 | 110 | basics.describe_bench( 111 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 3 components', N), 112 | function() 113 | local spawn = evo.spawn 114 | 115 | local components = { [D1] = true, [D2] = true, [D3] = true } 116 | 117 | for _ = 1, N do 118 | spawn(components) 119 | end 120 | 121 | evo.batch_destroy(QD1) 122 | end) 123 | 124 | basics.describe_bench( 125 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 5 components', N), 126 | function() 127 | local spawn = evo.spawn 128 | 129 | local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 130 | 131 | for _ = 1, N do 132 | spawn(components) 133 | end 134 | 135 | evo.batch_destroy(QF1) 136 | end) 137 | 138 | basics.describe_bench( 139 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 5 components', N), 140 | function() 141 | local spawn = evo.spawn 142 | 143 | local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 144 | 145 | evo.defer() 146 | for _ = 1, N do 147 | spawn(components) 148 | end 149 | evo.commit() 150 | 151 | evo.batch_destroy(QF1) 152 | end) 153 | 154 | basics.describe_bench( 155 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 5 components', N), 156 | function() 157 | local spawn = evo.spawn 158 | 159 | local components = { [D1] = true, [D2] = true, [D3] = true, [D4] = true, [D5] = true } 160 | 161 | for _ = 1, N do 162 | spawn(components) 163 | end 164 | 165 | evo.batch_destroy(QD1) 166 | end) 167 | 168 | print '----------------------------------------' 169 | 170 | basics.describe_bench( 171 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 1 required component', N), 172 | function() 173 | local spawn = evo.spawn 174 | 175 | local components = { [RF1] = true } 176 | 177 | for _ = 1, N do 178 | spawn(components) 179 | end 180 | 181 | evo.batch_destroy(QF1) 182 | end) 183 | 184 | basics.describe_bench( 185 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 1 required component', N), 186 | function() 187 | local spawn = evo.spawn 188 | 189 | local components = { [RF1] = true } 190 | 191 | evo.defer() 192 | for _ = 1, N do 193 | spawn(components) 194 | end 195 | evo.commit() 196 | 197 | evo.batch_destroy(QF1) 198 | end) 199 | 200 | basics.describe_bench( 201 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 1 required component', N), 202 | function() 203 | local spawn = evo.spawn 204 | 205 | local components = { [RD1] = true } 206 | 207 | for _ = 1, N do 208 | spawn(components) 209 | end 210 | 211 | evo.batch_destroy(QD1) 212 | end) 213 | 214 | basics.describe_bench( 215 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 3 required components', N), 216 | function() 217 | local spawn = evo.spawn 218 | 219 | local components = { [RF123] = true } 220 | 221 | for _ = 1, N do 222 | spawn(components) 223 | end 224 | 225 | evo.batch_destroy(QF1) 226 | end) 227 | 228 | basics.describe_bench( 229 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 3 required components', N), 230 | function() 231 | local spawn = evo.spawn 232 | 233 | local components = { [RF123] = true } 234 | 235 | evo.defer() 236 | for _ = 1, N do 237 | spawn(components) 238 | end 239 | evo.commit() 240 | 241 | evo.batch_destroy(QF1) 242 | end) 243 | 244 | basics.describe_bench( 245 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 3 required components', N), 246 | function() 247 | local spawn = evo.spawn 248 | 249 | local components = { [RD123] = true } 250 | 251 | for _ = 1, N do 252 | spawn(components) 253 | end 254 | 255 | evo.batch_destroy(QD1) 256 | end) 257 | 258 | basics.describe_bench( 259 | string.format('Spawn Benchmarks: Simple Spawn | %d entities with 5 required components', N), 260 | function() 261 | local spawn = evo.spawn 262 | 263 | local components = { [RF12345] = true } 264 | 265 | for _ = 1, N do 266 | spawn(components) 267 | end 268 | 269 | evo.batch_destroy(QF1) 270 | end) 271 | 272 | basics.describe_bench( 273 | string.format('Spawn Benchmarks: Simple Defer Spawn | %d entities with 5 required components', N), 274 | function() 275 | local spawn = evo.spawn 276 | 277 | local components = { [RF12345] = true } 278 | 279 | evo.defer() 280 | for _ = 1, N do 281 | spawn(components) 282 | end 283 | evo.commit() 284 | 285 | evo.batch_destroy(QF1) 286 | end) 287 | 288 | basics.describe_bench( 289 | string.format('Spawn Benchmarks: Simple Spawn With Defaults | %d entities with 5 required components', N), 290 | function() 291 | local spawn = evo.spawn 292 | 293 | local components = { [RD12345] = true } 294 | 295 | for _ = 1, N do 296 | spawn(components) 297 | end 298 | 299 | evo.batch_destroy(QD1) 300 | end) 301 | 302 | print '----------------------------------------' 303 | 304 | basics.describe_bench( 305 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 1 component', N), 306 | function() 307 | local builder = evo.builder():set(F1) 308 | 309 | for _ = 1, N do 310 | builder:spawn() 311 | end 312 | 313 | evo.batch_destroy(QF1) 314 | end) 315 | 316 | basics.describe_bench( 317 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 1 component', N), 318 | function() 319 | local builder = evo.builder():set(F1) 320 | 321 | evo.defer() 322 | for _ = 1, N do 323 | builder:spawn() 324 | end 325 | evo.commit() 326 | 327 | evo.batch_destroy(QF1) 328 | end) 329 | 330 | basics.describe_bench( 331 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 1 component', N), 332 | function() 333 | local builder = evo.builder():set(D1) 334 | 335 | for _ = 1, N do 336 | builder:spawn() 337 | end 338 | 339 | evo.batch_destroy(QD1) 340 | end) 341 | 342 | basics.describe_bench( 343 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 3 components', N), 344 | function() 345 | local builder = evo.builder():set(F1):set(F2):set(F3) 346 | 347 | for _ = 1, N do 348 | builder:spawn() 349 | end 350 | 351 | evo.batch_destroy(QF1) 352 | end) 353 | 354 | basics.describe_bench( 355 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 3 components', N), 356 | function() 357 | local builder = evo.builder():set(F1):set(F2):set(F3) 358 | 359 | evo.defer() 360 | for _ = 1, N do 361 | builder:spawn() 362 | end 363 | evo.commit() 364 | 365 | evo.batch_destroy(QF1) 366 | end) 367 | 368 | basics.describe_bench( 369 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 3 components', N), 370 | function() 371 | local builder = evo.builder():set(D1):set(D2):set(D3) 372 | 373 | for _ = 1, N do 374 | builder:spawn() 375 | end 376 | 377 | evo.batch_destroy(QD1) 378 | end) 379 | 380 | basics.describe_bench( 381 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 5 components', N), 382 | function() 383 | local builder = evo.builder():set(F1):set(F2):set(F3):set(F4):set(F5) 384 | 385 | for _ = 1, N do 386 | builder:spawn() 387 | end 388 | 389 | evo.batch_destroy(QF1) 390 | end) 391 | 392 | basics.describe_bench( 393 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 5 components', N), 394 | function() 395 | local builder = evo.builder():set(F1):set(F2):set(F3):set(F4):set(F5) 396 | 397 | evo.defer() 398 | for _ = 1, N do 399 | builder:spawn() 400 | end 401 | evo.commit() 402 | 403 | evo.batch_destroy(QF1) 404 | end) 405 | 406 | basics.describe_bench( 407 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 5 components', N), 408 | function() 409 | local builder = evo.builder():set(D1):set(D2):set(D3):set(D4):set(D5) 410 | 411 | for _ = 1, N do 412 | builder:spawn() 413 | end 414 | 415 | evo.batch_destroy(QD1) 416 | end) 417 | 418 | print '----------------------------------------' 419 | 420 | basics.describe_bench( 421 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 1 required component', N), 422 | function() 423 | local builder = evo.builder():set(RF1) 424 | 425 | for _ = 1, N do 426 | builder:spawn() 427 | end 428 | 429 | evo.batch_destroy(QF1) 430 | end) 431 | 432 | basics.describe_bench( 433 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 1 required component', N), 434 | function() 435 | local builder = evo.builder():set(RF1) 436 | 437 | evo.defer() 438 | for _ = 1, N do 439 | builder:spawn() 440 | end 441 | evo.commit() 442 | 443 | evo.batch_destroy(QF1) 444 | end) 445 | 446 | basics.describe_bench( 447 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 1 required component', N), 448 | function() 449 | local builder = evo.builder():set(RD1) 450 | 451 | for _ = 1, N do 452 | builder:spawn() 453 | end 454 | 455 | evo.batch_destroy(QD1) 456 | end) 457 | 458 | basics.describe_bench( 459 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 3 required components', N), 460 | function() 461 | local builder = evo.builder():set(RF123) 462 | 463 | for _ = 1, N do 464 | builder:spawn() 465 | end 466 | 467 | evo.batch_destroy(QF1) 468 | end) 469 | 470 | basics.describe_bench( 471 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 3 required components', N), 472 | function() 473 | local builder = evo.builder():set(RF123) 474 | 475 | evo.defer() 476 | for _ = 1, N do 477 | builder:spawn() 478 | end 479 | evo.commit() 480 | 481 | evo.batch_destroy(QF1) 482 | end) 483 | 484 | basics.describe_bench( 485 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 3 required components', N), 486 | function() 487 | local builder = evo.builder():set(RD123) 488 | 489 | for _ = 1, N do 490 | builder:spawn() 491 | end 492 | 493 | evo.batch_destroy(QD1) 494 | end) 495 | 496 | basics.describe_bench( 497 | string.format('Spawn Benchmarks: Builder Spawn | %d entities with 5 required components', N), 498 | function() 499 | local builder = evo.builder():set(RF12345) 500 | 501 | for _ = 1, N do 502 | builder:spawn() 503 | end 504 | 505 | evo.batch_destroy(QF1) 506 | end) 507 | 508 | basics.describe_bench( 509 | string.format('Spawn Benchmarks: Builder Defer Spawn | %d entities with 5 required components', N), 510 | function() 511 | local builder = evo.builder():set(RF12345) 512 | 513 | evo.defer() 514 | for _ = 1, N do 515 | builder:spawn() 516 | end 517 | evo.commit() 518 | 519 | evo.batch_destroy(QF1) 520 | end) 521 | 522 | basics.describe_bench( 523 | string.format('Spawn Benchmarks: Builder Spawn With Defaults | %d entities with 5 required components', N), 524 | function() 525 | local builder = evo.builder():set(RD12345) 526 | 527 | for _ = 1, N do 528 | builder:spawn() 529 | end 530 | 531 | evo.batch_destroy(QD1) 532 | end) 533 | 534 | print '----------------------------------------' 535 | 536 | basics.describe_bench( 537 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 1 component', N), 538 | function() 539 | local multi_spawn = evo.multi_spawn 540 | 541 | local components = { [F1] = true } 542 | 543 | multi_spawn(N, components) 544 | 545 | evo.batch_destroy(QF1) 546 | end) 547 | 548 | basics.describe_bench( 549 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 1 component', N), 550 | function() 551 | local multi_spawn = evo.multi_spawn 552 | 553 | local components = { [F1] = true } 554 | 555 | evo.defer() 556 | multi_spawn(N, components) 557 | evo.commit() 558 | 559 | evo.batch_destroy(QF1) 560 | end) 561 | 562 | basics.describe_bench( 563 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 1 component', N), 564 | function() 565 | local multi_spawn = evo.multi_spawn 566 | 567 | local components = { [D1] = true } 568 | 569 | multi_spawn(N, components) 570 | 571 | evo.batch_destroy(QD1) 572 | end) 573 | 574 | basics.describe_bench( 575 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 3 components', N), 576 | function() 577 | local multi_spawn = evo.multi_spawn 578 | 579 | local components = { [F1] = true, [F2] = true, [F3] = true } 580 | 581 | multi_spawn(N, components) 582 | 583 | evo.batch_destroy(QF1) 584 | end) 585 | 586 | basics.describe_bench( 587 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 3 components', N), 588 | function() 589 | local multi_spawn = evo.multi_spawn 590 | 591 | local components = { [F1] = true, [F2] = true, [F3] = true } 592 | 593 | evo.defer() 594 | multi_spawn(N, components) 595 | evo.commit() 596 | 597 | evo.batch_destroy(QF1) 598 | end) 599 | 600 | basics.describe_bench( 601 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 3 components', N), 602 | function() 603 | local multi_spawn = evo.multi_spawn 604 | 605 | local components = { [D1] = true, [D2] = true, [D3] = true } 606 | 607 | multi_spawn(N, components) 608 | 609 | evo.batch_destroy(QD1) 610 | end) 611 | 612 | basics.describe_bench( 613 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 5 components', N), 614 | function() 615 | local multi_spawn = evo.multi_spawn 616 | 617 | local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 618 | 619 | multi_spawn(N, components) 620 | 621 | evo.batch_destroy(QF1) 622 | end) 623 | 624 | basics.describe_bench( 625 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 5 components', N), 626 | function() 627 | local multi_spawn = evo.multi_spawn 628 | 629 | local components = { [F1] = true, [F2] = true, [F3] = true, [F4] = true, [F5] = true } 630 | 631 | evo.defer() 632 | multi_spawn(N, components) 633 | evo.commit() 634 | 635 | evo.batch_destroy(QF1) 636 | end) 637 | 638 | basics.describe_bench( 639 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 5 components', N), 640 | function() 641 | local multi_spawn = evo.multi_spawn 642 | 643 | local components = { [D1] = true, [D2] = true, [D3] = true, [D4] = true, [D5] = true } 644 | 645 | multi_spawn(N, components) 646 | 647 | evo.batch_destroy(QD1) 648 | end) 649 | 650 | print '----------------------------------------' 651 | 652 | basics.describe_bench( 653 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 1 required component', N), 654 | function() 655 | local multi_spawn = evo.multi_spawn 656 | 657 | local components = { [F1] = true } 658 | 659 | multi_spawn(N, components) 660 | 661 | evo.batch_destroy(QF1) 662 | end) 663 | 664 | basics.describe_bench( 665 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 1 required component', N), 666 | function() 667 | local multi_spawn = evo.multi_spawn 668 | 669 | local components = { [F1] = true } 670 | 671 | evo.defer() 672 | multi_spawn(N, components) 673 | evo.commit() 674 | 675 | evo.batch_destroy(QF1) 676 | end) 677 | 678 | basics.describe_bench( 679 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 1 required component', N), 680 | function() 681 | local multi_spawn = evo.multi_spawn 682 | 683 | local components = { [D1] = true } 684 | 685 | multi_spawn(N, components) 686 | 687 | evo.batch_destroy(QD1) 688 | end) 689 | 690 | basics.describe_bench( 691 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 3 required components', N), 692 | function() 693 | local multi_spawn = evo.multi_spawn 694 | 695 | local components = { [RF123] = true } 696 | 697 | multi_spawn(N, components) 698 | 699 | evo.batch_destroy(QF1) 700 | end) 701 | 702 | basics.describe_bench( 703 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 3 required components', N), 704 | function() 705 | local multi_spawn = evo.multi_spawn 706 | 707 | local components = { [RF123] = true } 708 | 709 | evo.defer() 710 | multi_spawn(N, components) 711 | evo.commit() 712 | 713 | evo.batch_destroy(QF1) 714 | end) 715 | 716 | basics.describe_bench( 717 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 3 required components', N), 718 | function() 719 | local multi_spawn = evo.multi_spawn 720 | 721 | local components = { [RD123] = true } 722 | 723 | multi_spawn(N, components) 724 | 725 | evo.batch_destroy(QD1) 726 | end) 727 | 728 | basics.describe_bench( 729 | string.format('Spawn Benchmarks: Multi Spawn | %d entities with 5 required components', N), 730 | function() 731 | local multi_spawn = evo.multi_spawn 732 | 733 | local components = { [RF12345] = true } 734 | 735 | multi_spawn(N, components) 736 | 737 | evo.batch_destroy(QF1) 738 | end) 739 | 740 | basics.describe_bench( 741 | string.format('Spawn Benchmarks: Multi Defer Spawn | %d entities with 5 required components', N), 742 | function() 743 | local multi_spawn = evo.multi_spawn 744 | 745 | local components = { [RF12345] = true } 746 | 747 | evo.defer() 748 | multi_spawn(N, components) 749 | evo.commit() 750 | 751 | evo.batch_destroy(QF1) 752 | end) 753 | 754 | basics.describe_bench( 755 | string.format('Spawn Benchmarks: Multi Spawn With Defaults | %d entities with 5 required components', N), 756 | function() 757 | local multi_spawn = evo.multi_spawn 758 | 759 | local components = { [RD12345] = true } 760 | 761 | multi_spawn(N, components) 762 | 763 | evo.batch_destroy(QD1) 764 | end) 765 | -------------------------------------------------------------------------------- /develop/testing/multi_spawn_tests.lua: -------------------------------------------------------------------------------- 1 | local evo = require 'evolved' 2 | 3 | do 4 | local entity_list 5 | 6 | do 7 | entity_list = evo.multi_spawn(0) 8 | assert(entity_list and #entity_list == 0) 9 | 10 | entity_list = evo.multi_spawn(0, {}) 11 | assert(entity_list and #entity_list == 0) 12 | end 13 | 14 | do 15 | entity_list = evo.multi_spawn(-1) 16 | assert(entity_list and #entity_list == 0) 17 | 18 | entity_list = evo.multi_spawn(-1, {}) 19 | assert(entity_list and #entity_list == 0) 20 | end 21 | 22 | do 23 | entity_list = evo.builder():multi_spawn(0) 24 | assert(entity_list and #entity_list == 0) 25 | end 26 | 27 | do 28 | entity_list = evo.builder():multi_spawn(-1) 29 | assert(entity_list and #entity_list == 0) 30 | end 31 | end 32 | 33 | do 34 | local entity_list 35 | 36 | do 37 | entity_list = evo.multi_spawn(1) 38 | assert(entity_list and #entity_list == 1) 39 | assert(entity_list[1] and evo.empty(entity_list[1])) 40 | assert(not entity_list[2]) 41 | 42 | entity_list = evo.multi_spawn(1, {}) 43 | assert(entity_list and #entity_list == 1) 44 | assert(entity_list[1] and evo.empty(entity_list[1])) 45 | assert(not entity_list[2]) 46 | end 47 | 48 | do 49 | entity_list = evo.multi_spawn(2) 50 | assert(entity_list and #entity_list == 2) 51 | assert(entity_list[1] and evo.empty(entity_list[1])) 52 | assert(entity_list[2] and evo.empty(entity_list[2])) 53 | assert(not entity_list[3]) 54 | 55 | entity_list = evo.multi_spawn(2, {}) 56 | assert(entity_list and #entity_list == 2) 57 | assert(entity_list[1] and evo.empty(entity_list[1])) 58 | assert(entity_list[2] and evo.empty(entity_list[2])) 59 | assert(not entity_list[3]) 60 | end 61 | 62 | do 63 | entity_list = evo.builder():multi_spawn(1) 64 | assert(entity_list and #entity_list == 1) 65 | assert(entity_list[1] and evo.empty(entity_list[1])) 66 | assert(not entity_list[2]) 67 | end 68 | 69 | do 70 | entity_list = evo.builder():multi_spawn(2) 71 | assert(entity_list and #entity_list == 2) 72 | assert(entity_list[1] and evo.empty(entity_list[1])) 73 | assert(entity_list[2] and evo.empty(entity_list[2])) 74 | assert(not entity_list[3]) 75 | end 76 | end 77 | 78 | do 79 | local entity_list 80 | 81 | local prefab = evo.id() 82 | 83 | do 84 | entity_list = evo.multi_clone(0, prefab) 85 | assert(entity_list and #entity_list == 0) 86 | 87 | entity_list = evo.multi_clone(0, prefab, {}) 88 | assert(entity_list and #entity_list == 0) 89 | end 90 | 91 | do 92 | entity_list = evo.multi_clone(-1, prefab) 93 | assert(entity_list and #entity_list == 0) 94 | 95 | entity_list = evo.multi_clone(-1, prefab, {}) 96 | assert(entity_list and #entity_list == 0) 97 | end 98 | 99 | do 100 | entity_list = evo.builder():multi_clone(0, prefab) 101 | assert(entity_list and #entity_list == 0) 102 | end 103 | 104 | do 105 | entity_list = evo.builder():multi_clone(-1, prefab) 106 | assert(entity_list and #entity_list == 0) 107 | end 108 | end 109 | 110 | do 111 | local entity_list 112 | 113 | local prefab = evo.id() 114 | 115 | do 116 | entity_list = evo.multi_clone(1, prefab) 117 | assert(entity_list and #entity_list == 1) 118 | assert(entity_list[1] and evo.empty(entity_list[1])) 119 | assert(not entity_list[2]) 120 | 121 | entity_list = evo.multi_clone(1, prefab, {}) 122 | assert(entity_list and #entity_list == 1) 123 | assert(entity_list[1] and evo.empty(entity_list[1])) 124 | assert(not entity_list[2]) 125 | end 126 | 127 | do 128 | entity_list = evo.multi_clone(2, prefab) 129 | assert(entity_list and #entity_list == 2) 130 | assert(entity_list[1] and evo.empty(entity_list[1])) 131 | assert(entity_list[2] and evo.empty(entity_list[2])) 132 | assert(not entity_list[3]) 133 | 134 | entity_list = evo.multi_clone(2, prefab, {}) 135 | assert(entity_list and #entity_list == 2) 136 | assert(entity_list[1] and evo.empty(entity_list[1])) 137 | assert(entity_list[2] and evo.empty(entity_list[2])) 138 | assert(not entity_list[3]) 139 | end 140 | 141 | do 142 | entity_list = evo.builder():multi_clone(1, prefab) 143 | assert(entity_list and #entity_list == 1) 144 | assert(entity_list[1] and evo.empty(entity_list[1])) 145 | assert(not entity_list[2]) 146 | end 147 | 148 | do 149 | entity_list = evo.builder():multi_clone(2, prefab) 150 | assert(entity_list and #entity_list == 2) 151 | assert(entity_list[1] and evo.empty(entity_list[1])) 152 | assert(entity_list[2] and evo.empty(entity_list[2])) 153 | assert(not entity_list[3]) 154 | end 155 | end 156 | 157 | do 158 | local f1, f2 = evo.id(2) 159 | 160 | do 161 | local entity_list 162 | 163 | entity_list = evo.multi_spawn(2, { [f1] = true, [f2] = 123 }) 164 | assert(entity_list and #entity_list == 2) 165 | assert(entity_list[1] and evo.get(entity_list[1], f1) == true and evo.get(entity_list[1], f2) == 123) 166 | assert(entity_list[2] and evo.get(entity_list[2], f1) == true and evo.get(entity_list[2], f2) == 123) 167 | 168 | entity_list = evo.multi_spawn(2, { [f1] = false, [f2] = 456 }) 169 | assert(entity_list and #entity_list == 2) 170 | assert(entity_list[1] and evo.get(entity_list[1], f1) == false and evo.get(entity_list[1], f2) == 456) 171 | assert(entity_list[2] and evo.get(entity_list[2], f1) == false and evo.get(entity_list[2], f2) == 456) 172 | end 173 | 174 | do 175 | local prefab = evo.builder():set(f1, true):set(f2, 123):spawn() 176 | 177 | local entity_list 178 | 179 | entity_list = evo.multi_clone(2, prefab) 180 | assert(entity_list and #entity_list == 2) 181 | assert(entity_list[1] and evo.get(entity_list[1], f1) == true and evo.get(entity_list[1], f2) == 123) 182 | assert(entity_list[2] and evo.get(entity_list[2], f1) == true and evo.get(entity_list[2], f2) == 123) 183 | 184 | entity_list = evo.multi_clone(2, prefab, {}) 185 | assert(entity_list and #entity_list == 2) 186 | assert(entity_list[1] and evo.get(entity_list[1], f1) == true and evo.get(entity_list[1], f2) == 123) 187 | assert(entity_list[2] and evo.get(entity_list[2], f1) == true and evo.get(entity_list[2], f2) == 123) 188 | 189 | entity_list = evo.multi_clone(2, prefab, { [f1] = false, [f2] = 456 }) 190 | assert(entity_list and #entity_list == 2) 191 | assert(entity_list[1] and evo.get(entity_list[1], f1) == false and evo.get(entity_list[1], f2) == 456) 192 | assert(entity_list[2] and evo.get(entity_list[2], f1) == false and evo.get(entity_list[2], f2) == 456) 193 | end 194 | end 195 | 196 | do 197 | local f1, f2, f3 = evo.id(3) 198 | 199 | do 200 | local entity_list1, entity_list2 201 | 202 | evo.defer() 203 | do 204 | entity_list1 = evo.multi_spawn(2, { [f1] = 42, [f2] = "hello", [f3] = false }) 205 | assert(entity_list1 and #entity_list1 == 2) 206 | assert(entity_list1[1] and evo.empty(entity_list1[1])) 207 | assert(entity_list1[2] and evo.empty(entity_list1[2])) 208 | assert(not entity_list1[3]) 209 | 210 | entity_list2 = evo.multi_spawn(3, { [f2] = "world", [f3] = true }) 211 | assert(entity_list2 and #entity_list2 == 3) 212 | assert(entity_list2[1] and evo.empty(entity_list2[1])) 213 | assert(entity_list2[2] and evo.empty(entity_list2[2])) 214 | assert(entity_list2[3] and evo.empty(entity_list2[3])) 215 | end 216 | evo.commit() 217 | do 218 | assert(entity_list1 and #entity_list1 == 2) 219 | assert(entity_list1[1] and not evo.empty(entity_list1[1])) 220 | assert(entity_list1[2] and not evo.empty(entity_list1[2])) 221 | assert(not entity_list1[3]) 222 | assert( 223 | evo.get(entity_list1[1], f1) == 42 and 224 | evo.get(entity_list1[1], f2) == "hello" and 225 | evo.get(entity_list1[1], f3) == false) 226 | assert( 227 | evo.get(entity_list1[2], f1) == 42 and 228 | evo.get(entity_list1[2], f2) == "hello" and 229 | evo.get(entity_list1[2], f3) == false) 230 | 231 | assert(entity_list2 and #entity_list2 == 3) 232 | assert(entity_list2[1] and not evo.empty(entity_list2[1])) 233 | assert(entity_list2[2] and not evo.empty(entity_list2[2])) 234 | assert(entity_list2[3] and not evo.empty(entity_list2[3])) 235 | assert(not entity_list2[4]) 236 | assert( 237 | evo.get(entity_list2[1], f1) == nil and 238 | evo.get(entity_list2[1], f2) == "world" and 239 | evo.get(entity_list2[1], f3) == true) 240 | assert( 241 | evo.get(entity_list2[2], f1) == nil and 242 | evo.get(entity_list2[2], f2) == "world" and 243 | evo.get(entity_list2[2], f3) == true) 244 | assert( 245 | evo.get(entity_list2[3], f1) == nil and 246 | evo.get(entity_list2[3], f2) == "world" and 247 | evo.get(entity_list2[3], f3) == true) 248 | end 249 | end 250 | end 251 | 252 | do 253 | local f1, f2, f3 = evo.id(3) 254 | 255 | do 256 | local prefab = evo.builder():set(f1, false):set(f2, 123):spawn() 257 | 258 | local entity_list1, entity_list2 259 | 260 | evo.defer() 261 | do 262 | entity_list1 = evo.multi_clone(2, prefab) 263 | assert(entity_list1 and #entity_list1 == 2) 264 | assert(entity_list1[1] and evo.empty(entity_list1[1])) 265 | assert(entity_list1[2] and evo.empty(entity_list1[2])) 266 | assert(not entity_list1[3]) 267 | 268 | entity_list2 = evo.multi_clone(3, prefab, { [f2] = 456, [f3] = "world" }) 269 | assert(entity_list2 and #entity_list2 == 3) 270 | assert(entity_list2[1] and evo.empty(entity_list2[1])) 271 | assert(entity_list2[2] and evo.empty(entity_list2[2])) 272 | assert(entity_list2[3] and evo.empty(entity_list2[3])) 273 | end 274 | evo.commit() 275 | do 276 | assert(entity_list1 and #entity_list1 == 2) 277 | assert(entity_list1[1] and not evo.empty(entity_list1[1])) 278 | assert(entity_list1[2] and not evo.empty(entity_list1[2])) 279 | assert(not entity_list1[3]) 280 | assert( 281 | evo.get(entity_list1[1], f1) == false and 282 | evo.get(entity_list1[1], f2) == 123 and 283 | evo.get(entity_list1[1], f3) == nil) 284 | assert( 285 | evo.get(entity_list1[2], f1) == false and 286 | evo.get(entity_list1[2], f2) == 123 and 287 | evo.get(entity_list1[2], f3) == nil) 288 | 289 | assert(entity_list2 and #entity_list2 == 3) 290 | assert(entity_list2[1] and not evo.empty(entity_list2[1])) 291 | assert(entity_list2[2] and not evo.empty(entity_list2[2])) 292 | assert(entity_list2[3] and not evo.empty(entity_list2[3])) 293 | assert(not entity_list2[4]) 294 | assert( 295 | evo.get(entity_list2[1], f1) == false and 296 | evo.get(entity_list2[1], f2) == 456 and 297 | evo.get(entity_list2[1], f3) == "world") 298 | assert( 299 | evo.get(entity_list2[2], f1) == false and 300 | evo.get(entity_list2[2], f2) == 456 and 301 | evo.get(entity_list2[2], f3) == "world") 302 | assert( 303 | evo.get(entity_list2[3], f1) == false and 304 | evo.get(entity_list2[3], f2) == 456 and 305 | evo.get(entity_list2[3], f3) == "world") 306 | end 307 | end 308 | end 309 | 310 | do 311 | local f1, f2, f3 = evo.id(3) 312 | 313 | evo.set(f1, evo.REQUIRES, { f2, f3 }) 314 | evo.set(f3, evo.TAG) 315 | 316 | do 317 | local entity_list, entity_count = evo.multi_spawn(2, { [f1] = 42 }) 318 | 319 | assert(entity_list and #entity_list == 2) 320 | assert(entity_count == 2) 321 | 322 | for i = 1, entity_count do 323 | local e = entity_list[i] 324 | assert(e and not evo.empty(e)) 325 | 326 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 327 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 328 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 329 | end 330 | end 331 | 332 | do 333 | local entity_prefab = evo.builder():set(f1, 42):spawn() 334 | 335 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab) 336 | 337 | assert(clone_list and #clone_list == 2) 338 | assert(clone_count == 2) 339 | 340 | for i = 1, clone_count do 341 | local e = clone_list[i] 342 | assert(e and not evo.empty(e)) 343 | 344 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 345 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 346 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 347 | end 348 | end 349 | 350 | do 351 | local entity_prefab = evo.builder():set(f1, 42):spawn() 352 | evo.remove(entity_prefab, f2, f3) 353 | 354 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab, { [f1] = 21 }) 355 | 356 | assert(clone_list and #clone_list == 2) 357 | assert(clone_count == 2) 358 | 359 | for i = 1, clone_count do 360 | local e = clone_list[i] 361 | assert(e and not evo.empty(e)) 362 | 363 | assert(evo.has(e, f1) and evo.get(e, f1) == 21) 364 | assert(evo.has(e, f2) and evo.get(e, f2) == true) 365 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 366 | end 367 | end 368 | 369 | evo.set(f2, evo.DEFAULT, false) 370 | 371 | do 372 | local entity_list, entity_count = evo.multi_spawn(2, { [f1] = 42 }) 373 | 374 | assert(entity_list and #entity_list == 2) 375 | assert(entity_count == 2) 376 | 377 | for i = 1, entity_count do 378 | local e = entity_list[i] 379 | assert(e and not evo.empty(e)) 380 | 381 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 382 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 383 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 384 | end 385 | end 386 | 387 | do 388 | local entity_prefab = evo.builder():set(f1, 42):spawn() 389 | 390 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab) 391 | 392 | assert(clone_list and #clone_list == 2) 393 | assert(clone_count == 2) 394 | 395 | for i = 1, clone_count do 396 | local e = clone_list[i] 397 | assert(e and not evo.empty(e)) 398 | 399 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 400 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 401 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 402 | end 403 | end 404 | 405 | do 406 | local entity_prefab = evo.builder():set(f1, 42):spawn() 407 | evo.remove(entity_prefab, f2, f3) 408 | 409 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab, { [f1] = 21 }) 410 | 411 | assert(clone_list and #clone_list == 2) 412 | assert(clone_count == 2) 413 | 414 | for i = 1, clone_count do 415 | local e = clone_list[i] 416 | assert(e and not evo.empty(e)) 417 | 418 | assert(evo.has(e, f1) and evo.get(e, f1) == 21) 419 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 420 | assert(evo.has(e, f3) and evo.get(e, f3) == nil) 421 | end 422 | end 423 | 424 | local v_set_sum = 0 425 | local v_insert_sum = 0 426 | 427 | local f3_set_times = 0 428 | local f3_insert_times = 0 429 | 430 | evo.set(f1, evo.ON_SET, function(e, f, v) 431 | assert(f == f1) 432 | v_set_sum = v_set_sum + v 433 | assert(evo.get(e, f) == v) 434 | end) 435 | 436 | evo.set(f1, evo.ON_INSERT, function(e, f, v) 437 | assert(f == f1) 438 | v_insert_sum = v_insert_sum + v 439 | assert(evo.get(e, f) == v) 440 | end) 441 | 442 | evo.set(f3, evo.ON_SET, function(e, f, v) 443 | assert(f == f3) 444 | f3_set_times = f3_set_times + 1 445 | assert(v == nil) 446 | assert(evo.has(e, f)) 447 | end) 448 | 449 | evo.set(f3, evo.ON_INSERT, function(e, f, v) 450 | assert(f == f3) 451 | f3_insert_times = f3_insert_times + 1 452 | assert(v == nil) 453 | assert(evo.has(e, f)) 454 | end) 455 | 456 | do 457 | local entity_list, entity_count = evo.multi_spawn(2, { [f1] = 42 }) 458 | 459 | assert(entity_list and #entity_list == 2) 460 | assert(entity_count == 2) 461 | 462 | for i = 1, entity_count do 463 | local e = entity_list[i] 464 | assert(e and not evo.empty(e)) 465 | 466 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 467 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 468 | end 469 | end 470 | 471 | do 472 | local entity_prefab = evo.builder():set(f1, 42):spawn() 473 | 474 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab) 475 | 476 | assert(clone_list and #clone_list == 2) 477 | assert(clone_count == 2) 478 | 479 | for i = 1, clone_count do 480 | local e = clone_list[i] 481 | assert(e and not evo.empty(e)) 482 | 483 | assert(evo.has(e, f1) and evo.get(e, f1) == 42) 484 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 485 | end 486 | end 487 | 488 | do 489 | local entity_prefab = evo.builder():set(f1, 42):spawn() 490 | evo.remove(entity_prefab, f2, f3) 491 | 492 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab, { [f1] = 21 }) 493 | 494 | assert(clone_list and #clone_list == 2) 495 | assert(clone_count == 2) 496 | 497 | for i = 1, clone_count do 498 | local e = clone_list[i] 499 | assert(e and not evo.empty(e)) 500 | 501 | assert(evo.has(e, f1) and evo.get(e, f1) == 21) 502 | assert(evo.has(e, f2) and evo.get(e, f2) == false) 503 | end 504 | end 505 | 506 | assert(v_set_sum == 42 * 6 + 21 * 2) 507 | assert(v_insert_sum == 42 * 6 + 21 * 2) 508 | 509 | assert(f3_set_times == 8) 510 | assert(f3_insert_times == 8) 511 | end 512 | 513 | do 514 | local function v2(x, y) return { x = x or 0, y = y or 0 } end 515 | local function v2_clone(v) return { x = v.x, y = v.y } end 516 | 517 | local f1, f2, f3, f4 = evo.id(4) 518 | evo.set(f1, evo.REQUIRES, { f2, f3, f4 }) 519 | 520 | local f1_default = v2(1, 2) 521 | local f2_default = v2(3, 4) 522 | local f3_default = v2(10, 11) 523 | local f4_default = v2(12, 13) 524 | 525 | evo.set(f1, evo.DEFAULT, f1_default) 526 | evo.set(f2, evo.DEFAULT, f2_default) 527 | evo.set(f3, evo.DEFAULT, f3_default) 528 | evo.set(f4, evo.DEFAULT, f4_default) 529 | 530 | evo.set(f1, evo.DUPLICATE, v2_clone) 531 | evo.set(f2, evo.DUPLICATE, v2_clone) 532 | evo.set(f3, evo.DUPLICATE, v2_clone) 533 | 534 | do 535 | local entity_list, entity_count = evo.multi_spawn(2, { [f1] = v2(5, 6), [f2] = v2(7, 8) }) 536 | 537 | assert(entity_list and #entity_list == 2) 538 | assert(entity_count == 2) 539 | 540 | for i = 1, entity_count do 541 | local e = entity_list[i] 542 | assert(e and not evo.empty(e)) 543 | 544 | assert(evo.has(e, f1) and evo.get(e, f1) ~= f1_default) 545 | assert(evo.get(e, f1).x == 5 and evo.get(e, f1).y == 6) 546 | 547 | assert(evo.has(e, f2) and evo.get(e, f2) ~= f2_default) 548 | assert(evo.get(e, f2).x == 7 and evo.get(e, f2).y == 8) 549 | 550 | assert(evo.has(e, f3) and evo.get(e, f3) ~= f3_default) 551 | assert(evo.get(e, f3).x == 10 and evo.get(e, f3).y == 11) 552 | 553 | assert(evo.has(e, f4) and evo.get(e, f4) == f4_default) 554 | end 555 | end 556 | 557 | do 558 | local entity_prefab = evo.builder():set(f1, v2(5, 6)):set(f2, v2(7, 8)):spawn() 559 | 560 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab, { [f2] = f2_default }) 561 | 562 | assert(clone_list and #clone_list == 2) 563 | assert(clone_count == 2) 564 | 565 | for i = 1, clone_count do 566 | local e = clone_list[i] 567 | assert(e and not evo.empty(e)) 568 | 569 | assert(evo.has(e, f1) and evo.get(e, f1) ~= f1_default and evo.get(e, f1) ~= evo.get(entity_prefab, f1)) 570 | assert(evo.get(e, f1).x == 5 and evo.get(e, f1).y == 6) 571 | 572 | assert(evo.has(e, f2) and evo.get(e, f2) ~= f2_default and evo.get(e, f2) ~= evo.get(entity_prefab, f2)) 573 | assert(evo.get(e, f2).x == 3 and evo.get(e, f2).y == 4) 574 | 575 | assert(evo.has(e, f3) and evo.get(e, f3) ~= f3_default) 576 | assert(evo.get(e, f3).x == 10 and evo.get(e, f3).y == 11) 577 | 578 | assert(evo.has(e, f4) and evo.get(e, f4) == f4_default) 579 | end 580 | end 581 | 582 | do 583 | local entity_prefab = evo.builder():set(f1, v2(5, 6)):set(f2, v2(7, 8)):spawn() 584 | evo.remove(entity_prefab, f2, f3, f4) 585 | 586 | local clone_list, clone_count = evo.multi_clone(2, entity_prefab, { [f2] = f2_default }) 587 | 588 | assert(clone_list and #clone_list == 2) 589 | assert(clone_count == 2) 590 | 591 | for i = 1, clone_count do 592 | local e = clone_list[i] 593 | assert(e and not evo.empty(e)) 594 | 595 | assert(evo.has(e, f1) and evo.get(e, f1) ~= f1_default and evo.get(e, f1) ~= evo.get(entity_prefab, f1)) 596 | assert(evo.get(e, f1).x == 5 and evo.get(e, f1).y == 6) 597 | 598 | assert(evo.has(e, f2) and evo.get(e, f2) ~= f2_default and evo.get(e, f2) ~= evo.get(entity_prefab, f2)) 599 | assert(evo.get(e, f2).x == 3 and evo.get(e, f2).y == 4) 600 | 601 | assert(evo.has(e, f3) and evo.get(e, f3) ~= f3_default) 602 | assert(evo.get(e, f3).x == 10 and evo.get(e, f3).y == 11) 603 | 604 | assert(evo.has(e, f4) and evo.get(e, f4) == f4_default) 605 | end 606 | end 607 | end 608 | -------------------------------------------------------------------------------- /develop/3rdparty/tiny.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2016 Calvin Rose 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | ]] 21 | 22 | --- @module tiny-ecs 23 | -- @author Calvin Rose 24 | -- @license MIT 25 | -- @copyright 2016 26 | local tiny = {} 27 | 28 | -- Local versions of standard lua functions 29 | local tinsert = table.insert 30 | local tremove = table.remove 31 | local tsort = table.sort 32 | local setmetatable = setmetatable 33 | local type = type 34 | local select = select 35 | 36 | -- Local versions of the library functions 37 | local tiny_manageEntities 38 | local tiny_manageSystems 39 | local tiny_addEntity 40 | local tiny_addSystem 41 | local tiny_add 42 | local tiny_removeEntity 43 | local tiny_removeSystem 44 | 45 | --- Filter functions. 46 | -- A Filter is a function that selects which Entities apply to a System. 47 | -- Filters take two parameters, the System and the Entity, and return a boolean 48 | -- value indicating if the Entity should be processed by the System. A truthy 49 | -- value includes the entity, while a falsey (nil or false) value excludes the 50 | -- entity. 51 | -- 52 | -- Filters must be added to Systems by setting the `filter` field of the System. 53 | -- Filter's returned by tiny-ecs's Filter functions are immutable and can be 54 | -- used by multiple Systems. 55 | -- 56 | -- local f1 = tiny.requireAll("position", "velocity", "size") 57 | -- local f2 = tiny.requireAny("position", "velocity", "size") 58 | -- 59 | -- local e1 = { 60 | -- position = {2, 3}, 61 | -- velocity = {3, 3}, 62 | -- size = {4, 4} 63 | -- } 64 | -- 65 | -- local entity2 = { 66 | -- position = {4, 5}, 67 | -- size = {4, 4} 68 | -- } 69 | -- 70 | -- local e3 = { 71 | -- position = {2, 3}, 72 | -- velocity = {3, 3} 73 | -- } 74 | -- 75 | -- print(f1(nil, e1), f1(nil, e2), f1(nil, e3)) -- prints true, false, false 76 | -- print(f2(nil, e1), f2(nil, e2), f2(nil, e3)) -- prints true, true, true 77 | -- 78 | -- Filters can also be passed as arguments to other Filter constructors. This is 79 | -- a powerful way to create complex, custom Filters that select a very specific 80 | -- set of Entities. 81 | -- 82 | -- -- Selects Entities with an "image" Component, but not Entities with a 83 | -- -- "Player" or "Enemy" Component. 84 | -- filter = tiny.requireAll("image", tiny.rejectAny("Player", "Enemy")) 85 | -- 86 | -- @section Filter 87 | 88 | -- A helper function to compile filters. 89 | local filterJoin 90 | 91 | -- A helper function to filters from string 92 | local filterBuildString 93 | 94 | do 95 | 96 | local loadstring = loadstring or load 97 | local function getchr(c) 98 | return "\\" .. c:byte() 99 | end 100 | local function make_safe(text) 101 | return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) 102 | end 103 | 104 | local function filterJoinRaw(prefix, seperator, ...) 105 | local accum = {} 106 | local build = {} 107 | for i = 1, select('#', ...) do 108 | local item = select(i, ...) 109 | if type(item) == 'string' then 110 | accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item)) 111 | elseif type(item) == 'function' then 112 | build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)') 113 | :format(i, i) 114 | accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i) 115 | else 116 | error 'Filter token must be a string or a filter function.' 117 | end 118 | end 119 | local source = ('%s\nreturn function(system, e) return %s(%s) end') 120 | :format( 121 | table.concat(build, '\n'), 122 | prefix, 123 | table.concat(accum, seperator)) 124 | local loader, err = loadstring(source) 125 | if err then error(err) end 126 | return loader(...) 127 | end 128 | 129 | function filterJoin(...) 130 | local state, value = pcall(filterJoinRaw, ...) 131 | if state then return value else return nil, value end 132 | end 133 | 134 | local function buildPart(str) 135 | local accum = {} 136 | local subParts = {} 137 | str = str:gsub('%b()', function(p) 138 | subParts[#subParts + 1] = buildPart(p:sub(2, -2)) 139 | return ('\255%d'):format(#subParts) 140 | end) 141 | for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do 142 | if part:match('^\255%d+$') then 143 | local partIndex = tonumber(part:match(part:sub(2))) 144 | accum[#accum + 1] = ('%s(%s)') 145 | :format(invert == '' and '' or 'not', subParts[partIndex]) 146 | else 147 | accum[#accum + 1] = ("(e[%s] %s nil)") 148 | :format(make_safe(part), invert == '' and '~=' or '==') 149 | end 150 | if sep ~= '' then 151 | accum[#accum + 1] = (sep == '|' and ' or ' or ' and ') 152 | end 153 | end 154 | return table.concat(accum) 155 | end 156 | 157 | function filterBuildString(str) 158 | local source = ("return function(_, e) return %s end") 159 | :format(buildPart(str)) 160 | local loader, err = loadstring(source) 161 | if err then 162 | error(err) 163 | end 164 | return loader() 165 | end 166 | 167 | end 168 | 169 | --- Makes a Filter that selects Entities with all specified Components and 170 | -- Filters. 171 | function tiny.requireAll(...) 172 | return filterJoin('', ' and ', ...) 173 | end 174 | 175 | --- Makes a Filter that selects Entities with at least one of the specified 176 | -- Components and Filters. 177 | function tiny.requireAny(...) 178 | return filterJoin('', ' or ', ...) 179 | end 180 | 181 | --- Makes a Filter that rejects Entities with all specified Components and 182 | -- Filters, and selects all other Entities. 183 | function tiny.rejectAll(...) 184 | return filterJoin('not', ' and ', ...) 185 | end 186 | 187 | --- Makes a Filter that rejects Entities with at least one of the specified 188 | -- Components and Filters, and selects all other Entities. 189 | function tiny.rejectAny(...) 190 | return filterJoin('not', ' or ', ...) 191 | end 192 | 193 | --- Makes a Filter from a string. Syntax of `pattern` is as follows. 194 | -- 195 | -- * Tokens are alphanumeric strings including underscores. 196 | -- * Tokens can be separated by |, &, or surrounded by parentheses. 197 | -- * Tokens can be prefixed with !, and are then inverted. 198 | -- 199 | -- Examples are best: 200 | -- 'a|b|c' - Matches entities with an 'a' OR 'b' OR 'c'. 201 | -- 'a&!b&c' - Matches entities with an 'a' AND NOT 'b' AND 'c'. 202 | -- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e' 203 | -- @param pattern 204 | function tiny.filter(pattern) 205 | local state, value = pcall(filterBuildString, pattern) 206 | if state then return value else return nil, value end 207 | end 208 | 209 | --- System functions. 210 | -- A System is a wrapper around function callbacks for manipulating Entities. 211 | -- Systems are implemented as tables that contain at least one method; 212 | -- an update function that takes parameters like so: 213 | -- 214 | -- * `function system:update(dt)`. 215 | -- 216 | -- There are also a few other optional callbacks: 217 | -- 218 | -- * `function system:filter(entity)` - Returns true if this System should 219 | -- include this Entity, otherwise should return false. If this isn't specified, 220 | -- no Entities are included in the System. 221 | -- * `function system:onAdd(entity)` - Called when an Entity is added to the 222 | -- System. 223 | -- * `function system:onRemove(entity)` - Called when an Entity is removed 224 | -- from the System. 225 | -- * `function system:onModify(dt)` - Called when the System is modified by 226 | -- adding or removing Entities from the System. 227 | -- * `function system:onAddToWorld(world)` - Called when the System is added 228 | -- to the World, before any entities are added to the system. 229 | -- * `function system:onRemoveFromWorld(world)` - Called when the System is 230 | -- removed from the world, after all Entities are removed from the System. 231 | -- * `function system:preWrap(dt)` - Called on each system before update is 232 | -- called on any system. 233 | -- * `function system:postWrap(dt)` - Called on each system in reverse order 234 | -- after update is called on each system. The idea behind `preWrap` and 235 | -- `postWrap` is to allow for systems that modify the behavior of other systems. 236 | -- Say there is a DrawingSystem, which draws sprites to the screen, and a 237 | -- PostProcessingSystem, that adds some blur and bloom effects. In the preWrap 238 | -- method of the PostProcessingSystem, the System could set the drawing target 239 | -- for the DrawingSystem to a special buffer instead the screen. In the postWrap 240 | -- method, the PostProcessingSystem could then modify the buffer and render it 241 | -- to the screen. In this setup, the PostProcessingSystem would be added to the 242 | -- World after the drawingSystem (A similar but less flexible behavior could 243 | -- be accomplished with a single custom update function in the DrawingSystem). 244 | -- 245 | -- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`, 246 | -- but one can write their own filters as well. Set the Filter of a System like 247 | -- so: 248 | -- system.filter = tiny.requireAll("a", "b", "c") 249 | -- or 250 | -- function system:filter(entity) 251 | -- return entity.myRequiredComponentName ~= nil 252 | -- end 253 | -- 254 | -- All Systems also have a few important fields that are initialized when the 255 | -- system is added to the World. A few are important, and few should be less 256 | -- commonly used. 257 | -- 258 | -- * The `world` field points to the World that the System belongs to. Useful 259 | -- for adding and removing Entities from the world dynamically via the System. 260 | -- * The `active` flag is whether or not the System is updated automatically. 261 | -- Inactive Systems should be updated manually or not at all via 262 | -- `system:update(dt)`. Defaults to true. 263 | -- * The `entities` field is an ordered list of Entities in the System. This 264 | -- list can be used to quickly iterate through all Entities in a System. 265 | -- * The `interval` field is an optional field that makes Systems update at 266 | -- certain intervals using buffered time, regardless of World update frequency. 267 | -- For example, to make a System update once a second, set the System's interval 268 | -- to 1. 269 | -- * The `index` field is the System's index in the World. Lower indexed 270 | -- Systems are processed before higher indices. The `index` is a read only 271 | -- field; to set the `index`, use `tiny.setSystemIndex(world, system)`. 272 | -- * The `indices` field is a table of Entity keys to their indices in the 273 | -- `entities` list. Most Systems can ignore this. 274 | -- * The `modified` flag is an indicator if the System has been modified in 275 | -- the last update. If so, the `onModify` callback will be called on the System 276 | -- in the next update, if it has one. This is usually managed by tiny-ecs, so 277 | -- users should mostly ignore this, too. 278 | -- 279 | -- There is another option to (hopefully) increase performance in systems that 280 | -- have items added to or removed from them often, and have lots of entities in 281 | -- them. Setting the `nocache` field of the system might improve performance. 282 | -- It is still experimental. There are some restriction to systems without 283 | -- caching, however. 284 | -- 285 | -- * There is no `entities` table. 286 | -- * Callbacks such onAdd, onRemove, and onModify will never be called 287 | -- * Noncached systems cannot be sorted (There is no entities list to sort). 288 | -- 289 | -- @section System 290 | 291 | -- Use an empty table as a key for identifying Systems. Any table that contains 292 | -- this key is considered a System rather than an Entity. 293 | local systemTableKey = { "SYSTEM_TABLE_KEY" } 294 | 295 | -- Checks if a table is a System. 296 | local function isSystem(table) 297 | return table[systemTableKey] 298 | end 299 | 300 | -- Update function for all Processing Systems. 301 | local function processingSystemUpdate(system, dt) 302 | local preProcess = system.preProcess 303 | local process = system.process 304 | local postProcess = system.postProcess 305 | 306 | if preProcess then 307 | preProcess(system, dt) 308 | end 309 | 310 | if process then 311 | if system.nocache then 312 | local entities = system.world.entities 313 | local filter = system.filter 314 | if filter then 315 | for i = 1, #entities do 316 | local entity = entities[i] 317 | if filter(system, entity) then 318 | process(system, entity, dt) 319 | end 320 | end 321 | end 322 | else 323 | local entities = system.entities 324 | for i = 1, #entities do 325 | process(system, entities[i], dt) 326 | end 327 | end 328 | end 329 | 330 | if postProcess then 331 | postProcess(system, dt) 332 | end 333 | end 334 | 335 | -- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify. 336 | local function sortedSystemOnModify(system) 337 | local entities = system.entities 338 | local indices = system.indices 339 | local sortDelegate = system.sortDelegate 340 | if not sortDelegate then 341 | local compare = system.compare 342 | sortDelegate = function(e1, e2) 343 | return compare(system, e1, e2) 344 | end 345 | system.sortDelegate = sortDelegate 346 | end 347 | tsort(entities, sortDelegate) 348 | for i = 1, #entities do 349 | indices[entities[i]] = i 350 | end 351 | end 352 | 353 | --- Creates a new System or System class from the supplied table. If `table` is 354 | -- nil, creates a new table. 355 | function tiny.system(table) 356 | table = table or {} 357 | table[systemTableKey] = true 358 | return table 359 | end 360 | 361 | --- Creates a new Processing System or Processing System class. Processing 362 | -- Systems process each entity individual, and are usually what is needed. 363 | -- Processing Systems have three extra callbacks besides those inheritted from 364 | -- vanilla Systems. 365 | -- 366 | -- function system:preProcess(dt) -- Called before iteration. 367 | -- function system:process(entity, dt) -- Process each entity. 368 | -- function system:postProcess(dt) -- Called after iteration. 369 | -- 370 | -- Processing Systems have their own `update` method, so don't implement a 371 | -- a custom `update` callback for Processing Systems. 372 | -- @see system 373 | function tiny.processingSystem(table) 374 | table = table or {} 375 | table[systemTableKey] = true 376 | table.update = processingSystemUpdate 377 | return table 378 | end 379 | 380 | --- Creates a new Sorted System or Sorted System class. Sorted Systems sort 381 | -- their Entities according to a user-defined method, `system:compare(e1, e2)`, 382 | -- which should return true if `e1` should come before `e2` and false otherwise. 383 | -- Sorted Systems also override the default System's `onModify` callback, so be 384 | -- careful if defining a custom callback. However, for processing the sorted 385 | -- entities, consider `tiny.sortedProcessingSystem(table)`. 386 | -- @see system 387 | function tiny.sortedSystem(table) 388 | table = table or {} 389 | table[systemTableKey] = true 390 | table.onModify = sortedSystemOnModify 391 | return table 392 | end 393 | 394 | --- Creates a new Sorted Processing System or Sorted Processing System class. 395 | -- Sorted Processing Systems have both the aspects of Processing Systems and 396 | -- Sorted Systems. 397 | -- @see system 398 | -- @see processingSystem 399 | -- @see sortedSystem 400 | function tiny.sortedProcessingSystem(table) 401 | table = table or {} 402 | table[systemTableKey] = true 403 | table.update = processingSystemUpdate 404 | table.onModify = sortedSystemOnModify 405 | return table 406 | end 407 | 408 | --- World functions. 409 | -- A World is a container that manages Entities and Systems. Typically, a 410 | -- program uses one World at a time. 411 | -- 412 | -- For all World functions except `tiny.world(...)`, object-oriented syntax can 413 | -- be used instead of the documented syntax. For example, 414 | -- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3)`. 415 | -- @section World 416 | 417 | -- Forward declaration 418 | local worldMetaTable 419 | 420 | --- Creates a new World. 421 | -- Can optionally add default Systems and Entities. Returns the new World along 422 | -- with default Entities and Systems. 423 | function tiny.world(...) 424 | local ret = setmetatable({ 425 | 426 | -- List of Entities to remove 427 | entitiesToRemove = {}, 428 | 429 | -- List of Entities to change 430 | entitiesToChange = {}, 431 | 432 | -- List of Entities to add 433 | systemsToAdd = {}, 434 | 435 | -- List of Entities to remove 436 | systemsToRemove = {}, 437 | 438 | -- Set of Entities 439 | entities = {}, 440 | 441 | -- List of Systems 442 | systems = {} 443 | 444 | }, worldMetaTable) 445 | 446 | tiny_add(ret, ...) 447 | tiny_manageSystems(ret) 448 | tiny_manageEntities(ret) 449 | 450 | return ret, ... 451 | end 452 | 453 | --- Adds an Entity to the world. 454 | -- Also call this on Entities that have changed Components such that they 455 | -- match different Filters. Returns the Entity. 456 | function tiny.addEntity(world, entity) 457 | local e2c = world.entitiesToChange 458 | e2c[#e2c + 1] = entity 459 | return entity 460 | end 461 | tiny_addEntity = tiny.addEntity 462 | 463 | --- Adds a System to the world. Returns the System. 464 | function tiny.addSystem(world, system) 465 | assert(system.world == nil, "System already belongs to a World.") 466 | local s2a = world.systemsToAdd 467 | s2a[#s2a + 1] = system 468 | system.world = world 469 | return system 470 | end 471 | tiny_addSystem = tiny.addSystem 472 | 473 | --- Shortcut for adding multiple Entities and Systems to the World. Returns all 474 | -- added Entities and Systems. 475 | function tiny.add(world, ...) 476 | for i = 1, select("#", ...) do 477 | local obj = select(i, ...) 478 | if obj then 479 | if isSystem(obj) then 480 | tiny_addSystem(world, obj) 481 | else -- Assume obj is an Entity 482 | tiny_addEntity(world, obj) 483 | end 484 | end 485 | end 486 | return ... 487 | end 488 | tiny_add = tiny.add 489 | 490 | --- Removes an Entity from the World. Returns the Entity. 491 | function tiny.removeEntity(world, entity) 492 | local e2r = world.entitiesToRemove 493 | e2r[#e2r + 1] = entity 494 | return entity 495 | end 496 | tiny_removeEntity = tiny.removeEntity 497 | 498 | --- Removes a System from the world. Returns the System. 499 | function tiny.removeSystem(world, system) 500 | assert(system.world == world, "System does not belong to this World.") 501 | local s2r = world.systemsToRemove 502 | s2r[#s2r + 1] = system 503 | return system 504 | end 505 | tiny_removeSystem = tiny.removeSystem 506 | 507 | --- Shortcut for removing multiple Entities and Systems from the World. Returns 508 | -- all removed Systems and Entities 509 | function tiny.remove(world, ...) 510 | for i = 1, select("#", ...) do 511 | local obj = select(i, ...) 512 | if obj then 513 | if isSystem(obj) then 514 | tiny_removeSystem(world, obj) 515 | else -- Assume obj is an Entity 516 | tiny_removeEntity(world, obj) 517 | end 518 | end 519 | end 520 | return ... 521 | end 522 | 523 | -- Adds and removes Systems that have been marked from the World. 524 | function tiny_manageSystems(world) 525 | local s2a, s2r = world.systemsToAdd, world.systemsToRemove 526 | 527 | -- Early exit 528 | if #s2a == 0 and #s2r == 0 then 529 | return 530 | end 531 | 532 | world.systemsToAdd = {} 533 | world.systemsToRemove = {} 534 | 535 | local worldEntityList = world.entities 536 | local systems = world.systems 537 | 538 | -- Remove Systems 539 | for i = 1, #s2r do 540 | local system = s2r[i] 541 | local index = system.index 542 | local onRemove = system.onRemove 543 | if onRemove and not system.nocache then 544 | local entityList = system.entities 545 | for j = 1, #entityList do 546 | onRemove(system, entityList[j]) 547 | end 548 | end 549 | tremove(systems, index) 550 | for j = index, #systems do 551 | systems[j].index = j 552 | end 553 | local onRemoveFromWorld = system.onRemoveFromWorld 554 | if onRemoveFromWorld then 555 | onRemoveFromWorld(system, world) 556 | end 557 | s2r[i] = nil 558 | 559 | -- Clean up System 560 | system.world = nil 561 | system.entities = nil 562 | system.indices = nil 563 | system.index = nil 564 | end 565 | 566 | -- Add Systems 567 | for i = 1, #s2a do 568 | local system = s2a[i] 569 | if systems[system.index or 0] ~= system then 570 | if not system.nocache then 571 | system.entities = {} 572 | system.indices = {} 573 | end 574 | if system.active == nil then 575 | system.active = true 576 | end 577 | system.modified = true 578 | system.world = world 579 | local index = #systems + 1 580 | system.index = index 581 | systems[index] = system 582 | local onAddToWorld = system.onAddToWorld 583 | if onAddToWorld then 584 | onAddToWorld(system, world) 585 | end 586 | 587 | -- Try to add Entities 588 | if not system.nocache then 589 | local entityList = system.entities 590 | local entityIndices = system.indices 591 | local onAdd = system.onAdd 592 | local filter = system.filter 593 | if filter then 594 | for j = 1, #worldEntityList do 595 | local entity = worldEntityList[j] 596 | if filter(system, entity) then 597 | local entityIndex = #entityList + 1 598 | entityList[entityIndex] = entity 599 | entityIndices[entity] = entityIndex 600 | if onAdd then 601 | onAdd(system, entity) 602 | end 603 | end 604 | end 605 | end 606 | end 607 | end 608 | s2a[i] = nil 609 | end 610 | end 611 | 612 | -- Adds, removes, and changes Entities that have been marked. 613 | function tiny_manageEntities(world) 614 | 615 | local e2r = world.entitiesToRemove 616 | local e2c = world.entitiesToChange 617 | 618 | -- Early exit 619 | if #e2r == 0 and #e2c == 0 then 620 | return 621 | end 622 | 623 | world.entitiesToChange = {} 624 | world.entitiesToRemove = {} 625 | 626 | local entities = world.entities 627 | local systems = world.systems 628 | 629 | -- Change Entities 630 | for i = 1, #e2c do 631 | local entity = e2c[i] 632 | -- Add if needed 633 | if not entities[entity] then 634 | local index = #entities + 1 635 | entities[entity] = index 636 | entities[index] = entity 637 | end 638 | for j = 1, #systems do 639 | local system = systems[j] 640 | if not system.nocache then 641 | local ses = system.entities 642 | local seis = system.indices 643 | local index = seis[entity] 644 | local filter = system.filter 645 | if filter and filter(system, entity) then 646 | if not index then 647 | system.modified = true 648 | index = #ses + 1 649 | ses[index] = entity 650 | seis[entity] = index 651 | local onAdd = system.onAdd 652 | if onAdd then 653 | onAdd(system, entity) 654 | end 655 | end 656 | elseif index then 657 | system.modified = true 658 | local tmpEntity = ses[#ses] 659 | ses[index] = tmpEntity 660 | seis[tmpEntity] = index 661 | seis[entity] = nil 662 | ses[#ses] = nil 663 | local onRemove = system.onRemove 664 | if onRemove then 665 | onRemove(system, entity) 666 | end 667 | end 668 | end 669 | end 670 | e2c[i] = nil 671 | end 672 | 673 | -- Remove Entities 674 | for i = 1, #e2r do 675 | local entity = e2r[i] 676 | e2r[i] = nil 677 | local listIndex = entities[entity] 678 | if listIndex then 679 | -- Remove Entity from world state 680 | local lastEntity = entities[#entities] 681 | entities[lastEntity] = listIndex 682 | entities[entity] = nil 683 | entities[listIndex] = lastEntity 684 | entities[#entities] = nil 685 | -- Remove from cached systems 686 | for j = 1, #systems do 687 | local system = systems[j] 688 | if not system.nocache then 689 | local ses = system.entities 690 | local seis = system.indices 691 | local index = seis[entity] 692 | if index then 693 | system.modified = true 694 | local tmpEntity = ses[#ses] 695 | ses[index] = tmpEntity 696 | seis[tmpEntity] = index 697 | seis[entity] = nil 698 | ses[#ses] = nil 699 | local onRemove = system.onRemove 700 | if onRemove then 701 | onRemove(system, entity) 702 | end 703 | end 704 | end 705 | end 706 | end 707 | end 708 | end 709 | 710 | --- Manages Entities and Systems marked for deletion or addition. Call this 711 | -- before modifying Systems and Entities outside of a call to `tiny.update`. 712 | -- Do not call this within a call to `tiny.update`. 713 | function tiny.refresh(world) 714 | tiny_manageSystems(world) 715 | tiny_manageEntities(world) 716 | local systems = world.systems 717 | for i = #systems, 1, -1 do 718 | local system = systems[i] 719 | if system.active then 720 | local onModify = system.onModify 721 | if onModify and system.modified then 722 | onModify(system, 0) 723 | end 724 | system.modified = false 725 | end 726 | end 727 | end 728 | 729 | --- Updates the World by dt (delta time). Takes an optional parameter, `filter`, 730 | -- which is a Filter that selects Systems from the World, and updates only those 731 | -- Systems. If `filter` is not supplied, all Systems are updated. Put this 732 | -- function in your main loop. 733 | function tiny.update(world, dt, filter) 734 | 735 | tiny_manageSystems(world) 736 | tiny_manageEntities(world) 737 | 738 | local systems = world.systems 739 | 740 | -- Iterate through Systems IN REVERSE ORDER 741 | for i = #systems, 1, -1 do 742 | local system = systems[i] 743 | if system.active then 744 | -- Call the modify callback on Systems that have been modified. 745 | local onModify = system.onModify 746 | if onModify and system.modified then 747 | onModify(system, dt) 748 | end 749 | local preWrap = system.preWrap 750 | if preWrap and 751 | ((not filter) or filter(world, system)) then 752 | preWrap(system, dt) 753 | end 754 | end 755 | end 756 | 757 | -- Iterate through Systems IN ORDER 758 | for i = 1, #systems do 759 | local system = systems[i] 760 | if system.active and ((not filter) or filter(world, system)) then 761 | 762 | -- Update Systems that have an update method (most Systems) 763 | local update = system.update 764 | if update then 765 | local interval = system.interval 766 | if interval then 767 | local bufferedTime = (system.bufferedTime or 0) + dt 768 | while bufferedTime >= interval do 769 | bufferedTime = bufferedTime - interval 770 | update(system, interval) 771 | end 772 | system.bufferedTime = bufferedTime 773 | else 774 | update(system, dt) 775 | end 776 | end 777 | 778 | system.modified = false 779 | end 780 | end 781 | 782 | -- Iterate through Systems IN ORDER AGAIN 783 | for i = 1, #systems do 784 | local system = systems[i] 785 | local postWrap = system.postWrap 786 | if postWrap and system.active and 787 | ((not filter) or filter(world, system)) then 788 | postWrap(system, dt) 789 | end 790 | end 791 | 792 | end 793 | 794 | --- Removes all Entities from the World. 795 | function tiny.clearEntities(world) 796 | local el = world.entities 797 | for i = 1, #el do 798 | tiny_removeEntity(world, el[i]) 799 | end 800 | end 801 | 802 | --- Removes all Systems from the World. 803 | function tiny.clearSystems(world) 804 | local systems = world.systems 805 | for i = #systems, 1, -1 do 806 | tiny_removeSystem(world, systems[i]) 807 | end 808 | end 809 | 810 | --- Gets number of Entities in the World. 811 | function tiny.getEntityCount(world) 812 | return #world.entities 813 | end 814 | 815 | --- Gets number of Systems in World. 816 | function tiny.getSystemCount(world) 817 | return #world.systems 818 | end 819 | 820 | --- Sets the index of a System in the World, and returns the old index. Changes 821 | -- the order in which they Systems processed, because lower indexed Systems are 822 | -- processed first. Returns the old system.index. 823 | function tiny.setSystemIndex(world, system, index) 824 | tiny_manageSystems(world) 825 | local oldIndex = system.index 826 | local systems = world.systems 827 | 828 | if index < 0 then 829 | index = tiny.getSystemCount(world) + 1 + index 830 | end 831 | 832 | tremove(systems, oldIndex) 833 | tinsert(systems, index, system) 834 | 835 | for i = oldIndex, index, index >= oldIndex and 1 or -1 do 836 | systems[i].index = i 837 | end 838 | 839 | return oldIndex 840 | end 841 | 842 | -- Construct world metatable. 843 | worldMetaTable = { 844 | __index = { 845 | add = tiny.add, 846 | addEntity = tiny.addEntity, 847 | addSystem = tiny.addSystem, 848 | remove = tiny.remove, 849 | removeEntity = tiny.removeEntity, 850 | removeSystem = tiny.removeSystem, 851 | refresh = tiny.refresh, 852 | update = tiny.update, 853 | clearEntities = tiny.clearEntities, 854 | clearSystems = tiny.clearSystems, 855 | getEntityCount = tiny.getEntityCount, 856 | getSystemCount = tiny.getSystemCount, 857 | setSystemIndex = tiny.setSystemIndex 858 | }, 859 | __tostring = function() 860 | return "" 861 | end 862 | } 863 | 864 | return tiny 865 | --------------------------------------------------------------------------------