From f570a4a9662437a731a7e5272fa3bd7020ef0e04 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Wed, 5 Mar 2025 00:23:19 -0500 Subject: [PATCH] Replace tiny-ecs with the version I've been using + my own tweaks --- lib/tiny.lua | 96 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/lib/tiny.lua b/lib/tiny.lua index a166999..9abd47c 100644 --- a/lib/tiny.lua +++ b/lib/tiny.lua @@ -19,6 +19,16 @@ 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. ]] +---@class System +---@field world World field points to the World that the System belongs to. Useful for adding and removing Entities from the world dynamically via the System. +---@field active boolean flag for 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. +---@field entities table[] is an ordered list of Entities in the System. This list can be used to quickly iterate through all Entities in a System. +---@field interval number 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. +---@field index number 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). +---@field indices table field is a table of Entity keys to their indices in the entities list. Most Systems can ignore this. +---@field modified boolean indicator for 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. + + --- @module tiny-ecs -- @author Calvin Rose -- @license MIT @@ -91,40 +101,49 @@ 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(invert, joining_op, ...) + local _args = {...} - 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.' + return function(system, e) + local acc + local args = _args + if joining_op == 'or' then + acc = false + for i = 1, #args do + local v = args[i] + if type(v) == "string" then + acc = acc or (e[v] ~= nil) + elseif type(v) == "function" then + acc = acc or v(system, e) + else + error 'Filter token must be a string or a filter function.' + end + end + else + acc = true + for i = 1, #args do + local v = args[i] + if type(v) == "string" then + acc = acc and (e[v] ~= nil) + elseif type(v) == "function" then + acc = acc and v(system, e) + else + error 'Filter token must be a string or a filter function.' + end 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(...) + + -- computes a simple xor + if invert then + return not acc + else + return acc + end end +end + +do function filterJoin(...) local state, value = pcall(filterJoinRaw, ...) @@ -169,25 +188,25 @@ end --- Makes a Filter that selects Entities with all specified Components and -- Filters. function tiny.requireAll(...) - return filterJoin('', ' and ', ...) + return filterJoin(false, 'and', ...) end --- Makes a Filter that selects Entities with at least one of the specified -- Components and Filters. function tiny.requireAny(...) - return filterJoin('', ' or ', ...) + return filterJoin(false, '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 ', ...) + return filterJoin(true, '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 ', ...) + return filterJoin(true, 'or', ...) end --- Makes a Filter from a string. Syntax of `pattern` is as follows. @@ -303,11 +322,12 @@ local function processingSystemUpdate(system, dt) local process = system.process local postProcess = system.postProcess + local shouldSkipSystemProcess = false if preProcess then - preProcess(system, dt) + shouldSkipSystemProcess = preProcess(system, dt) end - if process then + if process and not shouldSkipSystemProcess then if system.nocache then local entities = system.world.entities local filter = system.filter @@ -327,7 +347,7 @@ local function processingSystemUpdate(system, dt) end end - if postProcess then + if postProcess and not shouldSkipSystemProcess then postProcess(system, dt) end end @@ -420,6 +440,7 @@ local worldMetaTable --- Creates a new World. -- Can optionally add default Systems and Entities. Returns the new World along -- with default Entities and Systems. +---@return World function tiny.world(...) local ret = setmetatable({ @@ -861,4 +882,5 @@ worldMetaTable = { end } -return tiny +_G.tiny = tiny +return tiny \ No newline at end of file