mirror of
https://github.com/bakpakin/tiny-ecs.git
synced 2026-07-21 23:56:50 -06:00
Start using LDoc for documentation.
Remove class system Fix various small bug
This commit is contained in:
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
#Ignore generated documentation
|
||||
doc/*
|
||||
@@ -53,7 +53,7 @@ local joe = {
|
||||
hairColor = "brown"
|
||||
}
|
||||
|
||||
local world = tiny.newWorld(talkingSystem, joe)
|
||||
local world = tiny.world(talkingSystem, joe)
|
||||
|
||||
for i = 1, 20 do
|
||||
world:update(1)
|
||||
@@ -64,10 +64,14 @@ end
|
||||
Tiny-ecs uses [busted](http://olivinelabs.com/busted/) for testing. Install and run
|
||||
`busted` from the command line to test.
|
||||
|
||||
## Documentation ##
|
||||
See API [here](http://bakpakin.github.io/tiny-ecs/).
|
||||
Documentation can be generated locally with [LDoc](http://stevedonovan.github.io/ldoc/).
|
||||
|
||||
## TODO ##
|
||||
|
||||
* Dynamic reordering of Systems
|
||||
* More testing
|
||||
* Performance testing / optimization
|
||||
* API outside of source code
|
||||
* Improve Documentation
|
||||
* Add more complete examples
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
file = "tiny.lua"
|
||||
project = "tiny-ecs"
|
||||
description = "Entity Component System for lua."
|
||||
backtick_references = true
|
||||
one = false
|
||||
+2
-2
@@ -97,7 +97,7 @@ describe('tiny-ecs:', function()
|
||||
)
|
||||
|
||||
local timePassed = 0
|
||||
local oneTimeSystem = tiny.emptySystem(
|
||||
local oneTimeSystem = tiny.system(
|
||||
function(dt)
|
||||
timePassed = timePassed + dt
|
||||
end
|
||||
@@ -107,7 +107,7 @@ describe('tiny-ecs:', function()
|
||||
entity1 = deep_copy(entityTemplate1)
|
||||
entity2 = deep_copy(entityTemplate2)
|
||||
entity3 = deep_copy(entityTemplate3)
|
||||
world = tiny.newWorld(moveSystem, oneTimeSystem, entity1, entity2, entity3)
|
||||
world = tiny.world(moveSystem, oneTimeSystem, entity1, entity2, entity3)
|
||||
timePassed = 0
|
||||
end)
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
local tiny = {
|
||||
_VERSION = "0.2.0",
|
||||
_URL = "https://github.com/bakpakin/tiny-ecs",
|
||||
_DESCRIPTION = "tiny-ecs - Entity Component System for lua."
|
||||
}
|
||||
--- @module tiny-ecs
|
||||
-- @author Calvin Rose
|
||||
local tiny = {}
|
||||
|
||||
--- Tiny-ecs Version, a period-separated three number string like "1.2.3" with
|
||||
-- no leading zeros.
|
||||
tiny._VERSION = "0.3.0"
|
||||
|
||||
local tinsert = table.insert
|
||||
local tremove = table.remove
|
||||
@@ -12,107 +14,165 @@ local ipairs = ipairs
|
||||
local setmetatable = setmetatable
|
||||
local getmetatable = getmetatable
|
||||
|
||||
-- Simple class implementation with no inheritance or polymorphism.
|
||||
local function class()
|
||||
local c = {}
|
||||
local mt = {}
|
||||
setmetatable(c, mt)
|
||||
c.__index = c
|
||||
function mt.__call(_, ...)
|
||||
local newobj = {}
|
||||
setmetatable(newobj, c)
|
||||
if c.init then
|
||||
c.init(newobj, ...)
|
||||
-- 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 e[c] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return newobj
|
||||
return true
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
-- --- System --- --
|
||||
local System = class()
|
||||
|
||||
-- Initializes a System.
|
||||
function System:init(preupdate, filter, update, add, remove)
|
||||
self.preupdate = preupdate
|
||||
self.update = update
|
||||
self.filter = filter
|
||||
self.add = add
|
||||
self.remove = remove
|
||||
--- 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 e[c] ~= nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function System:__tostring()
|
||||
return "TinySystem<preupdate: " ..
|
||||
self.preupdate ..
|
||||
", update: " ..
|
||||
self.update ..
|
||||
", filter: " ..
|
||||
self.filter ..
|
||||
">"
|
||||
--- 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
|
||||
function tiny.system(callback, filter, entityCallback, onAdd, onRemove)
|
||||
local ret = {
|
||||
callback = callback,
|
||||
filter = filter,
|
||||
entityCallback = entityCallback,
|
||||
onAdd = onAdd,
|
||||
onRemove = onRemove
|
||||
}
|
||||
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
|
||||
function tiny.processingSystem(filter, entityCallback, onAdd, onRemove)
|
||||
return tiny_system(nil, filter, entityCallback, onAdd, onRemove)
|
||||
end
|
||||
|
||||
-- --- World --- --
|
||||
local World = class()
|
||||
local worldMetaTable = { __index = tiny }
|
||||
|
||||
-- Initializes a World.
|
||||
function World:init(...)
|
||||
--- 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
|
||||
|
||||
-- Table of Entities to status
|
||||
self.status = {}
|
||||
--- 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(...)
|
||||
|
||||
-- Set of Entities
|
||||
self.entities = {}
|
||||
local ret = {
|
||||
|
||||
-- Number of Entities in World.
|
||||
self.entityCount = 0
|
||||
-- Table of Entities to status
|
||||
status = {},
|
||||
|
||||
-- Number of Systems in World.
|
||||
self.systemCount = 0
|
||||
-- Set of Entities
|
||||
entities = {},
|
||||
|
||||
-- List of Systems
|
||||
self.systems = {}
|
||||
-- Number of Entities in World.
|
||||
entityCount = 0,
|
||||
|
||||
-- Table of Systems to whether or not they are active.
|
||||
self.activeSystems = {}
|
||||
-- Number of Systems in World.
|
||||
systemCount = 0,
|
||||
|
||||
-- Table of Systems to System Indices
|
||||
self.systemIndices = {}
|
||||
-- List of Systems
|
||||
systems = {},
|
||||
|
||||
-- Table of Systems to Sets of matching Entities
|
||||
self.systemEntities = {}
|
||||
-- Table of Systems to whether or not they are active.
|
||||
activeSystems = {},
|
||||
|
||||
-- List of Systems to add next update
|
||||
self.systemsToAdd = {}
|
||||
-- Table of Systems to System Indices
|
||||
systemIndices = {},
|
||||
|
||||
-- List of Systems to remove next update
|
||||
self.systemsToRemove = {}
|
||||
-- Table of Systems to Sets of matching Entities
|
||||
systemEntities = {},
|
||||
|
||||
-- List of Systems to add next update
|
||||
systemsToAdd = {},
|
||||
|
||||
-- List of Systems to remove next update
|
||||
systemsToRemove = {}
|
||||
|
||||
}
|
||||
|
||||
tiny.add(ret, ...)
|
||||
tiny.manageSystems(ret)
|
||||
tiny.manageEntities(ret)
|
||||
|
||||
setmetatable(ret, worldMetaTable)
|
||||
return ret
|
||||
|
||||
-- Add Systems and Entities
|
||||
self:add(...)
|
||||
self:manageSystems()
|
||||
self:manageEntities()
|
||||
end
|
||||
|
||||
function World:__tostring()
|
||||
return "TinyWorld<systemCount: " ..
|
||||
self.systemCount ..
|
||||
", entityCount: " ..
|
||||
self.entityCount ..
|
||||
">"
|
||||
end
|
||||
|
||||
-- World:add(...)
|
||||
|
||||
-- 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.
|
||||
function World:add(...)
|
||||
--- 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 status = self.status
|
||||
local entities = self.entities
|
||||
local systemsToAdd = self.systemsToAdd
|
||||
local status = world.status
|
||||
local entities = world.entities
|
||||
local systemsToAdd = world.systemsToAdd
|
||||
for _, obj in ipairs(args) do
|
||||
if getmetatable(obj) == System then
|
||||
if getmetatable(obj) == systemMetaTable then
|
||||
tinsert(systemsToAdd, obj)
|
||||
else -- Assume obj is an Entity
|
||||
entities[obj] = true
|
||||
@@ -120,60 +180,63 @@ function World:add(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
tiny_add = tiny.add
|
||||
|
||||
-- World:free(...)
|
||||
|
||||
-- Removes Entities and Systems from the World. Objects will exit the World the
|
||||
--- Removes Entities and Systems from the World. Objects will exit the World the
|
||||
-- next time World:update(dt) is called.
|
||||
function World:remove(...)
|
||||
-- @param world
|
||||
-- @param ... Systems and Entities
|
||||
function tiny.remove(world, ...)
|
||||
local args = {...}
|
||||
local status = self.status
|
||||
local entities = self.entities
|
||||
local systemsToRemove = self.systemsToRemove
|
||||
local status = world.status
|
||||
local entities = world.entities
|
||||
local systemsToRemove = world.systemsToRemove
|
||||
for _, obj in ipairs(args) do
|
||||
if getmetatable(obj) == System then
|
||||
if getmetatable(obj) == systemMetaTable then
|
||||
tinsert(systemsToRemove, obj)
|
||||
elseif entities[obj] then -- Assume obj is an Entity
|
||||
status[obj] = "remove"
|
||||
end
|
||||
end
|
||||
end
|
||||
tiny_remove = tiny.remove
|
||||
|
||||
-- World:updateSystem(system, dt)
|
||||
--- 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
|
||||
|
||||
-- Updates a System
|
||||
function World:updateSystem(system, dt)
|
||||
local preupdate = system.preupdate
|
||||
local update = system.update
|
||||
|
||||
if preupdate then
|
||||
preupdate(dt)
|
||||
if callback then
|
||||
callback(dt)
|
||||
end
|
||||
|
||||
if update then
|
||||
local entities = self.entities
|
||||
local es = self.systemEntities[system]
|
||||
if entityCallback then
|
||||
local entities = world.entities
|
||||
local es = world.systemEntities[system]
|
||||
if es then
|
||||
for e in pairs(es) do
|
||||
update(e, dt)
|
||||
entityCallback(e, dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
tiny_updateSystem = tiny.updateSystem
|
||||
|
||||
-- World:manageSystems()
|
||||
--- 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)
|
||||
|
||||
-- Adds and removes Systems that have been marked from the world. The user of
|
||||
-- this library should seldom if ever call this.
|
||||
function World:manageSystems()
|
||||
|
||||
local systemEntities = self.systemEntities
|
||||
local systemIndices = self.systemIndices
|
||||
local entities = self.entities
|
||||
local systems = self.systems
|
||||
local systemsToAdd = self.systemsToAdd
|
||||
local systemsToRemove = self.systemsToRemove
|
||||
local activeSystems = self.activeSystems
|
||||
local systemEntities = world.systemEntities
|
||||
local systemIndices = world.systemIndices
|
||||
local entities = world.entities
|
||||
local systems = world.systems
|
||||
local systemsToAdd = world.systemsToAdd
|
||||
local systemsToRemove = world.systemsToRemove
|
||||
local activeSystems = world.activeSystems
|
||||
|
||||
-- Keep track of the number of Systems in the world
|
||||
local deltaSystemCount = 0
|
||||
@@ -188,10 +251,10 @@ function World:manageSystems()
|
||||
if sysID then
|
||||
tremove(systems, sysID)
|
||||
|
||||
local removeCallback = sys.remove
|
||||
if removeCallback then -- call 'remove' on all entities in the System
|
||||
local onRemove = sys.onRemove
|
||||
if onRemove then
|
||||
for e in pairs(systemEntities[sys]) do
|
||||
removeCallback(e)
|
||||
onRemove(e)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -214,10 +277,10 @@ function World:manageSystems()
|
||||
systemIndices[sys] = #systems
|
||||
activeSystems[sys] = true
|
||||
|
||||
local a = sys.filter
|
||||
if a then
|
||||
local filter = sys.filter
|
||||
if filter then
|
||||
for e in pairs(entities) do
|
||||
es[e] = a(e) and true or nil
|
||||
es[e] = filter(e) and true or nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -225,19 +288,19 @@ function World:manageSystems()
|
||||
end
|
||||
|
||||
-- Update the number of Systems in the World
|
||||
self.systemCount = self.systemCount + deltaSystemCount
|
||||
world.systemCount = world.systemCount + deltaSystemCount
|
||||
end
|
||||
tiny_manageSystems = tiny.manageSystems
|
||||
|
||||
-- World:manageEntities()
|
||||
--- 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)
|
||||
|
||||
-- Adds and removes Entities that have been marked. The user of this library
|
||||
-- should seldom if ever call this.
|
||||
function World:manageEntities()
|
||||
|
||||
local statuses = self.status
|
||||
local systemEntities = self.systemEntities
|
||||
local entities = self.entities
|
||||
local systems = self.systems
|
||||
local statuses = world.status
|
||||
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
|
||||
@@ -250,9 +313,9 @@ function World:manageEntities()
|
||||
local filter = sys.filter
|
||||
if filter then
|
||||
local matches = filter(e) and true or nil
|
||||
local addCallback = sys.add
|
||||
if addCallback and matches and not es[e] then
|
||||
addCallback(e)
|
||||
local onAdd = sys.onAdd
|
||||
if onAdd and matches and not es[e] then
|
||||
onAdd(e)
|
||||
end
|
||||
es[e] = matches
|
||||
end
|
||||
@@ -261,9 +324,9 @@ function World:manageEntities()
|
||||
deltaEntityCount = deltaEntityCount - 1
|
||||
entities[e] = nil
|
||||
for sys, es in pairs(systemEntities) do
|
||||
local removec = sys.remove
|
||||
if es[e] and removec then
|
||||
removec(e)
|
||||
local onRemove = sys.onRemove
|
||||
if es[e] and onRemove then
|
||||
onRemove(e)
|
||||
end
|
||||
es[e] = nil
|
||||
end
|
||||
@@ -272,116 +335,61 @@ function World:manageEntities()
|
||||
end
|
||||
|
||||
-- Update Entity count
|
||||
self.entityCount = self.entityCount + deltaEntityCount
|
||||
world.entityCount = world.entityCount + deltaEntityCount
|
||||
|
||||
end
|
||||
tiny_manageEntities = tiny.manageEntities
|
||||
|
||||
-- World:update()
|
||||
|
||||
-- Updates the World, frees Entities that have been marked for freeing, adds
|
||||
--- Updates the World.
|
||||
-- Frees Entities that have been marked for freeing, adds
|
||||
-- entities that have been marked for adding, etc.
|
||||
function World:update(dt)
|
||||
-- @param world
|
||||
-- @param dt Delta time
|
||||
function tiny.update(world, dt)
|
||||
|
||||
self:manageSystems()
|
||||
self:manageEntities()
|
||||
tiny_manageSystems(world)
|
||||
tiny_manageEntities(world)
|
||||
|
||||
-- Iterate through Systems IN ORDER
|
||||
for _, s in ipairs(self.systems) do
|
||||
if self.activeSystems[s] then
|
||||
self:updateSystem(s, dt)
|
||||
for _, s in ipairs(world.systems) do
|
||||
if world.activeSystems[s] then
|
||||
tiny_updateSystem(world, s, dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- World:clearEntities()
|
||||
|
||||
-- Removes all Entities from the World. When World:update(dt) is next called,
|
||||
--- Removes all Entities from the World.
|
||||
-- When World:update(dt) is next called,
|
||||
-- all Entities will be removed.
|
||||
function World:clearEntities()
|
||||
local status = self.status
|
||||
for e in pairs(self.entities) do
|
||||
-- @param world
|
||||
function tiny.clearEntities(world)
|
||||
local status = world.status
|
||||
for e in pairs(world.entities) do
|
||||
status[e] = "remove"
|
||||
end
|
||||
end
|
||||
|
||||
-- World:clearSystems()
|
||||
|
||||
-- Removes all Systems from the World. When World:update(dt) is next called,
|
||||
--- Removes all Systems from the World.
|
||||
-- When World:update(dt) is next called,
|
||||
-- all Systems will be removed.
|
||||
function World:clearSystems()
|
||||
-- @param world
|
||||
function tiny.clearSystems(world)
|
||||
local newSystemsToRemove = {}
|
||||
local systems = self.systems
|
||||
local systems = world.systems
|
||||
for i = 1, #systems do
|
||||
newSystemsToRemove[i] = systems[i]
|
||||
end
|
||||
self.systemsToRemove = newSystemsToRemove
|
||||
world.systemsToRemove = newSystemsToRemove
|
||||
end
|
||||
|
||||
-- World:setSystemActive(system, active)
|
||||
|
||||
-- Sets if a System is active in a world. If the system is active, it will
|
||||
--- Sets if a System is active in a world. If the system is active, it will
|
||||
-- update automatically when World:update(dt) is called. Otherwise, the user
|
||||
-- must call World:updateSystem(system, dt) to update the unactivated system.
|
||||
function World:setSystemActive(system, active)
|
||||
self.activeSystem[system] = active and true or nil
|
||||
end
|
||||
|
||||
-- --- Top Level module functions --- --
|
||||
|
||||
--- Creates a new tiny-ecs World.
|
||||
function tiny.newWorld(...)
|
||||
return World(...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that filters Entities with specified Components.
|
||||
-- An Entity must have all Components to match the filter.
|
||||
function tiny.requireAll(...)
|
||||
local components = {...}
|
||||
local len = #components
|
||||
return function(e)
|
||||
local c
|
||||
for i = 1, len do
|
||||
c = components[i]
|
||||
if 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.
|
||||
function tiny.requireOne(...)
|
||||
local components = {...}
|
||||
local len = #components
|
||||
return function(e)
|
||||
local c
|
||||
for i = 1, len do
|
||||
c = components[i]
|
||||
if e[c] ~= nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a System that doesn't update any Entities, but executes a callback
|
||||
-- once per update.
|
||||
function tiny.emptySystem(callback)
|
||||
return System(callback)
|
||||
end
|
||||
|
||||
--- Creates a System that processes Entities every update. Also provides
|
||||
-- optional callbacks for when Entities are added or removed from the System.
|
||||
function tiny.processingSystem(filter, entityCallback, onAdd, onRemove)
|
||||
return System(nil, filter, entityCallback, onAdd, onRemove)
|
||||
end
|
||||
|
||||
--- Creates a System.
|
||||
function tiny.system(callback, filter, entityCallback, onAdd, onRemove)
|
||||
return System(callback, filter, entityCallback, onAdd, onRemove)
|
||||
-- @param world
|
||||
-- @param system A System in the World activate/deactivate
|
||||
-- @param active Boolean new state of the System
|
||||
function tiny.setSystemActive(world, system, active)
|
||||
world.activeSystem[system] = active and true or nil
|
||||
end
|
||||
|
||||
return tiny
|
||||
|
||||
Reference in New Issue
Block a user