Patch up some template holes.
More consistent collision detection, LiveForNFrames system, drawSystem split, more correct LOVE types, inspect.lus library for table printing, and a few improved tiny types.
This commit is contained in:
parent
3316f3fc3b
commit
7c69889098
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "<metatable> = ")
|
||||
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
|
80
lib/tiny.lua
80
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<any, any> 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 "<tiny-ecs_World>"
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
_G.tiny = tiny
|
||||
|
|
33
main.lua
33
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,4 +3,12 @@ filteredSystem("decay", { decayAfterSeconds = T.number }, function(e, dt, system
|
|||
if e.decayAfterSeconds <= 0 then
|
||||
system.world:removeEntity(e)
|
||||
end
|
||||
end)
|
||||
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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue