from scratch again

This commit is contained in:
BlackMATov
2024-12-12 11:41:40 +07:00
parent 0dd1b71e69
commit a872c64bd6
22 changed files with 7 additions and 4838 deletions

View File

@@ -6,11 +6,5 @@
"runtime": {
"version": "LuaJIT",
"pathStrict": true
},
"workspace": {
"ignoreDir": [
".vscode",
"develop/3rdparty"
]
}
}

143
README.md
View File

@@ -1,146 +1,3 @@
# evolved.lua
## Module `defers`
```
defers.defer -> (defer)
defers.set -> defer -> entity -> entity -> component -> (defer)
defers.assign -> defer -> entity -> entity -> component -> (defer)
defers.insert -> defer -> entity -> entity -> component -> (defer)
defers.remove -> defer -> entity -> entity... -> (defer)
defers.detach -> defer -> entity -> (defer)
defers.destroy -> defer -> entity -> (defer)
defers.playback -> defer -> (defer)
```
### Instance `defer`
```
defer:set -> entity -> entity -> component -> (defer)
defer:assign -> entity -> entity -> component -> (defer)
defer:insert -> entity -> entity -> component -> (defer)
defer:remove -> entity -> entity... -> (defer)
defer:detach -> entity -> (defer)
defer:destroy -> entity -> (defer)
defer:playback -> (defer)
```
## Module `idpools`
```
idpools.idpool -> (idpool)
idpools.pack -> integer -> integer -> (id)
idpools.unpack -> id -> (integer, integer)
idpools.alive -> idpool -> id -> (boolean)
idpools.acquire -> idpool -> (id)
idpools.release -> idpool -> id -> ()
```
### Instance `idpool`
```
idpool.pack -> integer -> integer -> (id)
idpool.unpack -> id -> (integer, integer)
idpool:alive -> id -> (boolean)
idpool:acquire -> (id)
idpool:release -> id -> ()
```
## Module `registry`
```
registry.entity -> (entity)
registry.guid -> entity -> (id)
registry.alive -> entity -> (boolean)
registry.get -> entity -> entity... -> (component...)
registry.has -> entity -> entity -> (boolean)
registry.has_all -> entity -> entity... -> (boolean)
registry.has_any -> entity -> entity... -> (boolean)
registry.set -> entity -> entity -> component -> (entity)
registry.chunk_set -> chunk -> entity -> component -> (integer, integer)
registry.query_set -> query -> entity -> component -> (integer, integer)
registry.assign -> entity -> entity -> component -> (boolean)
registry.chunk_assign -> chunk -> entity -> component -> (integer)
registry.query_assign -> query -> entity -> component -> (integer)
registry.insert -> entity -> entity -> component -> (boolean)
registry.chunk_insert -> chunk -> entity -> component -> (integer)
registry.query_insert -> query -> entity -> component -> (integer)
registry.remove -> entity -> entity... -> (boolean)
registry.chunk_remove -> chunk -> entity... -> (integer)
registry.query_remove -> query -> entity... -> (integer)
registry.detach -> entity -> (entity)
registry.chunk_detach -> chunk -> (integer)
registry.query_detach -> query -> (integer)
registry.destroy -> entity -> (entity)
registry.chunk_destroy -> chunk -> (integer)
registry.query_destroy -> query -> (integer)
registry.query -> entity... -> (query)
registry.query_include -> query -> entity... -> query
registry.query_exclude -> query -> entity... -> query
registry.query_execute -> query -> ({execution_state? -> chunk?}, execution_state?)
registry.chunk -> entity -> entity... -> (chunk)
registry.chunk_entities -> chunk -> (entity[])
registry.chunk_components -> chunk -> entity... -> (component[]...)
```
### Instance `entity`
```
entity:guid -> (id)
entity:alive -> (boolean)
entity:get -> entity... -> (component...)
entity:has -> entity -> (boolean)
entity:has_all -> entity... -> (boolean)
entity:has_any -> entity... -> (boolean)
entity:set -> entity -> component -> (entity)
entity:assign -> entity -> component -> (boolean)
entity:insert -> entity -> component -> (boolean)
entity:remove -> entity... -> (boolean)
entity:detach -> (entity)
entity:destroy -> (entity)
```
### Instance `query`
```
query:set -> entity -> component -> (integer, integer)
query:assign -> entity -> component -> (integer)
query:insert -> entity -> component -> (integer)
query:remove -> entity... -> (integer)
query:detach -> (integer)
query:destroy -> (integer)
query:include -> entity... -> query
query:exclude -> entity... -> query
query:execute -> ({execution_state? -> chunk?}, execution_state?)
```
### Instance `chunk`
```
chunk:set -> entity -> component -> (integer, integer)
chunk:assign -> entity -> component -> (integer)
chunk:insert -> entity -> component -> (integer)
chunk:remove -> entity... -> (integer)
chunk:detach -> (integer)
chunk:destroy -> (integer)
chunk:entities -> (entity[])
chunk:components -> entity... -> (component[]...)
```
## Module `singles`
```
singles.single -> component -> (entity)
singles.get -> entity -> (component)
singles.has -> entity -> (boolean)
singles.set -> entity -> component -> (entity)
```
## Module `vectors`
```
vectors.vector2 -> number -> number -> (vector2)
vectors.is_vector2 -> any -> (boolean)
```
## [License (MIT)](./LICENSE.md)

View File

@@ -1,27 +1,3 @@
# Roadmap
## Backlog
- [x] add additional oop-like api
- [x] add blocklist of fragments for queries
- [x] add checks of prohibited changes while querying
- [x] add check inserts and removes after destroying entity
- [x] add multi remove fragments function from entities
- [ ] cache matched chunks in queries
- [x] cache transitions between chunks
- [x] chunk's children should be stored in an array instead of a table
- [x] support queries without fragments
- [ ] add assertions for input arguments
- [x] registry.assign should not change chunks' tree
- [x] add deferred changes api
- [ ] add batch vector operations
- [ ] add inplace vector operations
- [x] cache chunk lists in batch operations
- [x] impl compat.move for 5.1 vanilla lua
- [x] add registry.batch_set
- [x] rename include/exclude/execute to query_include/exclude/execute
- [x] rename entities/components to chunk_entities/components
- [x] remove registry.apply to avoid encouraging incorrect patterns
- [ ] add on_set, on_assign, on_insert, on_remove callbacks
- [ ] add initial component fragment
- [ ] add constructor component fragment

View File

@@ -1,863 +0,0 @@
--[[
Copyright (c) 2016 Calvin Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
--- @module tiny-ecs
-- @author Calvin Rose
-- @license MIT
-- @copyright 2016
local tiny = {}
-- Local versions of standard lua functions
local tinsert = table.insert
local tremove = table.remove
local tsort = table.sort
local setmetatable = setmetatable
local type = type
local select = select
-- Local versions of the library functions
local tiny_manageEntities
local tiny_manageSystems
local tiny_addEntity
local tiny_addSystem
local tiny_add
local tiny_removeEntity
local tiny_removeSystem
--- Filter functions.
-- A Filter is a function that selects which Entities apply to a System.
-- Filters take two parameters, the System and the Entity, and return a boolean
-- value indicating if the Entity should be processed by the System. A truthy
-- value includes the entity, while a falsey (nil or false) value excludes the
-- entity.
--
-- Filters must be added to Systems by setting the `filter` field of the System.
-- Filter's returned by tiny-ecs's Filter functions are immutable and can be
-- used by multiple Systems.
--
-- local f1 = tiny.requireAll("position", "velocity", "size")
-- local f2 = tiny.requireAny("position", "velocity", "size")
--
-- local e1 = {
-- position = {2, 3},
-- velocity = {3, 3},
-- size = {4, 4}
-- }
--
-- local entity2 = {
-- position = {4, 5},
-- size = {4, 4}
-- }
--
-- local e3 = {
-- position = {2, 3},
-- velocity = {3, 3}
-- }
--
-- print(f1(nil, e1), f1(nil, e2), f1(nil, e3)) -- prints true, false, false
-- print(f2(nil, e1), f2(nil, e2), f2(nil, e3)) -- prints true, true, true
--
-- Filters can also be passed as arguments to other Filter constructors. This is
-- a powerful way to create complex, custom Filters that select a very specific
-- set of Entities.
--
-- -- Selects Entities with an "image" Component, but not Entities with a
-- -- "Player" or "Enemy" Component.
-- filter = tiny.requireAll("image", tiny.rejectAny("Player", "Enemy"))
--
-- @section Filter
-- A helper function to compile filters.
local filterJoin
-- A helper function to filters from string
local filterBuildString
do
local loadstring = loadstring or load
local function getchr(c)
return "\\" .. c:byte()
end
local function make_safe(text)
return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
end
local function filterJoinRaw(prefix, seperator, ...)
local accum = {}
local build = {}
for i = 1, select('#', ...) do
local item = select(i, ...)
if type(item) == 'string' then
accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item))
elseif type(item) == 'function' then
build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)')
:format(i, i)
accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i)
else
error 'Filter token must be a string or a filter function.'
end
end
local source = ('%s\nreturn function(system, e) return %s(%s) end')
:format(
table.concat(build, '\n'),
prefix,
table.concat(accum, seperator))
local loader, err = loadstring(source)
if err then error(err) end
return loader(...)
end
function filterJoin(...)
local state, value = pcall(filterJoinRaw, ...)
if state then return value else return nil, value end
end
local function buildPart(str)
local accum = {}
local subParts = {}
str = str:gsub('%b()', function(p)
subParts[#subParts + 1] = buildPart(p:sub(2, -2))
return ('\255%d'):format(#subParts)
end)
for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do
if part:match('^\255%d+$') then
local partIndex = tonumber(part:match(part:sub(2)))
accum[#accum + 1] = ('%s(%s)')
:format(invert == '' and '' or 'not', subParts[partIndex])
else
accum[#accum + 1] = ("(e[%s] %s nil)")
:format(make_safe(part), invert == '' and '~=' or '==')
end
if sep ~= '' then
accum[#accum + 1] = (sep == '|' and ' or ' or ' and ')
end
end
return table.concat(accum)
end
function filterBuildString(str)
local source = ("return function(_, e) return %s end")
:format(buildPart(str))
local loader, err = loadstring(source)
if err then
error(err)
end
return loader()
end
end
--- Makes a Filter that selects Entities with all specified Components and
-- Filters.
function tiny.requireAll(...)
return filterJoin('', ' and ', ...)
end
--- Makes a Filter that selects Entities with at least one of the specified
-- Components and Filters.
function tiny.requireAny(...)
return filterJoin('', ' or ', ...)
end
--- Makes a Filter that rejects Entities with all specified Components and
-- Filters, and selects all other Entities.
function tiny.rejectAll(...)
return filterJoin('not', ' and ', ...)
end
--- Makes a Filter that rejects Entities with at least one of the specified
-- Components and Filters, and selects all other Entities.
function tiny.rejectAny(...)
return filterJoin('not', ' or ', ...)
end
--- Makes a Filter from a string. Syntax of `pattern` is as follows.
--
-- * Tokens are alphanumeric strings including underscores.
-- * Tokens can be separated by |, &, or surrounded by parentheses.
-- * Tokens can be prefixed with !, and are then inverted.
--
-- Examples are best:
-- 'a|b|c' - Matches entities with an 'a' OR 'b' OR 'c'.
-- 'a&!b&c' - Matches entities with an 'a' AND NOT 'b' AND 'c'.
-- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e'
-- @param pattern
function tiny.filter(pattern)
local state, value = pcall(filterBuildString, pattern)
if state then return value else return nil, value end
end
--- System functions.
-- A System is a wrapper around function callbacks for manipulating Entities.
-- Systems are implemented as tables that contain at least one method;
-- an update function that takes parameters like so:
--
-- * `function system:update(dt)`.
--
-- There are also a few other optional callbacks:
--
-- * `function system:filter(entity)` - Returns true if this System should
-- include this Entity, otherwise should return false. If this isn't specified,
-- no Entities are included in the System.
-- * `function system:onAdd(entity)` - Called when an Entity is added to the
-- System.
-- * `function system:onRemove(entity)` - Called when an Entity is removed
-- from the System.
-- * `function system:onModify(dt)` - Called when the System is modified by
-- adding or removing Entities from the System.
-- * `function system:onAddToWorld(world)` - Called when the System is added
-- to the World, before any entities are added to the system.
-- * `function system:onRemoveFromWorld(world)` - Called when the System is
-- removed from the world, after all Entities are removed from the System.
-- * `function system:preWrap(dt)` - Called on each system before update is
-- called on any system.
-- * `function system:postWrap(dt)` - Called on each system in reverse order
-- after update is called on each system. The idea behind `preWrap` and
-- `postWrap` is to allow for systems that modify the behavior of other systems.
-- Say there is a DrawingSystem, which draws sprites to the screen, and a
-- PostProcessingSystem, that adds some blur and bloom effects. In the preWrap
-- method of the PostProcessingSystem, the System could set the drawing target
-- for the DrawingSystem to a special buffer instead the screen. In the postWrap
-- method, the PostProcessingSystem could then modify the buffer and render it
-- to the screen. In this setup, the PostProcessingSystem would be added to the
-- World after the drawingSystem (A similar but less flexible behavior could
-- be accomplished with a single custom update function in the DrawingSystem).
--
-- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`,
-- but one can write their own filters as well. Set the Filter of a System like
-- so:
-- system.filter = tiny.requireAll("a", "b", "c")
-- or
-- function system:filter(entity)
-- return entity.myRequiredComponentName ~= nil
-- end
--
-- All Systems also have a few important fields that are initialized when the
-- system is added to the World. A few are important, and few should be less
-- commonly used.
--
-- * The `world` field points to the World that the System belongs to. Useful
-- for adding and removing Entities from the world dynamically via the System.
-- * The `active` flag is whether or not the System is updated automatically.
-- Inactive Systems should be updated manually or not at all via
-- `system:update(dt)`. Defaults to true.
-- * The `entities` field is an ordered list of Entities in the System. This
-- list can be used to quickly iterate through all Entities in a System.
-- * The `interval` field is an optional field that makes Systems update at
-- certain intervals using buffered time, regardless of World update frequency.
-- For example, to make a System update once a second, set the System's interval
-- to 1.
-- * The `index` field is the System's index in the World. Lower indexed
-- Systems are processed before higher indices. The `index` is a read only
-- field; to set the `index`, use `tiny.setSystemIndex(world, system)`.
-- * The `indices` field is a table of Entity keys to their indices in the
-- `entities` list. Most Systems can ignore this.
-- * The `modified` flag is an indicator if the System has been modified in
-- the last update. If so, the `onModify` callback will be called on the System
-- in the next update, if it has one. This is usually managed by tiny-ecs, so
-- users should mostly ignore this, too.
--
-- There is another option to (hopefully) increase performance in systems that
-- have items added to or removed from them often, and have lots of entities in
-- them. Setting the `nocache` field of the system might improve performance.
-- It is still experimental. There are some restriction to systems without
-- caching, however.
--
-- * There is no `entities` table.
-- * Callbacks such onAdd, onRemove, and onModify will never be called
-- * Noncached systems cannot be sorted (There is no entities list to sort).
--
-- @section System
-- Use an empty table as a key for identifying Systems. Any table that contains
-- this key is considered a System rather than an Entity.
local systemTableKey = { "SYSTEM_TABLE_KEY" }
-- Checks if a table is a System.
local function isSystem(table)
return table[systemTableKey]
end
-- Update function for all Processing Systems.
local function processingSystemUpdate(system, dt)
local preProcess = system.preProcess
local process = system.process
local postProcess = system.postProcess
if preProcess then
preProcess(system, dt)
end
if process then
if system.nocache then
local entities = system.world.entities
local filter = system.filter
if filter then
for i = 1, #entities do
local entity = entities[i]
if filter(system, entity) then
process(system, entity, dt)
end
end
end
else
local entities = system.entities
for i = 1, #entities do
process(system, entities[i], dt)
end
end
end
if postProcess then
postProcess(system, dt)
end
end
-- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify.
local function sortedSystemOnModify(system)
local entities = system.entities
local indices = system.indices
local sortDelegate = system.sortDelegate
if not sortDelegate then
local compare = system.compare
sortDelegate = function(e1, e2)
return compare(system, e1, e2)
end
system.sortDelegate = sortDelegate
end
tsort(entities, sortDelegate)
for i = 1, #entities do
indices[entities[i]] = i
end
end
--- Creates a new System or System class from the supplied table. If `table` is
-- nil, creates a new table.
function tiny.system(table)
table = table or {}
table[systemTableKey] = true
return table
end
--- Creates a new Processing System or Processing System class. Processing
-- Systems process each entity individual, and are usually what is needed.
-- Processing Systems have three extra callbacks besides those inheritted from
-- vanilla Systems.
--
-- function system:preProcess(dt) -- Called before iteration.
-- function system:process(entity, dt) -- Process each entity.
-- function system:postProcess(dt) -- Called after iteration.
--
-- Processing Systems have their own `update` method, so don't implement a
-- a custom `update` callback for Processing Systems.
-- @see system
function tiny.processingSystem(table)
table = table or {}
table[systemTableKey] = true
table.update = processingSystemUpdate
return table
end
--- Creates a new Sorted System or Sorted System class. Sorted Systems sort
-- their Entities according to a user-defined method, `system:compare(e1, e2)`,
-- which should return true if `e1` should come before `e2` and false otherwise.
-- Sorted Systems also override the default System's `onModify` callback, so be
-- careful if defining a custom callback. However, for processing the sorted
-- entities, consider `tiny.sortedProcessingSystem(table)`.
-- @see system
function tiny.sortedSystem(table)
table = table or {}
table[systemTableKey] = true
table.onModify = sortedSystemOnModify
return table
end
--- Creates a new Sorted Processing System or Sorted Processing System class.
-- Sorted Processing Systems have both the aspects of Processing Systems and
-- Sorted Systems.
-- @see system
-- @see processingSystem
-- @see sortedSystem
function tiny.sortedProcessingSystem(table)
table = table or {}
table[systemTableKey] = true
table.update = processingSystemUpdate
table.onModify = sortedSystemOnModify
return table
end
--- World functions.
-- A World is a container that manages Entities and Systems. Typically, a
-- program uses one World at a time.
--
-- For all World functions except `tiny.world(...)`, object-oriented syntax can
-- be used instead of the documented syntax. For example,
-- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3)`.
-- @section World
-- Forward declaration
local worldMetaTable
--- Creates a new World.
-- Can optionally add default Systems and Entities. Returns the new World along
-- with default Entities and Systems.
function tiny.world(...)
local ret = setmetatable({
-- List of Entities to remove
entitiesToRemove = {},
-- List of Entities to change
entitiesToChange = {},
-- List of Entities to add
systemsToAdd = {},
-- List of Entities to remove
systemsToRemove = {},
-- Set of Entities
entities = {},
-- List of Systems
systems = {}
}, worldMetaTable)
tiny_add(ret, ...)
tiny_manageSystems(ret)
tiny_manageEntities(ret)
return ret, ...
end
--- Adds an Entity to the world.
-- Also call this on Entities that have changed Components such that they
-- match different Filters. Returns the Entity.
function tiny.addEntity(world, entity)
local e2c = world.entitiesToChange
e2c[#e2c + 1] = entity
return entity
end
tiny_addEntity = tiny.addEntity
--- Adds a System to the world. Returns the System.
function tiny.addSystem(world, system)
assert(system.world == nil, "System already belongs to a World.")
local s2a = world.systemsToAdd
s2a[#s2a + 1] = system
system.world = world
return system
end
tiny_addSystem = tiny.addSystem
--- Shortcut for adding multiple Entities and Systems to the World. Returns all
-- added Entities and Systems.
function tiny.add(world, ...)
for i = 1, select("#", ...) do
local obj = select(i, ...)
if obj then
if isSystem(obj) then
tiny_addSystem(world, obj)
else -- Assume obj is an Entity
tiny_addEntity(world, obj)
end
end
end
return ...
end
tiny_add = tiny.add
--- Removes an Entity from the World. Returns the Entity.
function tiny.removeEntity(world, entity)
local e2r = world.entitiesToRemove
e2r[#e2r + 1] = entity
return entity
end
tiny_removeEntity = tiny.removeEntity
--- Removes a System from the world. Returns the System.
function tiny.removeSystem(world, system)
assert(system.world == world, "System does not belong to this World.")
local s2r = world.systemsToRemove
s2r[#s2r + 1] = system
return system
end
tiny_removeSystem = tiny.removeSystem
--- Shortcut for removing multiple Entities and Systems from the World. Returns
-- all removed Systems and Entities
function tiny.remove(world, ...)
for i = 1, select("#", ...) do
local obj = select(i, ...)
if obj then
if isSystem(obj) then
tiny_removeSystem(world, obj)
else -- Assume obj is an Entity
tiny_removeEntity(world, obj)
end
end
end
return ...
end
-- Adds and removes Systems that have been marked from the World.
function tiny_manageSystems(world)
local s2a, s2r = world.systemsToAdd, world.systemsToRemove
-- Early exit
if #s2a == 0 and #s2r == 0 then
return
end
world.systemsToAdd = {}
world.systemsToRemove = {}
local worldEntityList = world.entities
local systems = world.systems
-- Remove Systems
for i = 1, #s2r do
local system = s2r[i]
local index = system.index
local onRemove = system.onRemove
if onRemove and not system.nocache then
local entityList = system.entities
for j = 1, #entityList do
onRemove(system, entityList[j])
end
end
tremove(systems, index)
for j = index, #systems do
systems[j].index = j
end
local onRemoveFromWorld = system.onRemoveFromWorld
if onRemoveFromWorld then
onRemoveFromWorld(system, world)
end
s2r[i] = nil
-- Clean up System
system.world = nil
system.entities = nil
system.indices = nil
system.index = nil
end
-- Add Systems
for i = 1, #s2a do
local system = s2a[i]
if systems[system.index or 0] ~= system then
if not system.nocache then
system.entities = {}
system.indices = {}
end
if system.active == nil then
system.active = true
end
system.modified = true
system.world = world
local index = #systems + 1
system.index = index
systems[index] = system
local onAddToWorld = system.onAddToWorld
if onAddToWorld then
onAddToWorld(system, world)
end
-- Try to add Entities
if not system.nocache then
local entityList = system.entities
local entityIndices = system.indices
local onAdd = system.onAdd
local filter = system.filter
if filter then
for j = 1, #worldEntityList do
local entity = worldEntityList[j]
if filter(system, entity) then
local entityIndex = #entityList + 1
entityList[entityIndex] = entity
entityIndices[entity] = entityIndex
if onAdd then
onAdd(system, entity)
end
end
end
end
end
end
s2a[i] = nil
end
end
-- Adds, removes, and changes Entities that have been marked.
function tiny_manageEntities(world)
local e2r = world.entitiesToRemove
local e2c = world.entitiesToChange
-- Early exit
if #e2r == 0 and #e2c == 0 then
return
end
world.entitiesToChange = {}
world.entitiesToRemove = {}
local entities = world.entities
local systems = world.systems
-- Change Entities
for i = 1, #e2c do
local entity = e2c[i]
-- Add if needed
if not entities[entity] then
local index = #entities + 1
entities[entity] = index
entities[index] = entity
end
for j = 1, #systems do
local system = systems[j]
if not system.nocache then
local ses = system.entities
local seis = system.indices
local index = seis[entity]
local filter = system.filter
if filter and filter(system, entity) then
if not index then
system.modified = true
index = #ses + 1
ses[index] = entity
seis[entity] = index
local onAdd = system.onAdd
if onAdd then
onAdd(system, entity)
end
end
elseif index then
system.modified = true
local tmpEntity = ses[#ses]
ses[index] = tmpEntity
seis[tmpEntity] = index
seis[entity] = nil
ses[#ses] = nil
local onRemove = system.onRemove
if onRemove then
onRemove(system, entity)
end
end
end
end
e2c[i] = nil
end
-- Remove Entities
for i = 1, #e2r do
local entity = e2r[i]
e2r[i] = nil
local listIndex = entities[entity]
if listIndex then
-- Remove Entity from world state
local lastEntity = entities[#entities]
entities[lastEntity] = listIndex
entities[entity] = nil
entities[listIndex] = lastEntity
entities[#entities] = nil
-- Remove from cached systems
for j = 1, #systems do
local system = systems[j]
if not system.nocache then
local ses = system.entities
local seis = system.indices
local index = seis[entity]
if index then
system.modified = true
local tmpEntity = ses[#ses]
ses[index] = tmpEntity
seis[tmpEntity] = index
seis[entity] = nil
ses[#ses] = nil
local onRemove = system.onRemove
if onRemove then
onRemove(system, entity)
end
end
end
end
end
end
end
--- Manages Entities and Systems marked for deletion or addition. Call this
-- before modifying Systems and Entities outside of a call to `tiny.update`.
-- Do not call this within a call to `tiny.update`.
function tiny.refresh(world)
tiny_manageSystems(world)
tiny_manageEntities(world)
local systems = world.systems
for i = #systems, 1, -1 do
local system = systems[i]
if system.active then
local onModify = system.onModify
if onModify and system.modified then
onModify(system, 0)
end
system.modified = false
end
end
end
--- Updates the World by dt (delta time). Takes an optional parameter, `filter`,
-- which is a Filter that selects Systems from the World, and updates only those
-- Systems. If `filter` is not supplied, all Systems are updated. Put this
-- function in your main loop.
function tiny.update(world, dt, filter)
tiny_manageSystems(world)
tiny_manageEntities(world)
local systems = world.systems
-- Iterate through Systems IN REVERSE ORDER
for i = #systems, 1, -1 do
local system = systems[i]
if system.active then
-- Call the modify callback on Systems that have been modified.
local onModify = system.onModify
if onModify and system.modified then
onModify(system, dt)
end
local preWrap = system.preWrap
if preWrap and
((not filter) or filter(world, system)) then
preWrap(system, dt)
end
end
end
-- Iterate through Systems IN ORDER
for i = 1, #systems do
local system = systems[i]
if system.active and ((not filter) or filter(world, system)) then
-- Update Systems that have an update method (most Systems)
local update = system.update
if update then
local interval = system.interval
if interval then
local bufferedTime = (system.bufferedTime or 0) + dt
while bufferedTime >= interval do
bufferedTime = bufferedTime - interval
update(system, interval)
end
system.bufferedTime = bufferedTime
else
update(system, dt)
end
end
system.modified = false
end
end
-- Iterate through Systems IN ORDER AGAIN
for i = 1, #systems do
local system = systems[i]
local postWrap = system.postWrap
if postWrap and system.active and
((not filter) or filter(world, system)) then
postWrap(system, dt)
end
end
end
--- Removes all Entities from the World.
function tiny.clearEntities(world)
local el = world.entities
for i = 1, #el do
tiny_removeEntity(world, el[i])
end
end
--- Removes all Systems from the World.
function tiny.clearSystems(world)
local systems = world.systems
for i = #systems, 1, -1 do
tiny_removeSystem(world, systems[i])
end
end
--- Gets number of Entities in the World.
function tiny.getEntityCount(world)
return #world.entities
end
--- Gets number of Systems in World.
function tiny.getSystemCount(world)
return #world.systems
end
--- Sets the index of a System in the World, and returns the old index. Changes
-- the order in which they Systems processed, because lower indexed Systems are
-- processed first. Returns the old system.index.
function tiny.setSystemIndex(world, system, index)
tiny_manageSystems(world)
local oldIndex = system.index
local systems = world.systems
if index < 0 then
index = tiny.getSystemCount(world) + 1 + index
end
tremove(systems, oldIndex)
tinsert(systems, index, system)
for i = oldIndex, index, index >= oldIndex and 1 or -1 do
systems[i].index = i
end
return oldIndex
end
-- Construct world metatable.
worldMetaTable = {
__index = {
add = tiny.add,
addEntity = tiny.addEntity,
addSystem = tiny.addSystem,
remove = tiny.remove,
removeEntity = tiny.removeEntity,
removeSystem = tiny.removeSystem,
refresh = tiny.refresh,
update = tiny.update,
clearEntities = tiny.clearEntities,
clearSystems = tiny.clearSystems,
getEntityCount = tiny.getEntityCount,
getSystemCount = tiny.getSystemCount,
setSystemIndex = tiny.setSystemIndex
},
__tostring = function()
return "<tiny-ecs_World>"
end
}
return tiny

View File

@@ -1,42 +1 @@
local evo = require 'evolved.evolved'
local v2 = evo.vectors.vector2
local singles = {
delta_time = evo.singles.single(0.016),
}
local fragments = {
disabled = evo.registry.entity(),
position = evo.registry.entity(),
velocity = evo.registry.entity(),
}
local queries = {
bodies = evo.registry.query(
fragments.position,
fragments.velocity)
:exclude(fragments.disabled)
}
do
evo.registry.entity()
:set(fragments.position, v2(512, 50))
:set(fragments.velocity, v2(math.random(-20, 20), 20))
end
do
local delta_time = evo.singles.get(singles.delta_time)
for chunk in queries.bodies:execute() do
local entities = chunk:entities()
local positions, velocities = chunk:components(
fragments.position,
fragments.velocity)
for i = 1, #entities do
positions[i] = positions[i] + velocities[i] * delta_time
end
end
end
local evo = require 'evolved'

View File

@@ -1,2 +1 @@
require 'develop.unbench.evolved_unbench'
require 'develop.unbench.tinyecs_unbench'
local evo = require 'evolved'

View File

@@ -1,41 +0,0 @@
local evo = require 'evolved.evolved'
local common = {}
---@param name string
---@param loop fun(...): ...
---@param init fun(): ...
function common.describe(name, loop, init)
collectgarbage('stop')
print(string.format('| %s ... |', name))
local state = evo.compat.pack(init())
local iters = 0
local start_s = os.clock()
local start_kb = collectgarbage('count')
local success, result = pcall(function()
repeat
iters = iters + 1
loop(evo.compat.unpack(state))
until os.clock() - start_s > 0.2
end)
local finish_s = os.clock()
local finish_kb = collectgarbage('count')
print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f',
success and 'OK' or 'FAILED',
(finish_s - start_s) * 1000 * 1000 / iters,
iters / (finish_s - start_s),
(finish_kb - start_kb) / iters))
if not success then print(' ' .. result) end
collectgarbage('restart')
collectgarbage('collect')
end
return common

View File

@@ -1,273 +0,0 @@
local common = require 'develop.unbench.common_unbench'
local evo = require 'evolved.evolved'
print '*******************'
print '***** evolved *****'
print '*******************'
---@param a evolved.entity
---@param b evolved.entity
---@param c evolved.entity
---@param d evolved.entity
---@param e evolved.entity
---@param A evolved.query
---@param B evolved.query
---@param C evolved.query
---@param D evolved.query
---@param E evolved.query
common.describe('Packed Iteration', function(a, b, c, d, e, A, B, C, D, E)
for chunk in A:execute() do
local as = chunk:components(a)
for i = 1, #chunk:entities() do as[i] = as[i] * 2 end
end
for chunk in B:execute() do
local bs = chunk:components(b)
for i = 1, #chunk:entities() do bs[i] = bs[i] * 2 end
end
for chunk in C:execute() do
local cs = chunk:components(c)
for i = 1, #chunk:entities() do cs[i] = cs[i] * 2 end
end
for chunk in D:execute() do
local ds = chunk:components(d)
for i = 1, #chunk:entities() do ds[i] = ds[i] * 2 end
end
for chunk in E:execute() do
local es = chunk:components(e)
for i = 1, #chunk:entities() do es[i] = es[i] * 2 end
end
end, function()
local A, B, C, D, E =
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 10000 do
evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(D, 0):set(E, 0)
end
return
A, B, C, D, E,
evo.registry.query(A),
evo.registry.query(B),
evo.registry.query(C),
evo.registry.query(D),
evo.registry.query(E)
end)
common.describe('Simple Iteration', function(a, b, c, d, e, AB, CD, CE)
for chunk in AB:execute() do
local as, bs = chunk:components(a, b)
for i = 1, #chunk:entities() do as[i], bs[i] = bs[i], as[i] end
end
for chunk in CD:execute() do
local cs, ds = chunk:components(c, d)
for i = 1, #chunk:entities() do cs[i], ds[i] = ds[i], cs[i] end
end
for chunk in CE:execute() do
local cs, es = chunk:components(c, e)
for i = 1, #chunk:entities() do cs[i], es[i] = es[i], cs[i] end
end
end, function()
local A, B, C, D, E =
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 10000 do
evo.registry.entity():set(A, 0):set(B, 0)
evo.registry.entity():set(A, 0):set(B, 0):set(C, 0)
evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(D, 0)
evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(E, 0)
end
return A, B, C, D, E,
evo.registry.query(A, B),
evo.registry.query(C, D),
evo.registry.query(C, E)
end)
---@param d evolved.entity
---@param z evolved.entity
---@param Data evolved.query
---@param Z evolved.query
common.describe('Fragmented Iteration', function(d, z, Data, Z)
for chunk in Data:execute() do
local ds = chunk:components(d)
for i = 1, #chunk:entities() do ds[i] = ds[i] * 2 end
end
for chunk in Z:execute() do
local zs = chunk:components(z)
for i = 1, #chunk:entities() do zs[i] = zs[i] * 2 end
end
end, function()
local chars, data = {}, evo.registry.entity()
for i = 1, 26 do chars[i] = evo.registry.entity() end
for i = 1, #chars do
for _ = 1, 10000 do
evo.registry.entity():set(chars[i], 0):set(data, 0)
end
end
return data, chars[#chars],
evo.registry.query(data),
evo.registry.query(chars[#chars])
end)
---@param a evolved.entity
---@param b evolved.entity
---@param A evolved.query
---@param B evolved.query
common.describe('Entity Cycle (Simple)', function(a, b, A, B)
---@type evolved.component[]
local to_create = {}
for chunk in A:execute() do
local as = chunk:components(a)
for i = 1, #chunk:entities() do
to_create[#to_create + 1] = as[i]
end
end
for i = 1, #to_create do
evo.registry.entity():set(b, to_create[i])
end
---@type evolved.entity[]
local to_destroy = {}
for chunk in B:execute() do
local es = chunk:entities()
for i = 1, #chunk:entities() do
to_destroy[#to_destroy + 1] = es[i]
end
end
for i = 1, #to_destroy do
evo.registry.destroy(to_destroy[i])
end
end, function()
local a, b =
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 1000 do
evo.registry.entity():set(a, 0)
end
return a, b,
evo.registry.query(a),
evo.registry.query(b)
end)
---@param a evolved.entity
---@param b evolved.entity
---@param A evolved.query
---@param B evolved.query
common.describe('Entity Cycle (Batched)', function(a, b, A, B)
---@type evolved.component[]
local to_create = {}
for chunk in A:execute() do
local as = chunk:components(a)
for i = 1, #chunk:entities() do
to_create[#to_create + 1] = as[i]
end
end
for i = 1, #to_create do
evo.registry.entity():set(b, to_create[i])
end
assert(1000 == evo.registry.query_destroy(B))
end, function()
local a, b =
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 1000 do
evo.registry.entity():set(a, 0)
end
return a, b,
evo.registry.query(a),
evo.registry.query(b)
end)
---@param b evolved.entity
---@param A evolved.query
---@param AB evolved.query
common.describe('Add / Remove (Simple)', function(b, A, AB)
---@type evolved.entity[]
local to_insert = {}
for chunk in A:execute() do
local es = chunk:entities()
for i = 1, #chunk:entities() do
to_insert[#to_insert + 1] = es[i]
end
end
for i = 1, #to_insert do
evo.registry.insert(to_insert[i], b)
end
---@type evolved.entity[]
local to_remove = {}
for chunk in AB:execute() do
local es = chunk:entities()
for i = 1, #chunk:entities() do
to_remove[#to_remove + 1] = es[i]
end
end
for i = 1, #to_remove do
evo.registry.remove(to_remove[i], b)
end
end, function()
local a, b =
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 10000 do
evo.registry.entity():set(a)
end
return b,
evo.registry.query(a),
evo.registry.query(b)
end)
---@param b evolved.entity
---@param A evolved.query
---@param AB evolved.query
common.describe('Add / Remove (Batched)', function(b, A, AB)
assert(10000 == evo.registry.query_insert(A, b))
assert(10000 == evo.registry.query_remove(AB, b))
end, function()
local a, b =
evo.registry.entity(),
evo.registry.entity()
for _ = 1, 10000 do
evo.registry.entity():set(a)
end
return b,
evo.registry.query(a),
evo.registry.query(b)
end)

View File

@@ -1,225 +0,0 @@
local common = require 'develop.unbench.common_unbench'
local tiny = require 'develop.3rdparty.tinyecs'
print '********************'
print '***** tiny-ecs *****'
print '********************'
common.describe('Packed Iteration', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 10000 do
tiny.addEntity(w, { a = 0, b = 0, c = 0, d = 0, e = 0 })
end
local A = tiny.processingSystem()
A.filter = tiny.requireAll('a')
A.process = function(_, e) e.a = e.a * 2 end
local B = tiny.processingSystem()
B.filter = tiny.requireAll('b')
B.process = function(_, e) e.b = e.b * 2 end
local C = tiny.processingSystem()
C.filter = tiny.requireAll('c')
C.process = function(_, e) e.c = e.c * 2 end
local D = tiny.processingSystem()
D.filter = tiny.requireAll('d')
D.process = function(_, e) e.d = e.d * 2 end
local E = tiny.processingSystem()
E.filter = tiny.requireAll('e')
E.process = function(_, e) e.e = e.e * 2 end
tiny.addSystem(w, A)
tiny.addSystem(w, B)
tiny.addSystem(w, C)
tiny.addSystem(w, D)
tiny.addSystem(w, E)
tiny.refresh(w)
return w
end)
common.describe('Simple Iteration', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 10000 do
tiny.addEntity(w, { a = 0, b = 0 })
tiny.addEntity(w, { a = 0, b = 0, c = 0 })
tiny.addEntity(w, { a = 0, b = 0, c = 0, d = 0 })
tiny.addEntity(w, { a = 0, b = 0, c = 0, e = 0 })
end
local AB = tiny.processingSystem()
AB.filter = tiny.requireAll('a', 'b')
AB.process = function(_, e) e.a, e.b = e.b, e.a end
local CD = tiny.processingSystem()
CD.filter = tiny.requireAll('c', 'd')
CD.process = function(_, e) e.c, e.d = e.d, e.c end
local CE = tiny.processingSystem()
CE.filter = tiny.requireAll('c', 'e')
CE.process = function(_, e) e.c, e.e = e.e, e.c end
tiny.addSystem(w, AB)
tiny.addSystem(w, CD)
tiny.addSystem(w, CE)
tiny.refresh(w)
return w
end)
common.describe('Fragmented Iteration', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for i = 1, 26 do
for _ = 1, 10000 do
local char = string.char(string.byte('a') + i - 1)
tiny.addEntity(w, { [char] = 0, data = 0 })
end
end
local Data = tiny.processingSystem()
Data.filter = tiny.requireAll('data')
Data.process = function(_, e) e.data = e.data * 2 end
local Z = tiny.processingSystem()
Z.filter = tiny.requireAll('z')
Z.process = function(_, e) e.z = e.z * 2 end
tiny.addSystem(w, Data)
tiny.addSystem(w, Z)
tiny.refresh(w)
return w
end)
common.describe('Entity Cycle (Cache)', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 1000 do
tiny.addEntity(w, { a = 0 })
end
local A = tiny.processingSystem()
A.filter = tiny.requireAll('a')
A.process = function(_, e) tiny.addEntity(w, { b = e.a }) end
A.postProcess = function(_) tiny.refresh(w) end
local B = tiny.processingSystem()
B.filter = tiny.requireAll('b')
B.process = function(_, e) tiny.removeEntity(w, e) end
B.postProcess = function(_) tiny.refresh(w) end
tiny.addSystem(w, A)
tiny.addSystem(w, B)
tiny.refresh(w)
return w
end)
common.describe('Entity Cycle (No Cache)', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 1000 do
tiny.addEntity(w, { a = 0 })
end
local A = tiny.processingSystem()
A.nocache = true
A.filter = tiny.requireAll('a')
A.process = function(_, e) tiny.addEntity(w, { b = e.a }) end
A.postProcess = function(_) tiny.refresh(w) end
local B = tiny.processingSystem()
B.nocache = true
B.filter = tiny.requireAll('b')
B.process = function(_, e) tiny.removeEntity(w, e) end
B.postProcess = function(_) tiny.refresh(w) end
tiny.addSystem(w, A)
tiny.addSystem(w, B)
tiny.refresh(w)
return w
end)
common.describe('Add / Remove (Cache)', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 10000 do
tiny.addEntity(w, { a = 0 })
end
local A = tiny.processingSystem()
A.filter = tiny.requireAll('a')
A.process = function(_, e)
e.b = 0
tiny.addEntity(w, e)
end
A.postProcess = function(_) tiny.refresh(w) end
local AB = tiny.processingSystem()
AB.filter = tiny.requireAll('a', 'b')
AB.process = function(_, e)
e.b = nil
tiny.addEntity(w, e)
end
AB.postProcess = function(_) tiny.refresh(w) end
tiny.addSystem(w, A)
tiny.addSystem(w, AB)
tiny.refresh(w)
return w
end)
common.describe('Add / Remove (No Cache)', function(w)
tiny.update(w, 0.016)
end, function()
local w = tiny.world()
for _ = 1, 10000 do
tiny.addEntity(w, { a = 0 })
end
local A = tiny.processingSystem()
A.nocache = true
A.filter = tiny.requireAll('a')
A.process = function(_, e)
e.b = 0
tiny.addEntity(w, e)
end
A.postProcess = function(_) tiny.refresh(w) end
local AB = tiny.processingSystem()
AB.nocache = true
AB.filter = tiny.requireAll('a', 'b')
AB.process = function(_, e)
e.b = nil
tiny.addEntity(w, e)
end
AB.postProcess = function(_) tiny.refresh(w) end
tiny.addSystem(w, A)
tiny.addSystem(w, AB)
tiny.refresh(w)
return w
end)

View File

@@ -1,4 +1 @@
require 'develop.untests.defer_untests'
require 'develop.untests.idpools_untests'
require 'develop.untests.registry_untests'
require 'develop.untests.singles_untests'
local evo = require 'evolved'

View File

@@ -1,82 +0,0 @@
---@diagnostic disable: invisible
local evo = require 'evolved.evolved'
do
local f = evo.registry.entity()
local e = evo.registry.entity()
local d = evo.defers.defer():set(e, f, 42)
assert(not e:has(f))
assert(d == d:playback())
assert(e:get(f) == 42)
evo.defers.defer():set(e, f, 84):playback()
assert(e:get(f) == 84)
end
do
local f = evo.registry.entity()
local e = evo.registry.entity():set(f, 21)
local d = evo.defers.defer():assign(e, f, 42)
assert(e:get(f) == 21)
assert(d == d:playback())
assert(e:get(f) == 42)
evo.defers.defer():assign(e, f, 84):playback()
assert(e:get(f) == 84)
end
do
local f = evo.registry.entity()
local e = evo.registry.entity()
local d = evo.defers.defer():insert(e, f, 42)
assert(not e:has(f))
assert(d == d:playback())
assert(e:get(f) == 42)
evo.defers.defer():insert(e, f, 84):playback()
assert(e:get(f) == 42)
end
do
local f = evo.registry.entity()
local e = evo.registry.entity():set(f, 21)
local d = evo.defers.defer():remove(e)
assert(e:get(f) == 21)
assert(d == d:playback())
assert(e:get(f) == 21)
end
do
local f = evo.registry.entity()
local e = evo.registry.entity():set(f, 21)
local d = evo.defers.defer():remove(e, f)
assert(e:get(f) == 21)
assert(d == d:playback())
assert(not e:has(f))
end
do
local f1, f2 = evo.registry.entity(), evo.registry.entity()
local e = evo.registry.entity():set(f1, 21):set(f2, 42)
assert(e:get(f1) == 21 and e:get(f2) == 42)
evo.defers.defer():remove(e, f1, f2):playback()
assert(not e:has(f1) and not e:has(f2))
end
do
local f1, f2 = evo.registry.entity(), evo.registry.entity()
local e = evo.registry.entity():set(f1, 4):set(f2, 2)
local d = evo.defers.defer():detach(e)
assert(e:alive() and e:has_all(f1, f2))
assert(d == d:playback())
assert(e:alive() and not e:has_any(f1, f2))
end
do
local f1, f2 = evo.registry.entity(), evo.registry.entity()
local e = evo.registry.entity():set(f1, 4):set(f2, 2)
local d = evo.defers.defer():destroy(e)
assert(e:alive() and e:has_all(f1, f2))
assert(d == d:playback())
assert(not e:alive() and not e:has_any(f1, f2))
end

View File

@@ -1,98 +0,0 @@
---@diagnostic disable: invisible
local evo = require 'evolved.evolved'
do
local p1 = evo.idpools.idpool()
local p2 = evo.idpools.idpool()
assert(p1 ~= p2)
end
do
local p = evo.idpools.idpool()
local i1_1 = p:acquire()
assert(i1_1 == 0x100001)
local i2_1 = p:acquire()
assert(i2_1 == 0x100002)
do
local i, v = p.unpack(i1_1)
assert(i == 1 and v == 1)
end
do
local i, v = p.unpack(i2_1)
assert(i == 2 and v == 1)
end
end
do
local p = evo.idpools.idpool()
local i1_1 = p:acquire()
local i2_1 = p:acquire()
assert(p:alive(i1_1))
assert(p:alive(i2_1))
p:release(i1_1)
assert(not p:alive(i1_1))
assert(p:alive(i2_1))
p:release(i2_1)
assert(not p:alive(i1_1))
assert(not p:alive(i2_1))
local i2_2 = p:acquire()
assert(i2_2 == 0x200002)
local i1_2 = p:acquire()
assert(i1_2 == 0x200001)
assert(not p:alive(i1_1))
assert(not p:alive(i2_1))
assert(p:alive(i1_2))
assert(p:alive(i2_2))
end
do
local p = evo.idpools.idpool()
for _ = 1, 0xFFFFF - 1 do
_ = p:acquire()
end
assert(p:acquire() == 0x1FFFFF)
if not os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") then
assert(not pcall(evo.idpools.acquire, p))
end
end
do
local p = evo.idpools.idpool()
for _ = 1, 0x7FF - 1 do
p:release(p:acquire())
end
local i1_7FF = p:acquire()
assert(i1_7FF == 0x7FF00001)
p:release(i1_7FF)
local i1_1 = p:acquire()
assert(i1_1 == 0x100001)
p:release(i1_1)
end
for _ = 1, 100 do
local o_index = math.random(0xFFFFF)
local o_version = math.random(0x7FF)
local id = evo.idpools.pack(o_index, o_version)
local r_index, r_version = evo.idpools.unpack(id)
assert(o_index == r_index)
assert(o_version == r_version)
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
---@diagnostic disable: invisible
local evo = require 'evolved.evolved'
do
local e = evo.registry.entity()
assert(not evo.singles.has(e))
assert(evo.singles.get(e) == nil)
assert(evo.singles.set(e, 42))
assert(evo.singles.has(e))
assert(evo.singles.get(e) == 42)
end
do
local s = evo.singles.single()
assert(evo.singles.has(s))
assert(evo.singles.get(s) == true)
assert(s == evo.singles.set(s, 42))
assert(evo.singles.get(s) == 42)
end
do
local s = evo.singles.single(42)
assert(evo.singles.has(s))
assert(evo.singles.get(s) == 42)
assert(s == evo.singles.set(s, true))
assert(evo.singles.get(s) == true)
assert(s == evo.singles.set(s, false))
assert(evo.singles.get(s) == false)
end

4
evolved.lua Normal file
View File

@@ -0,0 +1,4 @@
---@class evolved
local evolved = {}
return evolved

View File

@@ -1,32 +0,0 @@
---@class evolved.compat
local compat = {}
compat.pack = table.pack or function(...)
return { n = select('#', ...), ... }
end
compat.unpack = table.unpack or function(list, i, j)
return unpack(list, i, j)
end
compat.move = table.move or function(a1, f, e, t, a2)
if a2 == nil then
a2 = a1
end
if e < f then
return a2
end
local d = t - f
if t > e or t <= f or a2 ~= a1 then
for i = f, e do a2[i + d] = a1[i] end
else
for i = e, f, -1 do a2[i + d] = a1[i] end
end
return a2
end
return compat

View File

@@ -1,236 +0,0 @@
local compat = require 'evolved.compat'
local registry = require 'evolved.registry'
---@class evolved.defers
local defers = {}
---
---
---
---
---
---@enum evolved.defer_op
local evolved_defer_op = {
set = 1,
assign = 2,
insert = 3,
remove = 4,
detach = 5,
destroy = 6,
}
---@class (exact) evolved.__defer
---@field operations any[]
---@field operation_count integer
---@class evolved.defer : evolved.__defer
local evolved_defer_mt = {}
evolved_defer_mt.__index = evolved_defer_mt
---
---
---
---
---
---@type table<evolved.defer_op, fun(ops: any[], idx: integer): integer>
local __operation_processors = {
[evolved_defer_op.set] = function(ops, idx)
local entity = ops[idx + 1]
local fragment = ops[idx + 2]
local component = ops[idx + 3]
registry.set(entity, fragment, component)
return 4
end,
[evolved_defer_op.assign] = function(ops, idx)
local entity = ops[idx + 1]
local fragment = ops[idx + 2]
local component = ops[idx + 3]
registry.assign(entity, fragment, component)
return 4
end,
[evolved_defer_op.insert] = function(ops, ids)
local entity = ops[ids + 1]
local fragment = ops[ids + 2]
local component = ops[ids + 3]
registry.insert(entity, fragment, component)
return 4
end,
[evolved_defer_op.remove] = function(ops, idx)
local entity = ops[idx + 1]
local fragment_count = ops[idx + 2]
registry.remove(entity, compat.unpack(ops, idx + 3, idx + 3 + fragment_count))
return 3 + fragment_count
end,
[evolved_defer_op.detach] = function(ops, idx)
local entity = ops[idx + 1]
registry.detach(entity)
return 2
end,
[evolved_defer_op.destroy] = function(ops, idx)
local entity = ops[idx + 1]
registry.destroy(entity)
return 2
end,
}
---
---
---
---
---
---@return evolved.defer
---@nodiscard
function defers.defer()
---@type evolved.__defer
local defer = {
operations = {},
operation_count = 0,
}
---@cast defer evolved.defer
return setmetatable(defer, evolved_defer_mt)
end
---@param defer evolved.defer
---@param entity evolved.entity
---@param fragment evolved.entity
---@param component evolved.component
---@return evolved.defer
function defers.set(defer, entity, fragment, component)
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.set
operations[operation_count + 2] = entity
operations[operation_count + 3] = fragment
operations[operation_count + 4] = component
defer.operation_count = operation_count + 4
return defer
end
---@param defer evolved.defer
---@param entity evolved.entity
---@param fragment evolved.entity
---@param component evolved.component
---@return evolved.defer
function defers.assign(defer, entity, fragment, component)
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.assign
operations[operation_count + 2] = entity
operations[operation_count + 3] = fragment
operations[operation_count + 4] = component
defer.operation_count = operation_count + 4
return defer
end
---@param defer evolved.defer
---@param entity evolved.entity
---@param fragment evolved.entity
---@param component evolved.component
---@return evolved.defer
function defers.insert(defer, entity, fragment, component)
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.insert
operations[operation_count + 2] = entity
operations[operation_count + 3] = fragment
operations[operation_count + 4] = component
defer.operation_count = operation_count + 4
return defer
end
---@param defer evolved.defer
---@param entity evolved.entity
---@param ... evolved.entity fragments
---@return evolved.defer
function defers.remove(defer, entity, ...)
local fragment_count = select('#', ...)
if fragment_count == 0 then return defer end
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.remove
operations[operation_count + 2] = entity
operations[operation_count + 3] = fragment_count
for i = 1, fragment_count do
operations[operation_count + 3 + i] = select(i, ...)
end
defer.operation_count = operation_count + 3 + fragment_count
return defer
end
---@param defer evolved.defer
---@param entity evolved.entity
---@return evolved.defer
function defers.detach(defer, entity)
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.detach
operations[operation_count + 2] = entity
defer.operation_count = operation_count + 2
return defer
end
---@param defer evolved.defer
---@param entity evolved.entity
---@return evolved.defer
function defers.destroy(defer, entity)
local operations = defer.operations
local operation_count = defer.operation_count
operations[operation_count + 1] = evolved_defer_op.destroy
operations[operation_count + 2] = entity
defer.operation_count = operation_count + 2
return defer
end
---@param defer evolved.defer
---@return evolved.defer
function defers.playback(defer)
local operations = defer.operations
local operation_count = defer.operation_count
local operation_index = 1; while operation_index <= operation_count do
local operation = operations[operation_index]
local processor = __operation_processors[operation]
operation_index = operation_index + processor(operations, operation_index)
end
return defer
end
---
---
---
---
---
evolved_defer_mt.set = defers.set
evolved_defer_mt.assign = defers.assign
evolved_defer_mt.insert = defers.insert
evolved_defer_mt.remove = defers.remove
evolved_defer_mt.detach = defers.detach
evolved_defer_mt.destroy = defers.destroy
evolved_defer_mt.playback = defers.playback
---
---
---
---
---
return defers

View File

@@ -1,8 +0,0 @@
return {
compat = require 'evolved.compat',
defers = require 'evolved.defers',
idpools = require 'evolved.idpools',
registry = require 'evolved.registry',
singles = require 'evolved.singles',
vectors = require 'evolved.vectors',
}

View File

@@ -1,127 +0,0 @@
---@class evolved.idpools
local idpools = {}
---
---
---
---
---
---@alias evolved.id integer
---@class (exact) evolved.__idpool
---@field package __freelist_ids evolved.id[]
---@field package __available_index integer
---@class evolved.idpool : evolved.__idpool
local evolved_idpool_mt = {}
evolved_idpool_mt.__index = evolved_idpool_mt
---
---
---
---
---
---@return evolved.idpool
function idpools.idpool()
---@type evolved.__idpool
local idpool = {
__freelist_ids = {},
__available_index = 0,
}
---@cast idpool evolved.idpool
return setmetatable(idpool, evolved_idpool_mt)
end
---@param index integer
---@param version integer
---@return evolved.id
function idpools.pack(index, version)
assert(index >= 1 and index <= 0xFFFFF, 'id index out of range [1;0xFFFFF]')
assert(version >= 1 and version <= 0x7FF, 'id version out of range [1;0x7FF]')
return index + version * 0x100000
end
---@param id evolved.id
---@return integer index
---@return integer version
function idpools.unpack(id)
local index = id % 0x100000
local version = (id - index) / 0x100000
return index, version
end
---@param idpool evolved.idpool
---@param id evolved.id
---@return boolean
---@nodiscard
function idpools.alive(idpool, id)
local index = id % 0x100000
return idpool.__freelist_ids[index] == id
end
---@param idpool evolved.idpool
---@return evolved.id
---@nodiscard
function idpools.acquire(idpool)
if idpool.__available_index ~= 0 then
local index = idpool.__available_index
local freelist_id = idpool.__freelist_ids[index]
idpool.__available_index = freelist_id % 0x100000
local version = freelist_id - idpool.__available_index
local acquired_id = index + version
idpool.__freelist_ids[index] = acquired_id
return acquired_id
else
if #idpool.__freelist_ids == 0xFFFFF then
error('id index overflow', 2)
end
local index = #idpool.__freelist_ids + 1
local version = 0x100000
local acquired_id = index + version
idpool.__freelist_ids[index] = acquired_id
return acquired_id
end
end
---@param idpool evolved.idpool
---@param id evolved.id
function idpools.release(idpool, id)
local index = id % 0x100000
local version = id - index
if idpool.__freelist_ids[index] ~= id then
error('id is not acquired or already released', 2)
end
version = version == 0x7FF00000
and 0x100000
or version + 0x100000
idpool.__freelist_ids[index] = idpool.__available_index + version
idpool.__available_index = index
end
---
---
---
---
---
evolved_idpool_mt.pack = idpools.pack
evolved_idpool_mt.unpack = idpools.unpack
evolved_idpool_mt.alive = idpools.alive
evolved_idpool_mt.acquire = idpools.acquire
evolved_idpool_mt.release = idpools.release
---
---
---
---
---
return idpools

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +0,0 @@
local registry = require 'evolved.registry'
---@class evolved.singles
local singles = {}
---@param component evolved.component
---@return evolved.entity
---@nodiscard
function singles.single(component)
local single = registry.entity()
return single:set(single, component)
end
---@param single evolved.entity
---@return evolved.component
---@nodiscard
function singles.get(single)
return single:get(single)
end
---@param single evolved.entity
---@return boolean
---@nodiscard
function singles.has(single)
return single:has(single)
end
---@param single evolved.entity
---@param component evolved.component
---@return evolved.entity
function singles.set(single, component)
return single:set(single, component)
end
return singles

View File

@@ -1,138 +0,0 @@
---@class evolved.vectors
local vectors = {}
---@class (exact) evolved.__vector2
---@field x number
---@field y number
---@class evolved.vector2 : evolved.__vector2
local evolved_vector2_mt = {}
evolved_vector2_mt.__index = evolved_vector2_mt
---@param x number
---@param y number
---@return evolved.vector2
---@nodiscard
local function vector2(x, y)
---@type evolved.__vector2
local v = { x = x, y = y }
---@cast v evolved.vector2
return setmetatable(v, evolved_vector2_mt)
end
---@param v evolved.vector2
---@return evolved.vector2
---@nodiscard
function evolved_vector2_mt.__unm(v)
return vector2(-v.x, -v.y)
end
---@param a number | evolved.vector2
---@param b number | evolved.vector2
---@return evolved.vector2
---@nodiscard
function evolved_vector2_mt.__add(a, b)
if type(a) == 'number' then
return vector2(a + b.x, a + b.y)
elseif type(b) == 'number' then
return vector2(a.x + b, a.y + b)
else
return vector2(a.x + b.x, a.y + b.y)
end
end
---@param a number | evolved.vector2
---@param b number | evolved.vector2
---@return evolved.vector2
---@nodiscard
function evolved_vector2_mt.__sub(a, b)
if type(a) == 'number' then
return vector2(a - b.x, a - b.y)
elseif type(b) == 'number' then
return vector2(a.x - b, a.y - b)
else
return vector2(a.x - b.x, a.y - b.y)
end
end
---@param a number | evolved.vector2
---@param b number | evolved.vector2
---@return evolved.vector2
---@nodiscard
function evolved_vector2_mt.__mul(a, b)
if type(a) == 'number' then
return vector2(a * b.x, a * b.y)
elseif type(b) == 'number' then
return vector2(a.x * b, a.y * b)
else
return vector2(a.x * b.x, a.y * b.y)
end
end
---@param a number | evolved.vector2
---@param b number | evolved.vector2
---@return evolved.vector2
---@nodiscard
function evolved_vector2_mt.__div(a, b)
if type(a) == 'number' then
return vector2(a / b.x, a / b.y)
elseif type(b) == 'number' then
return vector2(a.x / b, a.y / b)
else
return vector2(a.x / b.x, a.y / b.y)
end
end
---@param a evolved.vector2
---@param b evolved.vector2
---@return boolean
---@nodiscard
function evolved_vector2_mt.__eq(a, b)
return a.x == b.x and a.y == b.y
end
---@param a evolved.vector2
---@param b evolved.vector2
---@return boolean
---@nodiscard
function evolved_vector2_mt.__le(a, b)
return a.x < b.x or (a.x == b.x and a.y <= b.y)
end
---@param a evolved.vector2
---@param b evolved.vector2
---@return boolean
---@nodiscard
function evolved_vector2_mt.__lt(a, b)
return a.x < b.x or (a.x == b.x and a.y < b.y)
end
---@param v evolved.vector2
---@return string
---@nodiscard
function evolved_vector2_mt.__tostring(v)
return string.format('(%f, %f)', v.x, v.y)
end
---
---
---
---
---
---@param x number
---@param y number
---@return evolved.vector2
---@nodiscard
function vectors.vector2(x, y)
return vector2(x, y)
end
---@param v any
---@return boolean
---@nodiscard
function vectors.is_vector2(v)
return getmetatable(v) == evolved_vector2_mt
end
return vectors