diff --git a/.luarc.json b/.luarc.json index a493161..377a4bf 100644 --- a/.luarc.json +++ b/.luarc.json @@ -23,5 +23,11 @@ "runtime": { "version": "LuaJIT", "pathStrict": true + }, + "workspace": { + "ignoreDir": [ + ".vscode", + "develop/3rdparty" + ] } } diff --git a/ROADMAP.md b/ROADMAP.md index 60603d4..e825d00 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,8 +5,9 @@ - add auto chunk count reducing - optimize batch operations for cases with moving entities to empty chunks - should we clear chunk's components by on_insert tag callback? -- replace id type aliases with separated types to hide implementation details - clear chunk's tables instead reallocating them - add REQUIRES fragment trait - try to keep entity_chunks/places tables as arrays - set/assign/insert/remove/destroy for lists? +- when we call hooks from chunk operations, we should use precached hook functions +- we shouldn't clear big reusable tables diff --git a/develop/3rdparty/tiny.lua b/develop/3rdparty/tiny.lua new file mode 100644 index 0000000..4dca2cd --- /dev/null +++ b/develop/3rdparty/tiny.lua @@ -0,0 +1,864 @@ +--[[ +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 "" + end +} + +return tiny diff --git a/develop/all.lua b/develop/all.lua index e386ff5..49a0913 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,3 +1,4 @@ require 'develop.example' require 'develop.unbench' require 'develop.untests' +require 'develop.usbench' diff --git a/develop/basics.lua b/develop/basics.lua index 0677c29..ca6dab3 100644 --- a/develop/basics.lua +++ b/develop/basics.lua @@ -13,7 +13,8 @@ end)() ---@param name string ---@param loop fun(...): ... ---@param init? fun(): ... -function basics.describe_bench(name, loop, init) +---@param fini? fun(...): ... +function basics.describe_bench(name, loop, init, fini) print(string.format('| %s ... |', name)) local state = init and __table_pack(init()) or {} @@ -28,7 +29,7 @@ function basics.describe_bench(name, loop, init) end) if not success then - print('|-- FAIL: ' .. result) + print('|-- WARMUP FAIL: ' .. result) return end end @@ -59,7 +60,17 @@ function basics.describe_bench(name, loop, init) (finish_kb - start_kb) / iters, iters)) else - print('|-- FAIL: ' .. result) + print('|-- LOOP FAIL: ' .. result) + end + end + + if fini then + local success, result = pcall(function() + fini(__table_unpack(state)) + end) + + if not success then + print('|-- FINI FAIL: ' .. result) end end diff --git a/develop/unbench.lua b/develop/unbench.lua index 89402ed..c317a2f 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -6,6 +6,7 @@ local basics = require 'develop.basics' local N = 1000 local B = evo.entity() local F1, F2, F3, F4, F5 = evo.id(5) +local Q1 = evo.query():include(F1):build() print '----------------------------------------' @@ -224,7 +225,7 @@ basics.describe_bench(string.format('create and destroy %d entities', N), entities[i] = e end - for i = 1, #entities do + for i = #entities, 1, -1 do destroy(entities[i]) end end, function() @@ -236,7 +237,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy for i = 1, N do local e = id() @@ -244,9 +244,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -256,7 +254,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy for i = 1, N do local e = id() @@ -265,9 +262,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -277,7 +272,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy for i = 1, N do local e = id() @@ -287,9 +281,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -299,7 +291,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy for i = 1, N do local e = id() @@ -310,9 +301,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -322,7 +311,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy for i = 1, N do local e = id() @@ -334,9 +322,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -348,7 +334,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy evo.defer() for i = 1, N do @@ -358,9 +343,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo end evo.commit() - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -370,7 +353,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy evo.defer() for i = 1, N do @@ -381,9 +363,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo end evo.commit() - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -393,7 +373,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy evo.defer() for i = 1, N do @@ -405,9 +384,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo end evo.commit() - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -417,7 +394,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy evo.defer() for i = 1, N do @@ -430,9 +406,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo end evo.commit() - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -442,7 +416,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo function(entities) local id = evo.id local insert = evo.insert - local destroy = evo.destroy evo.defer() for i = 1, N do @@ -456,9 +429,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo end evo.commit() - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -470,16 +441,13 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo function(entities) local set = B.set local build = B.build - local destroy = evo.destroy for i = 1, N do set(B, F1) entities[i] = build(B) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -489,7 +457,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo function(entities) local set = B.set local build = B.build - local destroy = evo.destroy for i = 1, N do set(B, F1) @@ -497,9 +464,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo entities[i] = build(B) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -509,7 +474,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo function(entities) local set = B.set local build = B.build - local destroy = evo.destroy for i = 1, N do set(B, F1) @@ -518,9 +482,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo entities[i] = build(B) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -530,7 +492,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo function(entities) local set = B.set local build = B.build - local destroy = evo.destroy for i = 1, N do set(B, F1) @@ -540,9 +501,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo entities[i] = build(B) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -552,7 +511,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo function(entities) local set = B.set local build = B.build - local destroy = evo.destroy for i = 1, N do set(B, F1) @@ -563,9 +521,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo entities[i] = build(B) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -576,7 +532,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo ---@param entities evolved.id[] function(entities) local set = evo.multi_set - local destroy = evo.destroy for i = 1, N do local e = evo.id() @@ -584,9 +539,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -595,7 +548,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo ---@param entities evolved.id[] function(entities) local set = evo.multi_set - local destroy = evo.destroy for i = 1, N do local e = evo.id() @@ -603,9 +555,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -614,7 +564,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo ---@param entities evolved.id[] function(entities) local set = evo.multi_set - local destroy = evo.destroy for i = 1, N do local e = evo.id() @@ -622,9 +571,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -633,7 +580,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo ---@param entities evolved.id[] function(entities) local set = evo.multi_set - local destroy = evo.destroy for i = 1, N do local e = evo.id() @@ -641,9 +587,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -652,7 +596,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo ---@param entities evolved.id[] function(entities) local set = evo.multi_set - local destroy = evo.destroy for i = 1, N do local e = evo.id() @@ -660,9 +603,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo entities[i] = e end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -672,8 +613,8 @@ print '----------------------------------------' basics.describe_bench(string.format('create and destroy %d entities / spawn_at', N), ---@param entities evolved.id[] function(entities) - local spawn_at = evo.spawn_at local destroy = evo.destroy + local spawn_at = evo.spawn_at local fragments = {} local components = {} @@ -684,7 +625,7 @@ basics.describe_bench(string.format('create and destroy %d entities / spawn_at', entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do + for i = #entities, 1, -1 do destroy(entities[i]) end end, function() @@ -695,7 +636,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo ---@param entities evolved.id[] function(entities) local spawn_at = evo.spawn_at - local destroy = evo.destroy local fragments = { F1 } local components = { true } @@ -706,9 +646,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -717,7 +655,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo ---@param entities evolved.id[] function(entities) local spawn_at = evo.spawn_at - local destroy = evo.destroy local fragments = { F1, F2 } local components = { true, true } @@ -728,9 +665,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -739,7 +674,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo ---@param entities evolved.id[] function(entities) local spawn_at = evo.spawn_at - local destroy = evo.destroy local fragments = { F1, F2, F3 } local components = { true, true, true } @@ -750,9 +684,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -761,7 +693,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo ---@param entities evolved.id[] function(entities) local spawn_at = evo.spawn_at - local destroy = evo.destroy local fragments = { F1, F2, F3, F4 } local components = { true, true, true, true } @@ -772,9 +703,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -783,7 +712,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo ---@param entities evolved.id[] function(entities) local spawn_at = evo.spawn_at - local destroy = evo.destroy local fragments = { F1, F2, F3, F4, F5 } local components = { true, true, true, true, true } @@ -794,9 +722,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo entities[i] = spawn_at(chunk, fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -806,8 +732,8 @@ print '----------------------------------------' basics.describe_bench(string.format('create and destroy %d entities / spawn_with', N), ---@param entities evolved.id[] function(entities) - local spawn_with = evo.spawn_with local destroy = evo.destroy + local spawn_with = evo.spawn_with local fragments = {} local components = {} @@ -816,7 +742,7 @@ basics.describe_bench(string.format('create and destroy %d entities / spawn_with entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do + for i = #entities, 1, -1 do destroy(entities[i]) end end, function() @@ -827,7 +753,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo ---@param entities evolved.id[] function(entities) local spawn_with = evo.spawn_with - local destroy = evo.destroy local fragments = { F1 } local components = { true } @@ -836,9 +761,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 1 compo entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -847,7 +770,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo ---@param entities evolved.id[] function(entities) local spawn_with = evo.spawn_with - local destroy = evo.destroy local fragments = { F1, F2 } local components = { true, true } @@ -856,9 +778,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 2 compo entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -867,7 +787,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo ---@param entities evolved.id[] function(entities) local spawn_with = evo.spawn_with - local destroy = evo.destroy local fragments = { F1, F2, F3 } local components = { true, true, true } @@ -876,9 +795,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 3 compo entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -887,7 +804,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo ---@param entities evolved.id[] function(entities) local spawn_with = evo.spawn_with - local destroy = evo.destroy local fragments = { F1, F2, F3, F4 } local components = { true, true, true, true } @@ -896,9 +812,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 4 compo entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) @@ -907,7 +821,6 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo ---@param entities evolved.id[] function(entities) local spawn_with = evo.spawn_with - local destroy = evo.destroy local fragments = { F1, F2, F3, F4, F5 } local components = { true, true, true, true, true } @@ -916,9 +829,7 @@ basics.describe_bench(string.format('create and destroy %d entities with 5 compo entities[i] = spawn_with(fragments, components) end - for i = 1, #entities do - destroy(entities[i]) - end + evo.batch_destroy(Q1) end, function() return {} end) diff --git a/develop/untests.lua b/develop/untests.lua index 81c1e4e..32bf93a 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -4957,9 +4957,14 @@ end do local id = evo.pack(7, 3) - assert(id == 0x300007) - local index, version = evo.unpack(0x500004) - assert(index == 4 and version == 5) + local index, version = evo.unpack(id) + assert(index == 7 and version == 3) +end + +do + local id = evo.pack(0xFFFFF, 0x7FF) + local index, version = evo.unpack(id) + assert(index == 0xFFFFF and version == 0x7FF) end do @@ -5120,3 +5125,450 @@ do assert(c4 and #c4 == 1 and c4[1] == 4) end end + +do + local f0, f1, f2, f3, f4 = evo.id(5) + + evo.set(f0, evo.CONSTRUCT, function() + return 42 + end) + + evo.set(f1, evo.CONSTRUCT, function(a1) + return a1 + end) + + evo.set(f2, evo.CONSTRUCT, function(a1, a2) + return a1 + a2 + end) + + evo.set(f3, evo.CONSTRUCT, function(a1, a2, a3) + return a1 + a2 + a3 + end) + + evo.set(f4, evo.CONSTRUCT, function(a1, a2, a3, a4) + return a1 + a2 + a3 + a4 + end) + + do + local e1 = evo.id() + evo.set(e1, f0) + evo.set(e1, f1, 1) + evo.set(e1, f2, 1, 2) + evo.set(e1, f3, 1, 2, 3) + evo.set(e1, f4, 1, 2, 3, 4) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + end + do + local e1 = evo.id() + evo.insert(e1, f0) + evo.insert(e1, f1, 1) + evo.insert(e1, f2, 1, 2) + evo.insert(e1, f3, 1, 2, 3) + evo.insert(e1, f4, 1, 2, 3, 4) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + end + do + local e1 = evo.id() + evo.multi_insert(e1, { f0, f1, f2, f3, f4 }) + evo.assign(e1, f0) + evo.assign(e1, f1, 1) + evo.assign(e1, f2, 1, 2) + evo.assign(e1, f3, 1, 2, 3) + evo.assign(e1, f4, 1, 2, 3, 4) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + end + do + local e1, e2, e3, e4 = evo.id(4) + evo.multi_insert(e1, { f1, f2, f3, f4 }) + evo.multi_insert(e2, { f1, f2, f3, f4 }) + evo.multi_insert(e3, { f1, f2, f3, f4 }) + evo.multi_insert(e4, { f1, f2, f3, f4 }) + evo.remove(e1, f0, f1) + evo.remove(e2, f0, f1, f2) + evo.remove(e3, f0, f1, f2, f3) + evo.remove(e4, f0, f1, f2, f3, f4) + assert(evo.get(e1, f1) == nil and evo.get(e1, f2) == true and evo.get(e1, f3) == true and evo.get(e1, f4) == true) + assert(evo.get(e2, f1) == nil and evo.get(e2, f2) == nil and evo.get(e2, f3) == true and evo.get(e2, f4) == true) + assert(evo.get(e3, f1) == nil and evo.get(e3, f2) == nil and evo.get(e3, f3) == nil and evo.get(e3, f4) == true) + assert(evo.get(e4, f1) == nil and evo.get(e4, f2) == nil and evo.get(e4, f3) == nil and evo.get(e4, f4) == nil) + end + do + local e1, e2 = evo.id(2) + assert(evo.defer()) + evo.set(e1, f0) + evo.set(e1, f1, 1) + evo.set(e1, f2, 1, 2) + evo.set(e1, f3, 1, 2, 3) + evo.set(e1, f4, 1, 2, 3, 4) + evo.set(e2, f0) + evo.set(e2, f1, 1) + evo.set(e2, f2, 1, 2) + evo.set(e2, f3, 1, 2, 3) + evo.set(e2, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1, e2 = evo.id(2) + assert(evo.defer()) + evo.insert(e1, f0) + evo.insert(e1, f1, 1) + evo.insert(e1, f2, 1, 2) + evo.insert(e1, f3, 1, 2, 3) + evo.insert(e1, f4, 1, 2, 3, 4) + evo.insert(e2, f0) + evo.insert(e2, f1, 1) + evo.insert(e2, f2, 1, 2) + evo.insert(e2, f3, 1, 2, 3) + evo.insert(e2, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1, e2 = evo.id(2) + evo.multi_insert(e1, { f0, f1, f2, f3, f4 }) + evo.multi_insert(e2, { f0, f1, f2, f3, f4 }) + assert(evo.defer()) + evo.assign(e1, f0) + evo.assign(e1, f1, 1) + evo.assign(e1, f2, 1, 2) + evo.assign(e1, f3, 1, 2, 3) + evo.assign(e1, f4, 1, 2, 3, 4) + evo.assign(e2, f0) + evo.assign(e2, f1, 1) + evo.assign(e2, f2, 1, 2) + evo.assign(e2, f3, 1, 2, 3) + evo.assign(e2, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1, e2, e3, e4 = evo.id(4) + evo.multi_insert(e1, { f1, f2, f3, f4 }) + evo.multi_insert(e2, { f1, f2, f3, f4 }) + evo.multi_insert(e3, { f1, f2, f3, f4 }) + evo.multi_insert(e4, { f1, f2, f3, f4 }) + assert(evo.defer()) + evo.remove(e1, f1) + evo.remove(e2, f1, f2) + evo.remove(e3, f1, f2, f3) + evo.remove(e4, f1, f2, f3, f4) + assert(evo.commit()) + assert(evo.get(e1, f1) == nil and evo.get(e1, f2) == true and evo.get(e1, f3) == true and evo.get(e1, f4) == true) + assert(evo.get(e2, f1) == nil and evo.get(e2, f2) == nil and evo.get(e2, f3) == true and evo.get(e2, f4) == true) + assert(evo.get(e3, f1) == nil and evo.get(e3, f2) == nil and evo.get(e3, f3) == nil and evo.get(e3, f4) == true) + assert(evo.get(e4, f1) == nil and evo.get(e4, f2) == nil and evo.get(e4, f3) == nil and evo.get(e4, f4) == nil) + end +end + +do + local fa, f0, f1, f2, f3, f4 = evo.id(6) + local q0 = evo.query():include(fa):build() + + evo.set(f0, evo.CONSTRUCT, function() + return 42 + end) + + evo.set(f1, evo.CONSTRUCT, function(a1) + return a1 + end) + + evo.set(f2, evo.CONSTRUCT, function(a1, a2) + return a1 + a2 + end) + + evo.set(f3, evo.CONSTRUCT, function(a1, a2, a3) + return a1 + a2 + a3 + end) + + evo.set(f4, evo.CONSTRUCT, function(a1, a2, a3, a4) + return a1 + a2 + a3 + a4 + end) + + do + local e1 = evo.entity():set(fa):build() + local e2 = evo.entity():set(fa):build() + assert(evo.defer()) + evo.batch_set(q0, f0) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.defer()) + evo.batch_set(q0, f1, 1) + assert(evo.commit()) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e2, f1) == 1) + assert(evo.defer()) + evo.batch_set(q0, f2, 1, 2) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.defer()) + evo.batch_set(q0, f3, 1, 2, 3) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.defer()) + evo.batch_set(q0, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.defer()) + evo.batch_set(q0, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1 = evo.entity():set(fa):build() + local e2 = evo.entity():set(fa):build() + assert(evo.defer()) + evo.batch_insert(q0, f0) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.defer()) + evo.batch_insert(q0, f1, 1) + assert(evo.commit()) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e2, f1) == 1) + assert(evo.defer()) + evo.batch_insert(q0, f2, 1, 2) + assert(evo.commit()) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.defer()) + evo.batch_insert(q0, f3, 1, 2, 3) + assert(evo.commit()) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.defer()) + evo.batch_insert(q0, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1 = evo.entity():set(fa):build() + local e2 = evo.entity():set(fa):build() + assert(evo.defer()) + evo.batch_insert(q0, f0, 0) + evo.batch_assign(q0, f0) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.defer()) + evo.batch_insert(q0, f1, 0) + evo.batch_assign(q0, f1, 1) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e2, f1) == 1) + assert(evo.defer()) + evo.batch_insert(q0, f2, 0, 0) + evo.batch_assign(q0, f2, 1, 2) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.defer()) + evo.batch_insert(q0, f3, 0, 0, 0) + evo.batch_assign(q0, f3, 1, 2, 3) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.defer()) + evo.batch_insert(q0, f4, 0, 0, 0, 0) + evo.batch_assign(q0, f4, 1, 2, 3, 4) + assert(evo.commit()) + assert(evo.get(e1, f0) == 42) + assert(evo.get(e2, f0) == 42) + assert(evo.get(e1, f1) == 1) + assert(evo.get(e1, f2) == 1 + 2) + assert(evo.get(e1, f3) == 1 + 2 + 3) + assert(evo.get(e1, f4) == 1 + 2 + 3 + 4) + assert(evo.get(e2, f1) == 1) + assert(evo.get(e2, f2) == 1 + 2) + assert(evo.get(e2, f3) == 1 + 2 + 3) + assert(evo.get(e2, f4) == 1 + 2 + 3 + 4) + end + do + local e1 = evo.entity() + :set(fa) + :build() + local e2 = evo.entity() + :set(fa) + :build() + assert(evo.defer()) + evo.batch_remove(q0) + assert(evo.commit()) + assert(evo.get(e1, fa) == true) + assert(evo.get(e2, fa) == true) + end + do + local e1 = evo.entity() + :set(fa) + :set(f1, 1) + :build() + local e2 = evo.entity() + :set(fa) + :set(f1, 1) + :build() + assert(evo.defer()) + evo.batch_remove(q0, f1) + assert(evo.commit()) + assert(evo.get(e1, f1) == nil) + assert(evo.get(e2, f1) == nil) + end + do + local e1 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :build() + local e2 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :build() + assert(evo.defer()) + evo.batch_remove(q0, f1, f2) + assert(evo.commit()) + assert(evo.get(e1, f1) == nil) + assert(evo.get(e2, f1) == nil) + assert(evo.get(e1, f2) == nil) + assert(evo.get(e2, f2) == nil) + end + do + local e1 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :set(f3, 1, 2, 3) + :build() + local e2 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :set(f3, 1, 2, 3) + :build() + assert(evo.defer()) + evo.batch_remove(q0, f1, f2, f3) + assert(evo.commit()) + assert(evo.get(e1, f1) == nil) + assert(evo.get(e2, f1) == nil) + assert(evo.get(e1, f2) == nil) + assert(evo.get(e2, f2) == nil) + assert(evo.get(e1, f3) == nil) + assert(evo.get(e2, f3) == nil) + end + do + local e1 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :set(f3, 1, 2, 3) + :set(f4, 1, 2, 3, 4) + :build() + local e2 = evo.entity() + :set(fa) + :set(f1, 1) + :set(f2, 1, 2) + :set(f3, 1, 2, 3) + :set(f4, 1, 2, 3, 4) + :build() + assert(evo.defer()) + evo.batch_remove(q0, f1, f2, f3, f4) + assert(evo.commit()) + assert(evo.get(e1, f1) == nil) + assert(evo.get(e2, f1) == nil) + assert(evo.get(e1, f2) == nil) + assert(evo.get(e2, f2) == nil) + assert(evo.get(e1, f3) == nil) + assert(evo.get(e2, f3) == nil) + assert(evo.get(e1, f4) == nil) + assert(evo.get(e2, f4) == nil) + end +end diff --git a/develop/usbench.lua b/develop/usbench.lua new file mode 100644 index 0000000..8c72b37 --- /dev/null +++ b/develop/usbench.lua @@ -0,0 +1,374 @@ +package.loaded['evolved'] = nil +local evo = require 'evolved' + +local basics = require 'develop.basics' +local tiny = require 'develop.3rdparty.tiny' + +local N = 1000 + +print '----------------------------------------' + +basics.describe_bench(string.format('Tiny Entity Cycle: %d entities', N), + function(world) + world:update(0.016) + end, function() + local world = tiny.world() + + for i = 1, N do + world:addEntity({ a = i }) + end + + local A = tiny.processingSystem() + A.filter = tiny.requireAll('a') + A.process = function(_, e) world:addEntity({ b = e.a }) end + A.postProcess = function(_) world:refresh() end + + local B = tiny.processingSystem() + B.filter = tiny.requireAll('b') + B.process = function(_, e) world:removeEntity(e) end + B.postProcess = function(_) world:refresh() end + + world:addSystem(A) + world:addSystem(B) + + world:refresh() + + return world + end) + +basics.describe_bench(string.format('Evolved Entity Cycle (Defer): %d entities', N), + function(a, b, A, B) + evo.defer() + do + for chunk, entities in evo.execute(A) do + local as = evo.select(chunk, a) + for i = 1, #entities do + evo.set(evo.id(), b, as[i]) + end + end + end + evo.commit() + + evo.batch_destroy(B) + end, function() + local a, b = evo.id(2) + + for i = 1, N do + evo.entity():set(a, i):build() + end + + local A = evo.query():include(a):build() + local B = evo.query():include(b):build() + + return a, b, A, B + end, function(_, _, A, _) + evo.batch_destroy(A) + end) + +basics.describe_bench(string.format('Evolved Entity Cycle (Manual): %d entities', N), + function(a, b, A, B) + local to_create = {} + + for chunk, entities in evo.execute(A) do + local as = evo.select(chunk, a) + for i = 1, #entities do + to_create[#to_create + 1] = as[i] + end + end + + for i = 1, #to_create do + local e = evo.id() + evo.set(e, b, to_create[i]) + end + + evo.batch_destroy(B) + end, function() + local a, b = evo.id(2) + + for i = 1, N do + evo.entity():set(a, i):build() + end + + local A = evo.query():include(a):build() + local B = evo.query():include(b):build() + + return a, b, A, B + end, function(_, _, A, _) + evo.batch_destroy(A) + end) + +print '----------------------------------------' + +basics.describe_bench(string.format('Tiny Simple Iteration: %d entities', N), + function(world) + world:update(0.016) + end, function() + local world = tiny.world() + + for i = 1, N do + world:addEntity({ a = i, b = i }) + world:addEntity({ a = i, b = i, c = i }) + world:addEntity({ a = i, b = i, c = i, d = i }) + world:addEntity({ a = i, b = i, c = i, e = i }) + 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 + + world:addSystem(AB) + world:addSystem(CD) + world:addSystem(CE) + + world:refresh() + + return world + end) + +basics.describe_bench(string.format('Evolved Simple Iteration: %d entities', N), + ---@param a evolved.entity + ---@param b evolved.entity + ---@param c evolved.entity + ---@param d evolved.entity + ---@param e evolved.entity + ---@param AB evolved.query + ---@param CD evolved.query + ---@param CE evolved.query + function(a, b, c, d, e, AB, CD, CE) + for chunk, entities in evo.execute(AB) do + local as, bs = evo.select(chunk, a, b) + for i = 1, #entities do + as[i], bs[i] = bs[i], as[i] + end + end + + for chunk, entities in evo.execute(CD) do + local cs, ds = evo.select(chunk, c, d) + for i = 1, #entities do + cs[i], ds[i] = ds[i], cs[i] + end + end + + for chunk, entities in evo.execute(CE) do + local cs, es = evo.select(chunk, c, e) + for i = 1, #entities do + cs[i], es[i] = es[i], cs[i] + end + end + end, function() + local a, b, c, d, e = evo.id(5) + + for i = 1, N do + evo.entity():set(a, i):set(b, i):build() + evo.entity():set(a, i):set(b, i):set(c, i):build() + evo.entity():set(a, i):set(b, i):set(c, i):set(d, i):build() + evo.entity():set(a, i):set(b, i):set(c, i):set(e, i):build() + end + + local AB = evo.query():include(a, b):build() + local CD = evo.query():include(c, d):build() + local CE = evo.query():include(c, e):build() + + return a, b, c, d, e, AB, CD, CE + end, function(_, _, _, _, _, AB, CD, CE) + evo.batch_destroy(AB) + evo.batch_destroy(CD) + evo.batch_destroy(CE) + end) + +print '----------------------------------------' + +basics.describe_bench(string.format('Tiny Packed Iteration: %d entities', N), + function(world) + world:update(0.016) + end, function() + local world = tiny.world() + + for i = 1, N do + world:addEntity({ a = i, b = i, c = i, d = i, e = i }) + 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 + + world:addSystem(A) + world:addSystem(B) + world:addSystem(C) + world:addSystem(D) + world:addSystem(E) + + world:refresh() + + return world + end) + +basics.describe_bench(string.format('Evolved Packed Iteration: %d entities', N), + ---@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 + function(a, b, c, d, e, A, B, C, D, E) + for chunk, entities in evo.execute(A) do + local as = evo.select(chunk, a) + for i = 1, #entities do + as[i] = as[i] * 2 + end + end + + for chunk, entities in evo.execute(B) do + local bs = evo.select(chunk, b) + for i = 1, #entities do + bs[i] = bs[i] * 2 + end + end + + for chunk, entities in evo.execute(C) do + local cs = evo.select(chunk, c) + for i = 1, #entities do + cs[i] = cs[i] * 2 + end + end + + for chunk, entities in evo.execute(D) do + local ds = evo.select(chunk, d) + for i = 1, #entities do + ds[i] = ds[i] * 2 + end + end + + for chunk, entities in evo.execute(E) do + local es = evo.select(chunk, e) + for i = 1, #entities do + es[i] = es[i] * 2 + end + end + end, function() + local a, b, c, d, e = evo.id(5) + + for i = 1, N do + evo.entity():set(a, i):set(b, i):set(c, i):set(d, i):set(e, i):build() + end + + local A = evo.query():include(a):build() + local B = evo.query():include(b):build() + local C = evo.query():include(c):build() + local D = evo.query():include(d):build() + local E = evo.query():include(e):build() + + return a, b, c, d, e, A, B, C, D, E + end, function(_, _, _, _, _, A, _, _, _, _) + evo.batch_destroy(A) + end) + +print '----------------------------------------' + +basics.describe_bench(string.format('Tiny Fragmented Iteration: %d entities', N), + function(world) + world:update(0.016) + end, function() + local world = tiny.world() + + ---@type string[] + local chars = {} + + for i = 1, 26 do + chars[i] = string.char(string.byte('a') + i - 1) + end + + for _, char in ipairs(chars) do + for i = 1, N do + world:addEntity({ [char] = i, data = i }) + end + end + + local Data = tiny.processingSystem() + Data.filter = tiny.requireAll('data') + Data.process = function(_, e) e.data = e.data * 2 end + + local Last = tiny.processingSystem() + Last.filter = tiny.requireAll('z') + Last.process = function(_, e) e.z = e.z * 2 end + + world:addSystem(Data) + world:addSystem(Last) + + world:refresh() + + return world + end) + +basics.describe_bench(string.format('Evolved Fragmented Iteration: %d entities', N), + ---@param data evolved.entity + ---@param last evolved.entity + ---@param Data evolved.query + ---@param Last evolved.query + function(data, last, Data, Last) + for chunk, entities in evo.execute(Data) do + local ds = evo.select(chunk, data) + for i = 1, #entities do + ds[i] = ds[i] * 2 + end + end + + for chunk, entities in evo.execute(Last) do + local ls = evo.select(chunk, last) + for i = 1, #entities do + ls[i] = ls[i] * 2 + end + end + end, function() + local data = evo.id() + + ---@type evolved.fragment[] + local chars = {} + + for i = 1, 26 do + chars[i] = evo.id() + end + + for _, char in ipairs(chars) do + for i = 1, N do + evo.entity():set(char, i):set(data, i):build() + end + end + + local Data = evo.query():include(data):build() + local Last = evo.query():include(chars[#chars]):build() + + return data, chars[#chars], Data, Last + end, function(_, _, Data, _) + evo.batch_destroy(Data) + end) + +print '----------------------------------------' diff --git a/evolved.lua b/evolved.lua index 6eb6a2a..c645c8d 100644 --- a/evolved.lua +++ b/evolved.lua @@ -27,7 +27,7 @@ local evolved = { ]] } ----@alias evolved.id integer +---@class evolved.id ---@alias evolved.entity evolved.id ---@alias evolved.fragment evolved.id @@ -43,6 +43,8 @@ local evolved = { ---@field package __fragment evolved.fragment ---@field package __fragment_set table ---@field package __fragment_list evolved.fragment[] +---@field package __fragment_count integer +---@field package __component_count integer ---@field package __component_indices table ---@field package __component_storages evolved.component_storage[] ---@field package __component_fragments evolved.fragment[] @@ -73,7 +75,7 @@ local evolved = { --- --- -local __freelist_ids = {} ---@type evolved.id[] +local __freelist_ids = {} ---@type integer[] local __available_idx = 0 ---@type integer local __defer_depth = 0 ---@type integer @@ -161,44 +163,56 @@ end)() ---@return evolved.id ---@nodiscard local function __acquire_id() - if __available_idx ~= 0 then - local index = __available_idx - local freelist_id = __freelist_ids[index] - __available_idx = freelist_id % 0x100000 - local version = freelist_id - __available_idx + local freelist_ids = __freelist_ids + local available_idx = __available_idx - local acquired_id = index + version - __freelist_ids[index] = acquired_id - return acquired_id + if available_idx ~= 0 then + local acquired_index = available_idx + local freelist_id = freelist_ids[acquired_index] + + local next_available_idx = freelist_id % 0x100000 + local shifted_version = freelist_id - next_available_idx + + __available_idx = next_available_idx + + local acquired_id = acquired_index + shifted_version + freelist_ids[acquired_index] = acquired_id + + return acquired_id --[[@as evolved.id]] else - if #__freelist_ids == 0xFFFFF then + local freelist_size = #freelist_ids + + if freelist_size == 0xFFFFF then error('id index overflow', 2) end - local index = #__freelist_ids + 1 - local version = 0x100000 + local acquired_index = freelist_size + 1 + local shifted_version = 0x100000 - local acquired_id = index + version - __freelist_ids[index] = acquired_id - return acquired_id + local acquired_id = acquired_index + shifted_version + freelist_ids[acquired_index] = acquired_id + + return acquired_id --[[@as evolved.id]] end end ---@param id evolved.id local function __release_id(id) - local index = id % 0x100000 - local version = id - index + local acquired_index = id % 0x100000 + local shifted_version = id - acquired_index - if __freelist_ids[index] ~= id then + local freelist_ids = __freelist_ids + + if freelist_ids[acquired_index] ~= id then error('id is not acquired or already released', 2) end - version = version == 0x7FF00000 + shifted_version = shifted_version == 0x7FF00000 and 0x100000 - or version + 0x100000 + or shifted_version + 0x100000 - __freelist_ids[index] = __available_idx + version - __available_idx = index + freelist_ids[acquired_index] = __available_idx + shifted_version + __available_idx = acquired_index end --- @@ -288,10 +302,11 @@ local function __each_iterator(each_state) end local entity_chunk_fragment_list = entity_chunk.__fragment_list + local entity_chunk_fragment_count = entity_chunk.__fragment_count local entity_chunk_component_indices = entity_chunk.__component_indices local entity_chunk_component_storages = entity_chunk.__component_storages - if fragment_index <= #entity_chunk_fragment_list then + if fragment_index <= entity_chunk_fragment_count then each_state[4] = fragment_index + 1 local fragment = entity_chunk_fragment_list[fragment_index] local component_index = entity_chunk_component_indices[fragment] @@ -496,6 +511,8 @@ local function __root_chunk(root_fragment) __fragment = root_fragment, __fragment_set = root_fragment_set, __fragment_list = root_fragment_list, + __fragment_count = 0, + __component_count = 0, __component_indices = root_component_indices, __component_storages = root_component_storages, __component_fragments = root_component_fragments, @@ -520,6 +537,11 @@ local function __root_chunk(root_fragment) end end + do + root_chunk.__fragment_count = #root_chunk.__fragment_list + root_chunk.__component_count = #root_chunk.__component_fragments + end + do __root_chunks[root_fragment] = root_chunk end @@ -591,6 +613,8 @@ local function __chunk_with_fragment(parent_chunk, child_fragment) __fragment = child_fragment, __fragment_set = child_fragment_set, __fragment_list = child_fragment_list, + __fragment_count = 0, + __component_count = 0, __component_indices = child_component_indices, __component_storages = child_component_storages, __component_fragments = child_component_fragments, @@ -638,6 +662,11 @@ local function __chunk_with_fragment(parent_chunk, child_fragment) child_chunk.__without_fragment_edges[child_fragment] = parent_chunk end + do + child_chunk.__fragment_count = #child_chunk.__fragment_list + child_chunk.__component_count = #child_chunk.__component_fragments + end + do local fragment_chunks = __major_chunks[child_fragment] @@ -1035,6 +1064,7 @@ local function __chunk_insert(chunk, fragment, ...) local old_entities = old_chunk.__entities local old_size = #old_entities + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments @@ -1049,7 +1079,7 @@ local function __chunk_insert(chunk, fragment, ...) old_entities, 1, old_size, new_size + 1, new_entities) - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -1115,17 +1145,20 @@ local function __chunk_insert(chunk, fragment, ...) end end + local entity_chunks = __entity_chunks + local entity_places = __entity_places + for new_place = new_size + 1, new_size + old_size do local entity = new_entities[new_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place end do old_chunk.__entities = {} - for i = 1, #old_component_storages do + for i = 1, old_component_count do old_component_storages[i] = {} end end @@ -1160,6 +1193,7 @@ local function __chunk_remove(chunk, ...) local old_size = #old_entities local old_fragment_set = old_chunk.__fragment_set + local old_component_count = old_chunk.__component_count local old_component_indices = chunk.__component_indices local old_component_storages = chunk.__component_storages @@ -1194,10 +1228,14 @@ local function __chunk_remove(chunk, ...) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, removed_set) end + local entity_chunks = __entity_chunks + local entity_places = __entity_places + if new_chunk then local new_entities = new_chunk.__entities local new_size = #new_entities + local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments @@ -1205,7 +1243,7 @@ local function __chunk_remove(chunk, ...) old_entities, 1, old_size, new_size + 1, new_entities) - for i = 1, #new_component_fragments do + for i = 1, new_component_count do local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] @@ -1218,22 +1256,22 @@ local function __chunk_remove(chunk, ...) for new_place = new_size + 1, new_size + old_size do local entity = new_entities[new_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place end else for old_place = 1, old_size do local entity = old_entities[old_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = nil - __entity_places[entity_index] = nil + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil end end do old_chunk.__entities = {} - for i = 1, #old_component_storages do + for i = 1, old_component_count do old_component_storages[i] = {} end end @@ -1253,11 +1291,15 @@ local function __chunk_clear(chunk) local chunk_entities = chunk.__entities local chunk_size = #chunk_entities + local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages if chunk.__has_remove_hooks then - for _, fragment in ipairs(chunk.__fragment_list) do + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + for i = 1, chunk_fragment_count do + local fragment = chunk_fragment_list[i] if __fragment_has_remove_hook(fragment) then local component_index = chunk_component_indices[fragment] if component_index then @@ -1277,17 +1319,20 @@ local function __chunk_clear(chunk) end end + local entity_chunks = __entity_chunks + local entity_places = __entity_places + for place = 1, chunk_size do local entity = chunk_entities[place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = nil - __entity_places[entity_index] = nil + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil end do chunk.__entities = {} - for i = 1, #chunk_component_storages do + for i = 1, chunk_component_count do chunk_component_storages[i] = {} end end @@ -1307,11 +1352,15 @@ local function __chunk_destroy(chunk) local chunk_entities = chunk.__entities local chunk_size = #chunk_entities + local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages if chunk.__has_remove_hooks then - for _, fragment in ipairs(chunk.__fragment_list) do + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + for i = 1, chunk_fragment_count do + local fragment = chunk_fragment_list[i] if __fragment_has_remove_hook(fragment) then local component_index = chunk_component_indices[fragment] if component_index then @@ -1331,18 +1380,21 @@ local function __chunk_destroy(chunk) end end + local entity_chunks = __entity_chunks + local entity_places = __entity_places + for place = 1, chunk_size do local entity = chunk_entities[place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = nil - __entity_places[entity_index] = nil + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil __release_id(entity) end do chunk.__entities = {} - for i = 1, #chunk_component_storages do + for i = 1, chunk_component_count do chunk_component_storages[i] = {} end end @@ -1377,6 +1429,7 @@ local function __chunk_multi_set(chunk, fragments, components) local old_size = #old_entities local old_fragment_set = old_chunk.__fragment_set + local old_component_count = old_chunk.__component_count local old_component_indices = old_chunk.__component_indices local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments @@ -1471,7 +1524,7 @@ local function __chunk_multi_set(chunk, fragments, components) old_entities, 1, old_size, new_size + 1, new_entities) - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -1640,17 +1693,20 @@ local function __chunk_multi_set(chunk, fragments, components) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, inserted_set) + local entity_chunks = __entity_chunks + local entity_places = __entity_places + for new_place = new_size + 1, new_size + old_size do local entity = new_entities[new_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place end do old_chunk.__entities = {} - for i = 1, #old_component_storages do + for i = 1, old_component_count do old_component_storages[i] = {} end end @@ -1796,6 +1852,7 @@ local function __chunk_multi_insert(chunk, fragments, components) local old_size = #old_entities local old_fragment_set = old_chunk.__fragment_set + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments @@ -1810,7 +1867,7 @@ local function __chunk_multi_insert(chunk, fragments, components) old_entities, 1, old_size, new_size + 1, new_entities) - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -1904,17 +1961,20 @@ local function __chunk_multi_insert(chunk, fragments, components) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, inserted_set) + local entity_chunks = __entity_chunks + local entity_places = __entity_places + for new_place = new_size + 1, new_size + old_size do local entity = new_entities[new_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place end do old_chunk.__entities = {} - for i = 1, #old_component_storages do + for i = 1, old_component_count do old_component_storages[i] = {} end end @@ -1948,6 +2008,7 @@ local function __chunk_multi_remove(chunk, fragments) local old_size = #old_entities local old_fragment_set = old_chunk.__fragment_set + local old_component_count = old_chunk.__component_count local old_component_indices = old_chunk.__component_indices local old_component_storages = old_chunk.__component_storages @@ -1982,10 +2043,14 @@ local function __chunk_multi_remove(chunk, fragments) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, removed_set) end + local entity_chunks = __entity_chunks + local entity_places = __entity_places + if new_chunk then local new_entities = new_chunk.__entities local new_size = #new_entities + local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments @@ -1993,7 +2058,7 @@ local function __chunk_multi_remove(chunk, fragments) old_entities, 1, old_size, new_size + 1, new_entities) - for i = 1, #new_component_fragments do + for i = 1, new_component_count do local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] @@ -2006,22 +2071,22 @@ local function __chunk_multi_remove(chunk, fragments) for new_place = new_size + 1, new_size + old_size do local entity = new_entities[new_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place end else for old_place = 1, old_size do local entity = old_entities[old_place] local entity_index = entity % 0x100000 - __entity_chunks[entity_index] = nil - __entity_places[entity_index] = nil + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil end end do old_chunk.__entities = {} - for i = 1, #old_component_storages do + for i = 1, old_component_count do old_component_storages[i] = {} end end @@ -2036,49 +2101,37 @@ end --- --- ----@param entity evolved.entity -local function __detach_entity(entity) - local entity_index = entity % 0x100000 +---@param chunk evolved.chunk +---@param place integer +local function __detach_entity(chunk, place) + local chunk_entities = chunk.__entities + local chunk_size = #chunk_entities - local old_chunk = __entity_chunks[entity_index] + local chunk_component_count = chunk.__component_count + local chunk_component_storages = chunk.__component_storages - if not old_chunk then - return - end + if place == chunk_size then + chunk_entities[place] = nil - local old_entities = old_chunk.__entities - local old_component_storages = old_chunk.__component_storages - - local old_place = __entity_places[entity_index] - local old_size = #old_entities - - if old_place == old_size then - old_entities[old_place] = nil - - for i = 1, #old_component_storages do - local old_cs = old_component_storages[i] - old_cs[old_place] = nil + for i = 1, chunk_component_count do + local component_storage = chunk_component_storages[i] + component_storage[place] = nil end else - local last_entity = old_entities[old_size] + local last_entity = chunk_entities[chunk_size] local last_entity_index = last_entity % 0x100000 - __entity_places[last_entity_index] = old_place + __entity_places[last_entity_index] = place - old_entities[old_place] = last_entity - old_entities[old_size] = nil + chunk_entities[place] = last_entity + chunk_entities[chunk_size] = nil - for i = 1, #old_component_storages do - local old_cs = old_component_storages[i] - local last_component = old_cs[old_size] - old_cs[old_place] = last_component - old_cs[old_size] = nil + for i = 1, chunk_component_count do + local component_storage = chunk_component_storages[i] + local last_component = component_storage[chunk_size] + component_storage[place] = last_component + component_storage[chunk_size] = nil end end - - __entity_chunks[entity_index] = nil - __entity_places[entity_index] = nil - - __structural_changes = __structural_changes + 1 end ---@param entity evolved.entity @@ -2091,44 +2144,47 @@ local function __spawn_entity_at(entity, chunk, fragments, components) end local chunk_entities = chunk.__entities - local chunk_fragment_list = chunk.__fragment_list + local chunk_component_count = chunk.__component_count local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages + local chunk_component_fragments = chunk.__component_fragments + local chunk_has_defaults_or_constructs = chunk.__has_defaults_or_constructs + local chunk_has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks local place = #chunk_entities + 1 chunk_entities[place] = entity - for i = 1, #chunk_fragment_list do - local fragment = chunk_fragment_list[i] - local component_index = chunk_component_indices[fragment] + if chunk_has_defaults_or_constructs then + for i = 1, chunk_component_count do + local fragment = chunk_component_fragments[i] + local component_storage = chunk_component_storages[i] - if component_index then - local component_storage = chunk_component_storages[component_index] + local new_component = evolved.get(fragment, evolved.DEFAULT) - if chunk.__has_defaults_or_constructs then - local new_component = evolved.get(fragment, evolved.DEFAULT) - - if new_component == nil then - new_component = true - end - - component_storage[place] = new_component - else - local new_component = true - - component_storage[place] = new_component + if new_component == nil then + new_component = true end + + component_storage[place] = new_component + end + else + for i = 1, chunk_component_count do + local component_storage = chunk_component_storages[i] + + local new_component = true + + component_storage[place] = new_component end end - for i = 1, #fragments do - local fragment = fragments[i] - local component_index = chunk_component_indices[fragment] + if chunk_has_defaults_or_constructs then + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] - if component_index then - local component_storage = chunk_component_storages[component_index] + if component_index then + local component_storage = chunk_component_storages[component_index] - if chunk.__has_defaults_or_constructs then local new_component = components[i] if new_component == nil then @@ -2140,7 +2196,16 @@ local function __spawn_entity_at(entity, chunk, fragments, components) end component_storage[place] = new_component - else + end + end + else + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + local new_component = components[i] if new_component == nil then @@ -2152,8 +2217,10 @@ local function __spawn_entity_at(entity, chunk, fragments, components) end end - if chunk.__has_set_or_insert_hooks then - for i = 1, #chunk_fragment_list do + if chunk_has_set_or_insert_hooks then + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + for i = 1, chunk_fragment_count do local fragment = chunk_fragment_list[i] local component_index = chunk_component_indices[fragment] @@ -2170,6 +2237,7 @@ local function __spawn_entity_at(entity, chunk, fragments, components) end local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk __entity_places[entity_index] = place @@ -2186,21 +2254,22 @@ local function __spawn_entity_with(entity, chunk, fragments, components) end local chunk_entities = chunk.__entities - local chunk_fragment_list = chunk.__fragment_list local chunk_component_indices = chunk.__component_indices local chunk_component_storages = chunk.__component_storages + local chunk_has_defaults_or_constructs = chunk.__has_defaults_or_constructs + local chunk_has_set_or_insert_hooks = chunk.__has_set_or_insert_hooks local place = #chunk_entities + 1 chunk_entities[place] = entity - for i = 1, #fragments do - local fragment = fragments[i] - local component_index = chunk_component_indices[fragment] + if chunk_has_defaults_or_constructs then + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] - if component_index then - local component_storage = chunk_component_storages[component_index] + if component_index then + local component_storage = chunk_component_storages[component_index] - if chunk.__has_defaults_or_constructs then local new_component = components[i] if new_component == nil then @@ -2212,7 +2281,16 @@ local function __spawn_entity_with(entity, chunk, fragments, components) end component_storage[place] = new_component - else + end + end + else + for i = 1, #fragments do + local fragment = fragments[i] + local component_index = chunk_component_indices[fragment] + + if component_index then + local component_storage = chunk_component_storages[component_index] + local new_component = components[i] if new_component == nil then @@ -2224,8 +2302,10 @@ local function __spawn_entity_with(entity, chunk, fragments, components) end end - if chunk.__has_set_or_insert_hooks then - for i = 1, #chunk_fragment_list do + if chunk_has_set_or_insert_hooks then + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + for i = 1, chunk_fragment_count do local fragment = chunk_fragment_list[i] local component_index = chunk_component_indices[fragment] @@ -2242,6 +2322,7 @@ local function __spawn_entity_with(entity, chunk, fragments, components) end local entity_index = entity % 0x100000 + __entity_chunks[entity_index] = chunk __entity_places[entity_index] = place @@ -2339,8 +2420,28 @@ local function __defer_set(entity, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2350,7 +2451,26 @@ __defer_ops[__defer_op.set] = function(bytes, index) local entity = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.set(entity, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local set = evolved.set + + if argument_count == 0 then + set(entity, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + set(entity, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + set(entity, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + set(entity, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + set(entity, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2368,8 +2488,28 @@ local function __defer_assign(entity, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2379,7 +2519,26 @@ __defer_ops[__defer_op.assign] = function(bytes, index) local entity = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.assign(entity, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local assign = evolved.assign + + if argument_count == 0 then + assign(entity, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + assign(entity, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + assign(entity, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + assign(entity, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + assign(entity, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2397,8 +2556,28 @@ local function __defer_insert(entity, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2408,7 +2587,26 @@ __defer_ops[__defer_op.insert] = function(bytes, index) local entity = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.insert(entity, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local insert = evolved.insert + + if argument_count == 0 then + insert(entity, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + insert(entity, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + insert(entity, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + insert(entity, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + insert(entity, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2425,8 +2623,28 @@ local function __defer_remove(entity, ...) bytecode[length + 2] = entity bytecode[length + 3] = fragment_count - for i = 1, fragment_count do - bytecode[length + 3 + i] = select(i, ...) + if fragment_count == 0 then + -- nothing + elseif fragment_count == 1 then + local f1 = select(1, ...) + bytecode[length + 4] = f1 + elseif fragment_count == 2 then + local f1, f2 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + elseif fragment_count == 3 then + local f1, f2, f3 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + bytecode[length + 6] = f3 + else + local f1, f2, f3 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + bytecode[length + 6] = f3 + for i = 4, fragment_count do + bytecode[length + 3 + i] = select(i, ...) + end end __defer_length = length + 3 + fragment_count @@ -2435,7 +2653,26 @@ end __defer_ops[__defer_op.remove] = function(bytes, index) local entity = bytes[index + 0] local fragment_count = bytes[index + 1] - evolved.remove(entity, __table_unpack(bytes, index + 2, index + 1 + fragment_count)) + + local remove = evolved.remove + + if fragment_count == 0 then + -- nothing + elseif fragment_count == 1 then + local f1 = bytes[index + 2] + remove(entity, f1) + elseif fragment_count == 2 then + local f1, f2 = bytes[index + 2], bytes[index + 3] + remove(entity, f1, f2) + elseif fragment_count == 3 then + local f1, f2, f3 = bytes[index + 2], bytes[index + 3], bytes[index + 4] + remove(entity, f1, f2, f3) + else + local f1, f2, f3 = bytes[index + 2], bytes[index + 3], bytes[index + 4] + remove(entity, f1, f2, f3, + __table_unpack(bytes, index + 5, index + 1 + fragment_count)) + end + return 2 + fragment_count end @@ -2611,8 +2848,28 @@ local function __defer_batch_set(query, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2622,7 +2879,26 @@ __defer_ops[__defer_op.batch_set] = function(bytes, index) local query = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.batch_set(query, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local batch_set = evolved.batch_set + + if argument_count == 0 then + batch_set(query, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + batch_set(query, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + batch_set(query, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_set(query, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_set(query, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2640,8 +2916,28 @@ local function __defer_batch_assign(query, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2651,7 +2947,26 @@ __defer_ops[__defer_op.batch_assign] = function(bytes, index) local query = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.batch_assign(query, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local batch_assign = evolved.batch_assign + + if argument_count == 0 then + batch_assign(query, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + batch_assign(query, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + batch_assign(query, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_assign(query, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_assign(query, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2669,8 +2984,28 @@ local function __defer_batch_insert(query, fragment, ...) bytecode[length + 3] = fragment bytecode[length + 4] = argument_count - for i = 1, argument_count do - bytecode[length + 4 + i] = select(i, ...) + if argument_count == 0 then + -- nothing + elseif argument_count == 1 then + local a1 = select(1, ...) + bytecode[length + 5] = a1 + elseif argument_count == 2 then + local a1, a2 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + elseif argument_count == 3 then + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + else + local a1, a2, a3 = select(1, ...) + bytecode[length + 5] = a1 + bytecode[length + 6] = a2 + bytecode[length + 7] = a3 + for i = 4, argument_count do + bytecode[length + 4 + i] = select(i, ...) + end end __defer_length = length + 4 + argument_count @@ -2680,7 +3015,26 @@ __defer_ops[__defer_op.batch_insert] = function(bytes, index) local query = bytes[index + 0] local fragment = bytes[index + 1] local argument_count = bytes[index + 2] - evolved.batch_insert(query, fragment, __table_unpack(bytes, index + 3, index + 2 + argument_count)) + + local batch_insert = evolved.batch_insert + + if argument_count == 0 then + batch_insert(query, fragment) + elseif argument_count == 1 then + local a1 = bytes[index + 3] + batch_insert(query, fragment, a1) + elseif argument_count == 2 then + local a1, a2 = bytes[index + 3], bytes[index + 4] + batch_insert(query, fragment, a1, a2) + elseif argument_count == 3 then + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_insert(query, fragment, a1, a2, a3) + else + local a1, a2, a3 = bytes[index + 3], bytes[index + 4], bytes[index + 5] + batch_insert(query, fragment, a1, a2, a3, + __table_unpack(bytes, index + 6, index + 2 + argument_count)) + end + return 3 + argument_count end @@ -2697,8 +3051,28 @@ local function __defer_batch_remove(query, ...) bytecode[length + 2] = query bytecode[length + 3] = fragment_count - for i = 1, fragment_count do - bytecode[length + 3 + i] = select(i, ...) + if fragment_count == 0 then + -- nothing + elseif fragment_count == 1 then + local f1 = select(1, ...) + bytecode[length + 4] = f1 + elseif fragment_count == 2 then + local f1, f2 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + elseif fragment_count == 3 then + local f1, f2, f3 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + bytecode[length + 6] = f3 + else + local f1, f2, f3 = select(1, ...) + bytecode[length + 4] = f1 + bytecode[length + 5] = f2 + bytecode[length + 6] = f3 + for i = 4, fragment_count do + bytecode[length + 3 + i] = select(i, ...) + end end __defer_length = length + 3 + fragment_count @@ -2707,7 +3081,26 @@ end __defer_ops[__defer_op.batch_remove] = function(bytes, index) local query = bytes[index + 0] local fragment_count = bytes[index + 1] - evolved.batch_remove(query, __table_unpack(bytes, index + 2, index + 1 + fragment_count)) + + local batch_remove = evolved.batch_remove + + if fragment_count == 0 then + -- nothing + elseif fragment_count == 1 then + local f1 = bytes[index + 2] + batch_remove(query, f1) + elseif fragment_count == 2 then + local f1, f2 = bytes[index + 2], bytes[index + 3] + batch_remove(query, f1, f2) + elseif fragment_count == 3 then + local f1, f2, f3 = bytes[index + 2], bytes[index + 3], bytes[index + 4] + batch_remove(query, f1, f2, f3) + else + local f1, f2, f3 = bytes[index + 2], bytes[index + 3], bytes[index + 4] + batch_remove(query, f1, f2, f3, + __table_unpack(bytes, index + 5, index + 1 + fragment_count)) + end + return 2 + fragment_count end @@ -2996,7 +3389,8 @@ function evolved.pack(index, version) error('id version out of range [1;0x7FF]', 2) end - return index + version * 0x100000 + local shifted_version = version * 0x100000 + return index + shifted_version --[[@as evolved.id]] end ---@param id evolved.id @@ -3139,8 +3533,11 @@ function evolved.set(entity, fragment, ...) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) @@ -3194,10 +3591,11 @@ function evolved.set(entity, fragment, ...) new_entities[new_place] = entity if old_chunk then + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -3207,7 +3605,7 @@ function evolved.set(entity, fragment, ...) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) end do @@ -3244,8 +3642,8 @@ function evolved.set(entity, fragment, ...) end end - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -3342,8 +3740,11 @@ function evolved.insert(entity, fragment, ...) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_with_fragment(old_chunk, fragment) @@ -3362,10 +3763,11 @@ function evolved.insert(entity, fragment, ...) new_entities[new_place] = entity if old_chunk then + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -3375,7 +3777,7 @@ function evolved.insert(entity, fragment, ...) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) end do @@ -3412,8 +3814,8 @@ function evolved.insert(entity, fragment, ...) end end - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -3438,8 +3840,11 @@ function evolved.remove(entity, ...) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_without_fragments(old_chunk, ...) @@ -3472,13 +3877,14 @@ function evolved.remove(entity, ...) if new_chunk then local new_entities = new_chunk.__entities + local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments local new_place = #new_entities + 1 new_entities[new_place] = entity - for i = 1, #new_component_fragments do + for i = 1, new_component_count do local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] @@ -3488,12 +3894,15 @@ function evolved.remove(entity, ...) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place else - __detach_entity(entity) + __detach_entity(old_chunk, old_place) + + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil end __structural_changes = __structural_changes + 1 @@ -3518,8 +3927,11 @@ function evolved.clear(entity) return false, false end - local chunk = __entity_chunks[entity_index] - local place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local chunk = entity_chunks[entity_index] + local place = entity_places[entity_index] if not chunk then return true, false @@ -3546,7 +3958,10 @@ function evolved.clear(entity) end end - __detach_entity(entity) + __detach_entity(chunk, place) + + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil __structural_changes = __structural_changes + 1 end @@ -3570,8 +3985,11 @@ function evolved.destroy(entity) return true, false end - local chunk = __entity_chunks[entity_index] - local place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local chunk = entity_chunks[entity_index] + local place = entity_places[entity_index] if not chunk then __release_id(entity) @@ -3599,9 +4017,12 @@ function evolved.destroy(entity) end end - __detach_entity(entity) + __detach_entity(chunk, place) __release_id(entity) + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil + __structural_changes = __structural_changes + 1 end @@ -3636,8 +4057,11 @@ function evolved.multi_set(entity, fragments, components) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_with_fragment_list(old_chunk, fragments) @@ -3711,10 +4135,11 @@ function evolved.multi_set(entity, fragments, components) new_entities[new_place] = entity if old_chunk then + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -3724,7 +4149,7 @@ function evolved.multi_set(entity, fragments, components) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) end local inserted_set = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_SET, 0, fragment_count) @@ -3822,8 +4247,8 @@ function evolved.multi_set(entity, fragments, components) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, inserted_set) - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -3954,8 +4379,11 @@ function evolved.multi_insert(entity, fragments, components) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_with_fragment_list(old_chunk, fragments) @@ -3976,10 +4404,11 @@ function evolved.multi_insert(entity, fragments, components) new_entities[new_place] = entity if old_chunk then + local old_component_count = old_chunk.__component_count local old_component_storages = old_chunk.__component_storages local old_component_fragments = old_chunk.__component_fragments - for i = 1, #old_component_fragments do + for i = 1, old_component_count do local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] @@ -3989,7 +4418,7 @@ function evolved.multi_insert(entity, fragments, components) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) end local inserted_set = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_SET, 0, fragment_count) @@ -4043,8 +4472,8 @@ function evolved.multi_insert(entity, fragments, components) __release_table(__TABLE_POOL_TAG__FRAGMENT_SET, inserted_set) - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end @@ -4075,8 +4504,11 @@ function evolved.multi_remove(entity, fragments) return false, false end - local old_chunk = __entity_chunks[entity_index] - local old_place = __entity_places[entity_index] + local entity_chunks = __entity_chunks + local entity_places = __entity_places + + local old_chunk = entity_chunks[entity_index] + local old_place = entity_places[entity_index] local new_chunk = __chunk_without_fragment_list(old_chunk, fragments) @@ -4114,13 +4546,14 @@ function evolved.multi_remove(entity, fragments) if new_chunk then local new_entities = new_chunk.__entities + local new_component_count = new_chunk.__component_count local new_component_storages = new_chunk.__component_storages local new_component_fragments = new_chunk.__component_fragments local new_place = #new_entities + 1 new_entities[new_place] = entity - for i = 1, #new_component_fragments do + for i = 1, new_component_count do local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] @@ -4130,12 +4563,15 @@ function evolved.multi_remove(entity, fragments) end end - __detach_entity(entity) + __detach_entity(old_chunk, old_place) - __entity_chunks[entity_index] = new_chunk - __entity_places[entity_index] = new_place + entity_chunks[entity_index] = new_chunk + entity_places[entity_index] = new_place else - __detach_entity(entity) + __detach_entity(old_chunk, old_place) + + entity_chunks[entity_index] = nil + entity_places[entity_index] = nil end __structural_changes = __structural_changes + 1