diff --git a/generated/filter-types.lua b/generated/filter-types.lua index 9f7a508..8f3660e 100644 --- a/generated/filter-types.lua +++ b/generated/filter-types.lua @@ -9,9 +9,8 @@ local SOME_TABLE = {} ---@alias BitMask number ---@alias ButtonState { receivedInputThisFrame: boolean, aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean } ---@alias Collision { collisionBetween: Entity[] } ----@alias CrankState { crankChange: number, changeInLastHalfSecond: number } ---@alias Entity table ----@alias InRelations Entity[] +---@alias FontData love.FontData ---@alias XyPair { x: number, y: number } T = { @@ -22,10 +21,6 @@ T = { marker = SOME_TABLE, ---@type fun(self) SelfFunction = function() end, - ---@type pd_image - pd_image = SOME_TABLE, - ---@type pd_font - pd_font = SOME_TABLE, ---@type AnyComponent AnyComponent = SOME_TABLE, @@ -39,14 +34,11 @@ T = { ---@type Collision Collision = SOME_TABLE, - ---@type CrankState - CrankState = SOME_TABLE, - ---@type Entity Entity = SOME_TABLE, - ---@type InRelations - InRelations = SOME_TABLE, + ---@type FontData + FontData = SOME_TABLE, ---@type XyPair XyPair = SOME_TABLE, diff --git a/generated/filter-types.lua2p b/generated/filter-types.lua2p index a5c4ccd..490ff02 100644 --- a/generated/filter-types.lua2p +++ b/generated/filter-types.lua2p @@ -59,9 +59,8 @@ local SOME_TABLE = {} XyPair = "{ x: number, y: number }", Collision = "{ collisionBetween: Entity[] }", BitMask = "number", - InRelations = "Entity[]", + FontData = "love.FontData", ButtonState = "{ receivedInputThisFrame: boolean, aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean }", - CrankState = "{ crankChange: number, changeInLastHalfSecond: number }", })) T = { bool = true, @@ -70,11 +69,7 @@ T = { str = "", marker = SOME_TABLE, ---@type fun(self) - SelfFunction = function() end, - ---@type pd_image - pd_image = SOME_TABLE, - ---@type pd_font - pd_font = SOME_TABLE,!!(dumpTypeObjects()) + SelfFunction = function() end,!!(dumpTypeObjects()) } ---@generic T diff --git a/lib/inspect.lua b/lib/inspect.lua new file mode 100644 index 0000000..e6cec1c --- /dev/null +++ b/lib/inspect.lua @@ -0,0 +1,367 @@ +local _tl_compat +if (tonumber((_VERSION or ""):match("[%d.]*$")) or 0) < 5.3 then + local p, m = pcall(require, "compat53.module") + if p then + _tl_compat = m + end +end +local math = _tl_compat and _tl_compat.math or math +local string = _tl_compat and _tl_compat.string or string +local table = _tl_compat and _tl_compat.table or table +local inspect = { Options = {} } + +inspect._VERSION = "inspect.lua 3.1.0" +inspect._URL = "http://github.com/kikito/inspect.lua" +inspect._DESCRIPTION = "human-readable representations of tables" +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique GarcĂ­a Cota + + 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. +]] +inspect.KEY = setmetatable({}, { + __tostring = function() + return "inspect.KEY" + end, +}) +inspect.METATABLE = setmetatable({}, { + __tostring = function() + return "inspect.METATABLE" + end, +}) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local _rawget +if rawget then + _rawget = rawget +else + _rawget = function(t, k) + return t[k] + end +end + +local function rawpairs(t) + return next, t, nil +end + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + +local shortControlCharEscapes = { + ["\a"] = "\\a", + ["\b"] = "\\b", + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + ["\v"] = "\\v", + ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), "(%c)%f[0-9]", longControlCharEscapes), "%c", shortControlCharEscapes)) +end + +local luaKeywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, +} + +local function isIdentifier(str) + return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") and not luaKeywords[str] +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and flr(k) == k and 1 <= k and k <= sequenceLength +end + +local defaultTypeOrders = { + ["number"] = 1, + ["boolean"] = 2, + ["string"] = 3, + ["table"] = 4, + ["function"] = 5, + ["userdata"] = 6, + ["thread"] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + if ta == tb and (ta == "string" or ta == "number") then + return a < b + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do + newPath[i] = path[i] + end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + +local function processRecursive(process, item, path, visited) + if item == nil then + return nil + end + if visited[item] then + return visited[item] + end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= "table" then + mt = nil + end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + +local Inspector = {} + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == "string" then + puts(buf, smartQuote(escape(v))) + elseif tv == "number" or tv == "boolean" or tv == "nil" or tv == "cdata" or tv == "ctype" then + puts(buf, tostring(v)) + elseif tv == "table" and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, "{...}") + else + if self.cycles[t] > 1 then + puts(buf, fmt("<%d>", self:getId(t))) + end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, "{") + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then + puts(buf, ",") + end + if i <= seqLen then + puts(buf, " ") + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, " = ") + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == "table" then + if seqLen + keysLen > 0 then + puts(buf, ",") + end + tabify(self) + puts(buf, " = ") + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == "table" then + tabify(self) + elseif seqLen > 0 then + puts(buf, " ") + end + + puts(buf, "}") + end + else + puts(buf, fmt("<%s %d>", tv, self:getId(v))) + end +end + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or "\n" + local indent = options.indent or " " + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +_G.Inspect = inspect +return inspect diff --git a/lib/tiny.lua b/lib/tiny.lua index edaf1f1..71bccd4 100644 --- a/lib/tiny.lua +++ b/lib/tiny.lua @@ -19,8 +19,6 @@ 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 World - ---@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. @@ -29,12 +27,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---@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. - +---@field preProcess nil | fun(self, dt: number): nil | table +---@field postProcess nil | fun(self, dt: number) +---@field isDrawSystem nil | boolean --- @module tiny-ecs --- @author Calvin Rose --- @license MIT --- @copyright 2016 +--- @author Calvin Rose +--- @license MIT +--- @copyright 2016 local tiny = {} -- Local versions of standard lua functions @@ -103,14 +103,13 @@ local filterJoin -- A helper function to filters from string local filterBuildString - local function filterJoinRaw(invert, joining_op, ...) - local _args = {...} + local _args = { ... } return function(system, e) local acc local args = _args - if joining_op == 'or' then + if joining_op == "or" then acc = false for i = 1, #args do local v = args[i] @@ -119,7 +118,7 @@ local function filterJoinRaw(invert, joining_op, ...) elseif type(v) == "function" then acc = acc or v(system, e) else - error 'Filter token must be a string or a filter function.' + error("Filter token must be a string or a filter function.") end end else @@ -131,7 +130,7 @@ local function filterJoinRaw(invert, joining_op, ...) elseif type(v) == "function" then acc = acc and v(system, e) else - error 'Filter token must be a string or a filter function.' + error("Filter token must be a string or a filter function.") end end end @@ -146,69 +145,68 @@ local function filterJoinRaw(invert, joining_op, ...) end do - function filterJoin(...) local state, value = pcall(filterJoinRaw, ...) - if state then return value else return nil, value end + 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) + str = str:gsub("%b()", function(p) subParts[#subParts + 1] = buildPart(p:sub(2, -2)) - return ('\255%d'):format(#subParts) + return ("\255%d"):format(#subParts) end) - for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do - if part:match('^\255%d+$') then + 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]) + 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 '==') + 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 ') + 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 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(false, '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(false, '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(true, '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(true, 'or', ...) + return filterJoin(true, "or", ...) end --- Makes a Filter from a string. Syntax of `pattern` is as follows. @@ -224,7 +222,11 @@ end -- @param pattern function tiny.filter(pattern) local state, value = pcall(filterBuildString, pattern) - if state then return value else return nil, value end + if state then + return value + else + return nil, value + end end --- System functions. @@ -463,8 +465,7 @@ function tiny.world(...) entities = {}, -- List of Systems - systems = {} - + systems = {}, }, worldMetaTable) tiny_add(ret, ...) @@ -673,7 +674,6 @@ end -- Adds, removes, and changes Entities that have been marked. function tiny_manageEntities(world) - local e2r = world.entitiesToRemove local e2c = world.entitiesToChange @@ -793,7 +793,6 @@ end -- 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) @@ -809,8 +808,7 @@ function tiny.update(world, dt, filter) onModify(system, dt) end local preWrap = system.preWrap - if preWrap and - ((not filter) or filter(world, system)) then + if preWrap and ((not filter) or filter(world, system)) then preWrap(system, dt) end end @@ -853,12 +851,10 @@ function tiny.update(world, dt, filter) 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 + 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. @@ -924,11 +920,11 @@ worldMetaTable = { clearSystems = tiny.clearSystems, getEntityCount = tiny.getEntityCount, getSystemCount = tiny.getSystemCount, - setSystemIndex = tiny.setSystemIndex + setSystemIndex = tiny.setSystemIndex, }, __tostring = function() return "" - end + end, } _G.tiny = tiny diff --git a/main.lua b/main.lua index 3616e6b..8aab077 100644 --- a/main.lua +++ b/main.lua @@ -27,13 +27,40 @@ local scenarios = { end, } -scenarios.textTestScenario() +local currentScenario = scenarios.textTestScenario +local freeze = false +local delta function love.load() + currentScenario() + World:setSystemIndex(LiveForNFrames, 1) love.graphics.setBackgroundColor(1, 1, 1) love.graphics.setFont(EtBt7001Z0xa(32)) end -function love.draw() - World:update(love.timer.getDelta()) +function love.update(dt) + delta = dt + if freeze then + return + end + + if love.keyboard.isDown("r") then + World:clearEntities() + currentScenario() + freeze = false + end + + if love.keyboard.isDown("f") then + freeze = not freeze + end + + World:update(delta, function(_, system) + return not system.isDrawSystem + end) +end + +function love.draw() + World:update(delta, function(_, system) + return system.isDrawSystem + end) end diff --git a/systems/collision-detection.lua b/systems/collision-detection.lua index 1d2f7a4..47b2203 100644 --- a/systems/collision-detection.lua +++ b/systems/collision-detection.lua @@ -1,36 +1,37 @@ collidingEntities = filteredSystem("collidingEntitites", { - velocity = T.XyPair, position = T.XyPair, size = T.XyPair, canCollideWith = T.BitMask, isSolid = Maybe(T.bool), }) +local function intersects(rect, rectOther) + local left = rect.position.x + local right = rect.position.x + rect.size.x + local top = rect.position.y + local bottom = rect.position.y + rect.size.y + + local leftOther = rectOther.position.x + local rightOther = rectOther.position.x + rectOther.size.x + local topOther = rectOther.position.y + local bottomOther = rectOther.position.y + rectOther.size.y + + return leftOther < right and left < rightOther and topOther < bottom and top < bottomOther +end + filteredSystem( "collisionDetection", { position = T.XyPair, size = T.XyPair, canBeCollidedBy = T.BitMask, isSolid = Maybe(T.bool) }, - -- Here, the entity, e, refers to some entity that a moving object may be colliding *into* +-- Here, the entity, e, refers to some entity that a moving object may be colliding *into* function(e, _, system) for _, collider in pairs(collidingEntities.entities) do if - (e ~= collider) + (e ~= collider) and collider.canCollideWith and e.canBeCollidedBy and bit.band(collider.canCollideWith, e.canBeCollidedBy) ~= 0 then - local colliderTop = collider.position.y - local colliderBottom = collider.position.y + collider.size.y - local entityTop = e.position.y - local entityBottom = entityTop + e.size.y - - local withinY = (entityTop > colliderTop and entityTop < colliderBottom) - or (entityBottom > colliderTop and entityBottom < colliderBottom) - - if - withinY - and collider.position.x < e.position.x + e.size.x - and collider.position.x + collider.size.x > e.position.x - then + if intersects(e, collider) then system.world:addEntity({ collisionBetween = { e, collider } }) end end diff --git a/systems/decay.lua b/systems/decay.lua index 1314221..53815fe 100644 --- a/systems/decay.lua +++ b/systems/decay.lua @@ -3,4 +3,12 @@ filteredSystem("decay", { decayAfterSeconds = T.number }, function(e, dt, system if e.decayAfterSeconds <= 0 then system.world:removeEntity(e) end -end) \ No newline at end of file +end) + +LiveForNFrames = filteredSystem("liveForNFrames", { liveForNFrames = T.number }, function(e, _, system) + e.liveForNFrames = e.liveForNFrames - 1 + if e.liveForNFrames <= 0 then + system.world:removeEntity(e) + end +end) + diff --git a/systems/draw.lua b/systems/draw.lua index 601c9a3..538e689 100644 --- a/systems/draw.lua +++ b/systems/draw.lua @@ -1,27 +1,49 @@ local gfx = love.graphics -filteredSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _) - gfx.fillRect(e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y) -end) +---@generic T +---@param shape T | fun() +---@param process fun(entity: T, dt: number, system: System) | nil +---@return System | { entities: T[] } +local function drawSystem(name, shape, process) + local system = filteredSystem(name, shape, process, function(_, a, b) + if a.z ~= nil and b.z ~= nil then + return a.z < b.z + end + if a.z ~= nil then + return true + end + return false + end) + system.isDrawSystem = true + return system +end -filteredSystem("drawSprites", { position = T.XyPair, drawAsSprite = T.pd_image }, function(e) - if e.position.y < Camera.pan.y - 240 or e.position.y > Camera.pan.y + 480 then - return +local spriteDrawSystem = drawSystem( + "drawSprites", + { position = T.XyPair, drawAsSprite = T.Drawable, rotation = Maybe(T.number) }, + function(e) + if not e.drawAsSprite then + return + end + gfx.draw(e.drawAsSprite, e.position.x, e.position.y) end - e.drawAsSprite:draw(e.position.x, e.position.y) -end) +) + +function spriteDrawSystem:preProcess() + gfx.setColor(1, 1, 1) +end local margin = 8 -filteredSystem( +drawSystem( "drawText", - { position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.pd_font) } }, + { position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.FontData) } }, function(e) - local font = gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold + local font = e.drawAsText.font or gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold local textHeight = font:getHeight() local textWidth = font:getWidth(e.drawAsText.text) - local bgLeftEdge = e.position.x - margin - textWidth / 2 + local bgLeftEdge = e.position.x - margin -- - (textWidth / 2) local bgTopEdge = e.position.y - 2 local bgWidth, bgHeight = textWidth + (margin * 2), textHeight + 2 @@ -34,9 +56,15 @@ filteredSystem( gfx.rectangle("fill", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) gfx.setColor(0, 0, 0) - gfx.drawRect("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) + gfx.rectangle("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) end gfx.print(e.drawAsText.text, bgLeftEdge + margin, bgTopEdge + margin) end ) + +drawSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _) + gfx.setColor(1, 1, 1, 0.5) + local mode = e.drawAsRectangle.mode or "line" -- (e.highlighted and "fill" or "line") + gfx.rectangle(mode, e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y) +end) diff --git a/tiny-tools.lua b/tiny-tools.lua index 10259c8..0b4a40c 100644 --- a/tiny-tools.lua +++ b/tiny-tools.lua @@ -1,13 +1,15 @@ ---@generic T ---@param shape T | fun() ---@param process fun(entity: T, dt: number, system: System) | nil +---@param compare nil | fun(system: System, entityA: T, entityB: T): boolean ---@return System | { entities: T[] } -function filteredSystem(name, shape, process) +function filteredSystem(name, shape, process, compare) assert(type(name) == "string") assert(type(shape) == "table" or type(shape) == "function") assert(process == nil or type(process) == "function") - local system = tiny.processingSystem() + local system = compare and tiny.sortedProcessingSystem() or tiny.processingSystem() + system.compare = compare system.name = name if type(shape) == "table" then local keys = {} diff --git a/tiny-types.lua b/tiny-types.lua index d81e914..205f198 100644 --- a/tiny-types.lua +++ b/tiny-types.lua @@ -1,12 +1,18 @@ ----@meta +---@meta tiny-ecs ---@class World World = {} function World:add(...) end +---@generic T : Entity +---@param entity T +---@return T function World:addEntity(entity) end +---@generic T : System +---@param system T +---@return T function World:addSystem(system) end function World:remove(...) end @@ -15,17 +21,28 @@ function World:removeEntity(entity) end function World:removeSystem(system) 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 World:refresh() end ---@param dt number -function World:update(dt) end +---@param systemFilter nil | fun(world: World, system: System): boolean +function World:update(dt, systemFilter) end +--- Removes all Entities from the World. function World:clearEntities() end +--- Removes all Systems from the World. function World:clearSystems() end +--- Gets number of Entities in the World. function World:getEntityCount() end +--- Gets number of Systems in World. function World:getSystemCount() end -function World:setSystemIndex() 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 World:setSystemIndex(world, system, index) end