mirror of
https://github.com/bakpakin/tiny-ecs.git
synced 2024-10-12 19:44:17 +00:00
429 lines
13 KiB
Lua
429 lines
13 KiB
Lua
--- @module tiny-ecs
|
|
-- @author Calvin Rose
|
|
local tiny = {}
|
|
|
|
--- Tiny-ecs Version, a period-separated three number string like "1.2.3"
|
|
tiny._VERSION = "0.3.0"
|
|
|
|
local tinsert = table.insert
|
|
local tremove = table.remove
|
|
local tconcat = table.concat
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local setmetatable = setmetatable
|
|
local getmetatable = getmetatable
|
|
local type = type
|
|
|
|
-- Local versions of the library functions
|
|
local tiny_system
|
|
local tiny_manageEntities
|
|
local tiny_manageSystems
|
|
local tiny_updateSystem
|
|
local tiny_add
|
|
local tiny_remove
|
|
|
|
--- Filter functions.
|
|
-- A Filter is a function that selects which Entities apply to a System.
|
|
-- @section Filter
|
|
|
|
--- Makes a Filter that filters Entities with specified Components.
|
|
-- An Entity must have all Components to match the filter.
|
|
-- @param ... List of Components
|
|
function tiny.requireAll(...)
|
|
local components = {...}
|
|
local len = #components
|
|
return function(e)
|
|
local c
|
|
for i = 1, len do
|
|
c = components[i]
|
|
if type(c) == 'function' then
|
|
if not c(e) then
|
|
return false
|
|
end
|
|
elseif e[c] == nil then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
|
|
--- Makes a Filter that filters Entities with specified Components.
|
|
-- An Entity must have at least one specified Component to match the filter.
|
|
-- @param ... List of Components
|
|
function tiny.requireOne(...)
|
|
local components = {...}
|
|
local len = #components
|
|
return function(e)
|
|
local c
|
|
for i = 1, len do
|
|
c = components[i]
|
|
if type(c) == 'function' then
|
|
if c(e) then
|
|
return true
|
|
end
|
|
elseif e[c] ~= nil then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- System functions.
|
|
-- A System a wrapper around function callbacks for manipulating Entities.
|
|
-- @section System
|
|
|
|
local systemMetaTable = {}
|
|
|
|
--- Creates a System.
|
|
-- @param callback Function of one argument, delta time, that is called once
|
|
-- per world update
|
|
-- @param filter Function of one argument, an Entity, that returns a boolean
|
|
-- @param entityCallback Function of two arguments, an Entity and delta time
|
|
-- @param onAdd Optional callback for when Enities are added to the System that
|
|
-- takes one argument, an Entity
|
|
-- @param onRemove Similar to onAdd, but is instead called when an Entity is
|
|
-- removed from the System
|
|
-- @param postCallback similar to callback, but is called after Entites are
|
|
-- processed
|
|
function tiny.system(callback, filter, entityCallback, onAdd, onRemove, postCallback)
|
|
local ret = {
|
|
callback = callback,
|
|
filter = filter,
|
|
entityCallback = entityCallback,
|
|
onAdd = onAdd,
|
|
onRemove = onRemove,
|
|
postCallback = postCallback
|
|
}
|
|
setmetatable(ret, systemMetaTable)
|
|
return ret
|
|
end
|
|
tiny_system = tiny.system
|
|
|
|
--- Creates a System that processes Entities every update. Also provides
|
|
-- optional callbacks for when Entities are added or removed from the System.
|
|
-- @param filter Function of one argument, an Entity, that returns a boolean
|
|
-- @param entityCallback Function of two arguments, an Entity and delta time
|
|
-- @param onAdd Optional callback for when Entities are added to the System that
|
|
-- takes one argument, an Entity
|
|
-- @param onRemove Similar to onAdd, but is instead called when an Entity is
|
|
-- removed from the System
|
|
-- @param postCallback similar to callback, but is called after Entites are
|
|
-- processed
|
|
function tiny.processingSystem(filter, entityCallback, onAdd, onRemove, postCallback)
|
|
return tiny_system(nil, filter, entityCallback, onAdd, onRemove, postCallback)
|
|
end
|
|
|
|
local worldMetaTable = { __index = tiny }
|
|
|
|
--- World functions.
|
|
-- A World is a container that manages Entities and Systems. The tiny-ecs module
|
|
-- is set to be the `__index` of all World tables, so the often clearer syntax of
|
|
-- World:method can be used for any function in the library. For example,
|
|
-- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3).`
|
|
-- @section World
|
|
|
|
--- Creates a new World.
|
|
-- Can optionally add default Systems and Entities.
|
|
-- @param ... Systems and Entities to add to the World
|
|
-- @return A new World
|
|
function tiny.world(...)
|
|
|
|
local ret = {
|
|
|
|
-- Table of Entities to status
|
|
-- Statuses: remove, add
|
|
entityStatus = {},
|
|
|
|
-- Table of Systems to status
|
|
-- Statuses: remove, add
|
|
systemStatus = {},
|
|
|
|
-- Set of Entities -- active are true, inactive are false
|
|
entities = {},
|
|
|
|
-- Number of Entities in World.
|
|
entityCount = 0,
|
|
|
|
-- Number of Systems in World.
|
|
systemCount = 0,
|
|
|
|
-- List of Systems
|
|
systems = {},
|
|
|
|
-- Table of Systems to whether or not they are active.
|
|
activeSystems = {},
|
|
|
|
-- Table of Systems to System Indices
|
|
systemIndices = {},
|
|
|
|
-- Table of Systems to Sets of matching Entities
|
|
systemEntities = {}
|
|
}
|
|
|
|
tiny.add(ret, ...)
|
|
tiny.manageSystems(ret)
|
|
tiny.manageEntities(ret)
|
|
|
|
setmetatable(ret, worldMetaTable)
|
|
return ret
|
|
|
|
end
|
|
|
|
--- Adds Entities and Systems to the World.
|
|
-- New objects will enter the World the next time World:update(dt) is called.
|
|
-- Also call this method when an Entity has had its Components changed, such
|
|
-- that it matches different Filters.
|
|
-- @param world
|
|
-- @param ... Systems and Entities
|
|
function tiny.add(world, ...)
|
|
local args = {...}
|
|
local entityStatus = world.entityStatus
|
|
local systemStatus = world.systemStatus
|
|
local systemIndices = world.systemIndices
|
|
local systemCount = world.systemCount
|
|
for _, obj in ipairs(args) do
|
|
if getmetatable(obj) == systemMetaTable then
|
|
systemStatus[obj] = "add"
|
|
systemCount = systemCount + 1
|
|
systemIndices[obj] = systemCount
|
|
else -- Assume obj is an Entity
|
|
entityStatus[obj] = "add"
|
|
end
|
|
end
|
|
end
|
|
tiny_add = tiny.add
|
|
|
|
--- Removes Entities and Systems from the World. Objects will exit the World the
|
|
-- next time World:update(dt) is called.
|
|
-- @param world
|
|
-- @param ... Systems and Entities
|
|
function tiny.remove(world, ...)
|
|
local args = {...}
|
|
local entityStatus = world.entityStatus
|
|
local systemStatus = world.systemStatus
|
|
for _, obj in ipairs(args) do
|
|
if getmetatable(obj) == systemMetaTable then
|
|
systemStatus[obj] = "remove"
|
|
else -- Assume obj is an Entity
|
|
entityStatus[obj] = "remove"
|
|
end
|
|
end
|
|
end
|
|
tiny_remove = tiny.remove
|
|
|
|
--- Updates a System.
|
|
-- @param world
|
|
-- @param system A System in the World to update
|
|
-- @param dt Delta time
|
|
function tiny.updateSystem(world, system, dt)
|
|
local callback = system.callback
|
|
local entityCallback = system.entityCallback
|
|
local postCallback = system.postCallback
|
|
|
|
if callback then
|
|
callback(dt)
|
|
end
|
|
|
|
if entityCallback then
|
|
local entities = world.entities
|
|
local es = world.systemEntities[system]
|
|
if es then
|
|
for e in pairs(es) do
|
|
entityCallback(e, dt)
|
|
end
|
|
end
|
|
end
|
|
|
|
if postCallback then
|
|
postCallback(dt)
|
|
end
|
|
|
|
end
|
|
tiny_updateSystem = tiny.updateSystem
|
|
|
|
--- Adds and removes Systems that have been marked from the World.
|
|
-- The user of this library should seldom if ever call this.
|
|
-- @param world
|
|
function tiny.manageSystems(world)
|
|
|
|
local systemEntities = world.systemEntities
|
|
local systemIndices = world.systemIndices
|
|
local entities = world.entities
|
|
local systems = world.systems
|
|
local systemStatus = world.systemStatus
|
|
local activeSystems = world.activeSystems
|
|
|
|
-- Keep track of the number of Systems in the world
|
|
local deltaSystemCount = 0
|
|
|
|
for system, status in pairs(systemStatus) do
|
|
|
|
if status == "add" then
|
|
local es = {}
|
|
systemEntities[system] = es
|
|
systems[systemIndices[system]] = system
|
|
activeSystems[system] = true
|
|
local filter = system.filter
|
|
if filter then
|
|
for e in pairs(entities) do
|
|
es[e] = filter(e) and true or nil
|
|
end
|
|
end
|
|
deltaSystemCount = deltaSystemCount + 1
|
|
elseif status == "remove" then
|
|
local index = systemIndices[system]
|
|
tremove(systems, index)
|
|
local onRemove = system.onRemove
|
|
if onRemove then
|
|
for e in pairs(systemEntities[sys]) do
|
|
onRemove(e)
|
|
end
|
|
end
|
|
systemEntities[system] = nil
|
|
activeSystems[system] = nil
|
|
deltaSystemCount = deltaSystemCount - 1
|
|
end
|
|
|
|
systemStatus[system] = nil
|
|
end
|
|
|
|
-- Update the number of Systems in the World
|
|
world.systemCount = world.systemCount + deltaSystemCount
|
|
end
|
|
tiny_manageSystems = tiny.manageSystems
|
|
|
|
--- Adds and removes Entities that have been marked.
|
|
-- The user of this library should seldom if ever call this.
|
|
-- @param world
|
|
function tiny.manageEntities(world)
|
|
|
|
local entityStatus = world.entityStatus
|
|
local systemEntities = world.systemEntities
|
|
local entities = world.entities
|
|
local systems = world.systems
|
|
|
|
-- Keep track of the number of Entities in the World
|
|
local deltaEntityCount = 0
|
|
|
|
-- Add, remove, or change Entities
|
|
for e, s in pairs(entityStatus) do
|
|
if s == "add" then
|
|
if not entities[e] then
|
|
deltaEntityCount = deltaEntityCount + 1
|
|
end
|
|
entities[e] = true
|
|
for sys, es in pairs(systemEntities) do
|
|
local filter = sys.filter
|
|
if filter then
|
|
local matches = filter(e) and true or nil
|
|
local onAdd = sys.onAdd
|
|
if onAdd and matches and not es[e] then
|
|
onAdd(e)
|
|
end
|
|
es[e] = matches
|
|
end
|
|
end
|
|
elseif s == "remove" then
|
|
if entities[e] then
|
|
deltaEntityCount = deltaEntityCount - 1
|
|
end
|
|
entities[e] = nil
|
|
for sys, es in pairs(systemEntities) do
|
|
local onRemove = sys.onRemove
|
|
if es[e] and onRemove then
|
|
onRemove(e)
|
|
end
|
|
es[e] = nil
|
|
end
|
|
end
|
|
entityStatus[e] = nil
|
|
end
|
|
|
|
-- Update Entity count
|
|
world.entityCount = world.entityCount + deltaEntityCount
|
|
|
|
end
|
|
tiny_manageEntities = tiny.manageEntities
|
|
|
|
--- Updates the World.
|
|
-- Frees Entities that have been marked for freeing, adds
|
|
-- entities that have been marked for adding, etc.
|
|
-- @param world
|
|
-- @param dt Delta time
|
|
function tiny.update(world, dt)
|
|
|
|
tiny_manageSystems(world)
|
|
tiny_manageEntities(world)
|
|
|
|
-- Iterate through Systems IN ORDER
|
|
for _, s in ipairs(world.systems) do
|
|
if world.activeSystems[s] then
|
|
tiny_updateSystem(world, s, dt)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Removes all Entities from the World.
|
|
-- When World:update(dt) is next called,
|
|
-- all Entities will be removed.
|
|
-- @param world
|
|
function tiny.clearEntities(world)
|
|
local entityStatus = world.entityStatus
|
|
for e in pairs(world.entities) do
|
|
entityStatus[e] = "remove"
|
|
end
|
|
end
|
|
|
|
--- Removes all Systems from the World.
|
|
-- When World:update(dt) is next called,
|
|
-- all Systems will be removed.
|
|
-- @param world
|
|
function tiny.clearSystems(world)
|
|
local systemStatus = world.systemStatus
|
|
for _, s in ipairs(world.systems) do
|
|
systemStatus[s] = "remove"
|
|
end
|
|
end
|
|
|
|
--- Gets count of Entities in World.
|
|
-- @param world
|
|
function tiny.getEntityCount(world)
|
|
return world.entityCount
|
|
end
|
|
|
|
--- Gets count of Systems in World.
|
|
-- @param world
|
|
function tiny.getSystemCount(world)
|
|
return world.systemCount
|
|
end
|
|
|
|
--- Activates Systems in the World.
|
|
-- Activated Systems will be update whenever tiny.update(world, dt) is called.
|
|
-- @param world
|
|
-- @param ... Systems to activate. The Systems must already be added to the
|
|
-- World.
|
|
function tiny.activate(world, ...)
|
|
local args = {...}
|
|
for _, obj in ipairs(args) do
|
|
world.activeSystems[obj] = true
|
|
end
|
|
end
|
|
|
|
--- Deactivates Systems in the World.
|
|
-- Deactivated Systems must be update manually, and will not update when the
|
|
-- rest of World updates. They will, however, process new Entities added while
|
|
-- the System is deactivated.
|
|
-- @param world
|
|
-- @param ... Systems to deactivate. The Systems must already be added to the
|
|
-- World.
|
|
function tiny.deactivate(world, ...)
|
|
local args = {...}
|
|
for _, obj in ipairs(args) do
|
|
world.activeSystems[obj] = nil
|
|
end
|
|
end
|
|
|
|
return tiny
|