From 556a546fb299493d1203450a7618dcebaed6c500 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 30 Nov 2024 01:19:58 +0700 Subject: [PATCH] tiny-ecs benches --- develop/3rdparty/tiny.lua | 863 ++++++++++++++++++++++++++++ develop/unbench.lua | 238 +------- develop/unbench/common_unbench.lua | 40 ++ develop/unbench/evolved_unbench.lua | 178 ++++++ develop/unbench/tiny_unbench.lua | 154 +++++ 5 files changed, 1237 insertions(+), 236 deletions(-) create mode 100644 develop/3rdparty/tiny.lua create mode 100644 develop/unbench/common_unbench.lua create mode 100644 develop/unbench/evolved_unbench.lua create mode 100644 develop/unbench/tiny_unbench.lua diff --git a/develop/3rdparty/tiny.lua b/develop/3rdparty/tiny.lua new file mode 100644 index 0000000..e2cd869 --- /dev/null +++ b/develop/3rdparty/tiny.lua @@ -0,0 +1,863 @@ +--[[ +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/unbench.lua b/develop/unbench.lua index 9074322..e885990 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -1,236 +1,2 @@ -local evo = require 'evolved.evolved' - ----@param name string ----@param loop fun(...): ... ----@param init fun(): ... -local function describe(name, loop, init) - collectgarbage('stop') - - print(string.format('| %s ... |', name)) - - local state = evo.compat.pack(init()) - - local iters = 0 - local start_s = os.clock() - local start_kb = collectgarbage('count') - - local success, result = pcall(function() - while os.clock() - start_s < 1.0 do - iters = iters + 1 - loop(evo.compat.unpack(state)) - end - end) - - local finish_s = os.clock() - local finish_kb = collectgarbage('count') - - print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f', - success and 'OK' or 'FAILED', - (finish_s - start_s) * 1000 * 1000 / iters, - iters / (finish_s - start_s), - (finish_kb - start_kb) / iters)) - - if not success then print(' ' .. result) end - - collectgarbage('restart') -end - ----@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 -describe('Packed Iteration', function(a, b, c, d, e, A, B, C, D, E) - for chunk in A:execute() do - local as = chunk:components(a) - for i = 1, #chunk:entities() do - as[i] = as[i] * 2 - end - end - - for chunk in B:execute() do - local bs = chunk:components(b) - for i = 1, #chunk:entities() do - bs[i] = bs[i] * 2 - end - end - - for chunk in C:execute() do - local cs = chunk:components(c) - for i = 1, #chunk:entities() do - cs[i] = cs[i] * 2 - end - end - - for chunk in D:execute() do - local ds = chunk:components(d) - for i = 1, #chunk:entities() do - ds[i] = ds[i] * 2 - end - end - - for chunk in E:execute() do - local es = chunk:components(e) - for i = 1, #chunk:entities() do - es[i] = es[i] * 2 - end - end -end, function() - local A, B, C, D, E = - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity() - - for _ = 1, 1000 do - evo.registry.entity() - :set(A, 0) - :set(B, 0) - :set(C, 0) - :set(D, 0) - :set(E, 0) - end - - return - A, B, C, D, E, - evo.registry.query(A), - evo.registry.query(B), - evo.registry.query(C), - evo.registry.query(D), - evo.registry.query(E) -end) - -describe('Simple Iteration', function(a, b, c, d, e, AB, CD, CE) - for chunk in AB:execute() do - local as, bs = chunk:components(a, b) - for i = 1, #chunk:entities() do - as[i], bs[i] = bs[i], as[i] - end - end - - for chunk in CD:execute() do - local cs, ds = chunk:components(c, d) - for i = 1, #chunk:entities() do - cs[i], ds[i] = ds[i], cs[i] - end - end - - for chunk in CE:execute() do - local cs, es = chunk:components(c, e) - for i = 1, #chunk:entities() do - cs[i], es[i] = es[i], cs[i] - end - end -end, function() - local A, B, C, D, E = - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity(), - evo.registry.entity() - - for _ = 1, 1000 do - evo.registry.entity():set(A, 0):set(B, 0) - evo.registry.entity():set(A, 0):set(B, 0):set(C, 0) - evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(D, 0) - evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(E, 0) - end - - return A, B, C, D, E, - evo.registry.query(A, B), - evo.registry.query(C, D), - evo.registry.query(C, E) -end) - ----@param d evolved.entity ----@param z evolved.entity ----@param D evolved.query ----@param Z evolved.query -describe('Fragmented Iteration', function(d, z, D, Z) - for chunk in D:execute() do - local ds = chunk:components(d) - for i = 1, #chunk:entities() do - ds[i] = ds[i] * 2 - end - end - - for chunk in Z:execute() do - local zs = chunk:components(z) - for i = 1, #chunk:entities() do - zs[i] = zs[i] * 2 - end - end -end, function() - local cs, d = {}, evo.registry.entity() - for i = 1, 26 do cs[i] = evo.registry.entity() end - - for i = 1, #cs do - for _ = 1, 100 do - evo.registry.entity():set(cs[i], 0):set(d, 0) - end - end - - return d, cs[#cs], - evo.registry.query(d), - evo.registry.query(cs[#cs]) -end) - ----@param a evolved.entity ----@param b evolved.entity ----@param A evolved.query ----@param B evolved.query -describe('Entity Cycle', function(a, b, A, B) - ---@type any[] - local to_create = {} - - for chunk in A:execute() do - local as = chunk:components(a) - for i = 1, #chunk:entities() do - to_create[#to_create + 1] = as[i] - end - end - - for i = 1, #to_create do - evo.registry.entity():set(b, to_create[i]) - end - - assert(1000 == evo.registry.batch_destroy(B)) -end, function() - local a, b = - evo.registry.entity(), - evo.registry.entity() - - for _ = 1, 1000 do - evo.registry.entity():set(a, 0) - end - - return a, b, - evo.registry.query(a), - evo.registry.query(b) -end) - ----@param b evolved.entity ----@param A evolved.query ----@param AB evolved.query -describe('Add / Remove', function(b, A, AB) - assert(1000 == evo.registry.batch_insert(A, b)) - assert(1000 == evo.registry.batch_remove(AB, b)) -end, function() - local a, b = - evo.registry.entity(), - evo.registry.entity() - - for _ = 1, 1000 do - evo.registry.entity():set(a) - end - - return b, - evo.registry.query(a), - evo.registry.query(b) -end) +require 'develop.unbench.evolved_unbench' +require 'develop.unbench.tiny_unbench' diff --git a/develop/unbench/common_unbench.lua b/develop/unbench/common_unbench.lua new file mode 100644 index 0000000..77b21a2 --- /dev/null +++ b/develop/unbench/common_unbench.lua @@ -0,0 +1,40 @@ +local evo = require 'evolved.evolved' + +local common = {} + +---@param name string +---@param loop fun(...): ... +---@param init fun(): ... +function common.describe(name, loop, init) + collectgarbage('stop') + + print(string.format('| %s ... |', name)) + + local state = evo.compat.pack(init()) + + local iters = 0 + local start_s = os.clock() + local start_kb = collectgarbage('count') + + local success, result = pcall(function() + while os.clock() - start_s < 1.0 do + iters = iters + 1 + loop(evo.compat.unpack(state)) + end + end) + + local finish_s = os.clock() + local finish_kb = collectgarbage('count') + + print(string.format(' %s | us: %.2f | op/s: %.2f | kb/i: %.2f', + success and 'OK' or 'FAILED', + (finish_s - start_s) * 1000 * 1000 / iters, + iters / (finish_s - start_s), + (finish_kb - start_kb) / iters)) + + if not success then print(' ' .. result) end + + collectgarbage('restart') +end + +return common diff --git a/develop/unbench/evolved_unbench.lua b/develop/unbench/evolved_unbench.lua new file mode 100644 index 0000000..5bba197 --- /dev/null +++ b/develop/unbench/evolved_unbench.lua @@ -0,0 +1,178 @@ +local common = require 'develop.unbench.common_unbench' + +local evo = require 'evolved.evolved' + +---@param a evolved.entity +---@param b evolved.entity +---@param c evolved.entity +---@param d evolved.entity +---@param e evolved.entity +---@param A evolved.query +---@param B evolved.query +---@param C evolved.query +---@param D evolved.query +---@param E evolved.query +common.describe('Evolved Packed Iteration', function(a, b, c, d, e, A, B, C, D, E) + for chunk in A:execute() do + local as = chunk:components(a) + for i = 1, #chunk:entities() do as[i] = as[i] * 2 end + end + + for chunk in B:execute() do + local bs = chunk:components(b) + for i = 1, #chunk:entities() do bs[i] = bs[i] * 2 end + end + + for chunk in C:execute() do + local cs = chunk:components(c) + for i = 1, #chunk:entities() do cs[i] = cs[i] * 2 end + end + + for chunk in D:execute() do + local ds = chunk:components(d) + for i = 1, #chunk:entities() do ds[i] = ds[i] * 2 end + end + + for chunk in E:execute() do + local es = chunk:components(e) + for i = 1, #chunk:entities() do es[i] = es[i] * 2 end + end +end, function() + local A, B, C, D, E = + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity() + + for _ = 1, 1000 do + evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(D, 0):set(E, 0) + end + + return + A, B, C, D, E, + evo.registry.query(A), + evo.registry.query(B), + evo.registry.query(C), + evo.registry.query(D), + evo.registry.query(E) +end) + +common.describe('Evolved Simple Iteration', function(a, b, c, d, e, AB, CD, CE) + for chunk in AB:execute() do + local as, bs = chunk:components(a, b) + for i = 1, #chunk:entities() do as[i], bs[i] = bs[i], as[i] end + end + + for chunk in CD:execute() do + local cs, ds = chunk:components(c, d) + for i = 1, #chunk:entities() do cs[i], ds[i] = ds[i], cs[i] end + end + + for chunk in CE:execute() do + local cs, es = chunk:components(c, e) + for i = 1, #chunk:entities() do cs[i], es[i] = es[i], cs[i] end + end +end, function() + local A, B, C, D, E = + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity(), + evo.registry.entity() + + for _ = 1, 1000 do + evo.registry.entity():set(A, 0):set(B, 0) + evo.registry.entity():set(A, 0):set(B, 0):set(C, 0) + evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(D, 0) + evo.registry.entity():set(A, 0):set(B, 0):set(C, 0):set(E, 0) + end + + return A, B, C, D, E, + evo.registry.query(A, B), + evo.registry.query(C, D), + evo.registry.query(C, E) +end) + +---@param d evolved.entity +---@param z evolved.entity +---@param Data evolved.query +---@param Z evolved.query +common.describe('Evolved Fragmented Iteration', function(d, z, Data, Z) + for chunk in Data:execute() do + local ds = chunk:components(d) + for i = 1, #chunk:entities() do ds[i] = ds[i] * 2 end + end + + for chunk in Z:execute() do + local zs = chunk:components(z) + for i = 1, #chunk:entities() do zs[i] = zs[i] * 2 end + end +end, function() + local chars, data = {}, evo.registry.entity() + for i = 1, 26 do chars[i] = evo.registry.entity() end + + for i = 1, #chars do + for _ = 1, 100 do + evo.registry.entity():set(chars[i], 0):set(data, 0) + end + end + + return data, chars[#chars], + evo.registry.query(data), + evo.registry.query(chars[#chars]) +end) + +---@param a evolved.entity +---@param b evolved.entity +---@param A evolved.query +---@param B evolved.query +common.describe('Evolved Entity Cycle', function(a, b, A, B) + ---@type any[] + local to_create = {} + + for chunk in A:execute() do + local as = chunk:components(a) + for i = 1, #chunk:entities() do + to_create[#to_create + 1] = as[i] + end + end + + for i = 1, #to_create do + evo.registry.entity():set(b, to_create[i]) + end + + assert(1000 == evo.registry.batch_destroy(B)) +end, function() + local a, b = + evo.registry.entity(), + evo.registry.entity() + + for _ = 1, 1000 do + evo.registry.entity():set(a, 0) + end + + return a, b, + evo.registry.query(a), + evo.registry.query(b) +end) + +---@param b evolved.entity +---@param A evolved.query +---@param AB evolved.query +common.describe('Evolved Add / Remove', function(b, A, AB) + assert(1000 == evo.registry.batch_insert(A, b)) + assert(1000 == evo.registry.batch_remove(AB, b)) +end, function() + local a, b = + evo.registry.entity(), + evo.registry.entity() + + for _ = 1, 1000 do + evo.registry.entity():set(a) + end + + return b, + evo.registry.query(a), + evo.registry.query(b) +end) diff --git a/develop/unbench/tiny_unbench.lua b/develop/unbench/tiny_unbench.lua new file mode 100644 index 0000000..d6fc8d9 --- /dev/null +++ b/develop/unbench/tiny_unbench.lua @@ -0,0 +1,154 @@ +local common = require 'develop.unbench.common_unbench' + +local tiny = require 'develop.3rdparty.tiny' + +common.describe('Tiny Packed Iteration', function(w) + tiny.update(w, 0.016) +end, function() + local w = tiny.world() + + for _ = 1, 1000 do + tiny.addEntity(w, { a = 0, b = 0, c = 0, d = 0, e = 0 }) + end + + local A = tiny.processingSystem() + A.filter = tiny.requireAll('a') + A.process = function(_, e) e.a = e.a * 2 end + + local B = tiny.processingSystem() + B.filter = tiny.requireAll('b') + B.process = function(_, e) e.b = e.b * 2 end + + local C = tiny.processingSystem() + C.filter = tiny.requireAll('c') + C.process = function(_, e) e.c = e.c * 2 end + + local D = tiny.processingSystem() + D.filter = tiny.requireAll('d') + D.process = function(_, e) e.d = e.d * 2 end + + local E = tiny.processingSystem() + E.filter = tiny.requireAll('e') + E.process = function(_, e) e.e = e.e * 2 end + + tiny.addSystem(w, A) + tiny.addSystem(w, B) + tiny.addSystem(w, C) + tiny.addSystem(w, D) + tiny.addSystem(w, E) + + return w +end) + +common.describe('Tiny Simple Iteration', function(w) + tiny.update(w, 0.016) +end, function() + local w = tiny.world() + + for _ = 1, 1000 do + tiny.addEntity(w, { a = 0, b = 0 }) + tiny.addEntity(w, { a = 0, b = 0, c = 0 }) + tiny.addEntity(w, { a = 0, b = 0, c = 0, d = 0 }) + tiny.addEntity(w, { a = 0, b = 0, c = 0, e = 0 }) + end + + local AB = tiny.processingSystem() + AB.filter = tiny.requireAll('a', 'b') + AB.process = function(_, e) e.a, e.b = e.b, e.a end + + local CD = tiny.processingSystem() + CD.filter = tiny.requireAll('c', 'd') + CD.process = function(_, e) e.c, e.d = e.d, e.c end + + local CE = tiny.processingSystem() + CE.filter = tiny.requireAll('c', 'e') + CE.process = function(_, e) e.c, e.e = e.e, e.c end + + tiny.addSystem(w, AB) + tiny.addSystem(w, CD) + tiny.addSystem(w, CE) + + return w +end) + +common.describe('Tiny Fragmented Iteration', function(w) + tiny.update(w, 0.016) +end, function() + local w = tiny.world() + + for i = 1, 26 do + for _ = 1, 100 do + local char = string.char(string.byte('a') + i - 1) + tiny.addEntity(w, { [char] = 0, data = 0 }) + end + end + + local Data = tiny.processingSystem() + Data.filter = tiny.requireAll('data') + Data.process = function(_, e) e.data = e.data * 2 end + + local Z = tiny.processingSystem() + Z.filter = tiny.requireAll('z') + Z.process = function(_, e) e.z = e.z * 2 end + + tiny.addSystem(w, Data) + tiny.addSystem(w, Z) + + return w +end) + +common.describe('Tiny Entity Cycle', function(w) + tiny.update(w, 0.016) +end, function() + local w = tiny.world() + + for _ = 1, 1000 do + tiny.addEntity(w, { a = 0 }) + end + + local A = tiny.processingSystem() + A.filter = tiny.requireAll('a') + A.process = function(_, e) tiny.addEntity(w, { b = e.a }) end + A.postProcess = function(_) tiny.refresh(w) end + + local B = tiny.processingSystem() + B.filter = tiny.requireAll('b') + B.process = function(_, e) tiny.removeEntity(w, e) end + B.postProcess = function(_) tiny.refresh(w) end + + tiny.addSystem(w, A) + tiny.addSystem(w, B) + + return w +end) + +common.describe('Tiny Add / Remove', function(w) + tiny.update(w, 0.016) +end, function() + local w = tiny.world() + + for _ = 1, 1000 do + tiny.addEntity(w, { a = 0 }) + end + + local A = tiny.processingSystem() + A.filter = tiny.requireAll('a') + A.process = function(_, e) + e.b = 0 + tiny.addEntity(w, e) + end + A.postProcess = function(_) tiny.refresh(w) end + + local AB = tiny.processingSystem() + AB.filter = tiny.requireAll('a', 'b') + AB.process = function(_, e) + e.b = nil + tiny.addEntity(w, e) + end + AB.postProcess = function(_) tiny.refresh(w) end + + tiny.addSystem(w, A) + tiny.addSystem(w, AB) + + return w +end)