49 Commits

Author SHA1 Message Date
bakpakin
b1c7e2ba83 Update to version 1.1-6. 2015-07-07 19:47:06 -04:00
bakpakin
6b549fcbc1 Readd tiny.getSystemIndex(world, system) 2015-07-07 19:26:12 -04:00
bakpakin
b9d994ad50 Update documentation for what functions return.
Remove useless code in world metatable
Make tiny.world(...) return newworld, ...
2015-07-07 19:05:56 -04:00
bakpakin
ce8ca35f7e Remove world.systemIndices to fix possible obscure bugs.
Remove tiny.getSystemIndex(world, system) in favor of a System 'index'
field.
Add some comments and documentation.
2015-07-06 23:28:50 -04:00
bakpakin
19ae03707f Make tiny.add, tiny.remove, and similar methods return entities and systems. 2015-07-06 22:01:06 -04:00
bakpakin
c1f1225248 Update to version 1.1-5. 2015-06-21 14:48:48 -04:00
bakpakin
45e8552054 Fix entity count in worlds. 2015-06-21 14:42:03 -04:00
bakpakin
f187dce4fd Update documentation. 2015-06-19 19:45:26 -04:00
bakpakin
927652597c Update to version 1.1-4. 2015-06-19 19:08:06 -04:00
bakpakin
6b3af0f2da Remove 2.0-1 from existence. 2015-06-19 18:55:06 -04:00
bakpakin
31f3396db1 Change System API again.
Remove tiny.getSystemEntityCount because it's useless and deprecation is for chumps.
2015-06-19 18:48:18 -04:00
bakpakin
cb9ab332d2 Fix sorting + add sorting test. 2015-06-18 16:02:16 -04:00
bakpakin
1fd32dab9a Add "world" flag in Systems. Clean up Systems when removing them from Worlds. 2015-06-18 15:19:21 -04:00
bakpakin
0a12897695 Correct use of 'self' in intervalSystemUpdate 2015-06-17 10:35:47 -04:00
bakpakin
e44ac6cdb3 Update to version 2.0-1 2015-06-16 23:48:57 -04:00
bakpakin
6f299470d0 Add CHANGELOG.md 2015-06-16 23:44:19 -04:00
bakpakin
61ffd2551b Readd tiny.processingSystem(table). 2015-06-16 23:26:48 -04:00
bakpakin
c66ed98a5a Add License sticker to README.md. 2015-06-16 23:13:53 -04:00
bakpakin
2621bd16a3 Add tiny.getSystemEntityCount(system). 2015-06-16 22:51:23 -04:00
bakpakin
c408267932 Update documentation. 2015-06-16 22:39:10 -04:00
bakpakin
557927577d Fix sorting. Change API for creating Systems to be more flexible. 2015-06-16 21:15:30 -04:00
bakpakin
cbfc3d360f Add attributes to tiny.system. Implement Interval Systems. 2015-06-16 20:28:21 -04:00
bakpakin
d35d62ed3c Add optional filter to tiny.update(world, dt, [filter]) 2015-06-16 19:45:09 -04:00
bakpakin
74666ca0c3 Fix adding inactive Systems. 2015-06-16 19:14:56 -04:00
bakpakin
5978ace463 Update to version 1.1-3 2015-06-14 10:22:03 -04:00
bakpakin
a504bd6336 Add removeSystem to World metatable. 2015-06-14 10:17:18 -04:00
bakpakin
69ae37586e Fix scm rockspec. 2015-06-07 20:05:56 -04:00
bakpakin
44fb94c457 Add scm rockspec. Fix README. 2015-06-07 18:47:50 -04:00
bakpakin
9230dad1e1 Update README.md to direct users to demo. 2015-05-17 08:26:04 +08:00
bakpakin
e4bf3f3767 Update to version 1.1-2. 2015-05-07 19:53:51 +08:00
bakpakin
71e97c12f6 Improve System documentation. 2015-05-07 19:45:28 +08:00
bakpakin
7c4ff7747c Change names of tiny.requireOne and tiny.rejectOne to tiny.requireAny and tiny.rejectAny. 2015-05-07 17:32:32 +08:00
bakpakin
808407f7c4 Fix 'tiny.add' and 'tiny.remove' iterating nil values. 2015-05-05 22:21:39 +08:00
bakpakin
153f326612 Add two new filter functions - rejectAll and rejectOne. 2015-05-05 22:03:04 +08:00
bakpakin
a4e3c90724 Update version to 1.1-1. 2015-05-04 20:24:03 +08:00
bakpakin
e47920f332 Update documentation. 2015-05-04 20:21:12 +08:00
bakpakin
e96d494526 Change sorted System calbacks. 2015-05-04 12:05:53 +08:00
bakpakin
994f3b6533 Remove tiny.activate and tiny.deactivate. 2015-05-04 11:46:18 +08:00
bakpakin
264d835334 Fix Clearing Systems. 2015-05-04 09:32:04 +08:00
bakpakin
e54f29884c Remove systemData from luadoc. 2015-05-04 09:24:36 +08:00
bakpakin
9991ff7474 Remove use of systemData - instead, Systems now have mutable data.
This removes a level of indirection and prevents passing around of entities.
2015-05-04 09:09:34 +08:00
bakpakin
5e7d2ea7fc Add license in tiny.lua.
All non-release commits are now versioned 'dev'.
2015-05-04 08:21:30 +08:00
bakpakin
6d1b280a40 Make enabling/disabling Systems not use varargs. 2015-05-03 23:31:56 +08:00
bakpakin
fb345c7718 Add Sorted Systems.
Remove tiny.manageSystems and tiny.manageEntities from API.
Add onModify System callback.
2015-05-03 23:05:50 +08:00
bakpakin
0f86eae571 Remove pesky trailing whitespace >:( 2015-04-27 21:48:27 +08:00
bakpakin
fd49633d17 Don't update a System without an update field in the System. 2015-04-27 15:45:54 +08:00
bakpakin
94fc3ac453 Update README.md to include luarocks. 2015-04-27 14:24:59 +08:00
bakpakin
4109d37cdb Update README.md to include demo. 2015-04-27 14:20:34 +08:00
bakpakin
c9dccaf85f Ignore .rock. 2015-04-26 11:55:07 +08:00
13 changed files with 642 additions and 265 deletions
Vendored
+3
View File
@@ -3,3 +3,6 @@ doc/*
index.html index.html
ldoc.css ldoc.css
topics/* topics/*
#Ignore generated rocks
*.rock
+24 -13
View File
@@ -1,16 +1,18 @@
# tiny-ecs # # tiny-ecs #
[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE)
Tiny-ecs is an Entity Component System for lua that's simple, flexible, and useful. Tiny-ecs is an Entity Component System for lua that's simple, flexible, and useful.
Because of lua's tabular nature, Entity Component Systems are a natural choice Because of lua's tabular nature, Entity Component Systems are a natural choice
for simulating large and complex systems. For more explanation on Entity for simulating large and complex systems. For more explanation on Entity
Component Systems, here is some Component Systems, here is some
[basic info](http://en.wikipedia.org/wiki/Entity_component_system "Wikipedia"). [basic info](http://en.wikipedia.org/wiki/Entity_component_system "Wikipedia").
Tiny-ecs also works well with Objected Oriented programming in lua because Tiny-ecs also works well with objected oriented programming in lua because
Systems and Entities do not use metatables. This means you can subclass your Systems and Entities do not use metatables. This means you can subclass your
Systems and Entities, and use existing lua class frameworks with tiny-ecs, no problem. Systems and Entities, and use existing lua class frameworks with tiny-ecs, no problem.
For an example on how to use tiny-ecs with object-oriented lua, take a look at the
## Use It ## demo branch, specifically the systems and entities sub-directories.
Copy paste tiny.lua into your source folder.
## Overview ## ## Overview ##
Tiny-ecs has four important types: Worlds, Filters, Systems, and Entities. Tiny-ecs has four important types: Worlds, Filters, Systems, and Entities.
@@ -32,7 +34,7 @@ Systems in tiny-ecs describe how to update Entities. Systems select certain Enti
using a Filter, and then only update those select Entities. Some Systems don't using a Filter, and then only update those select Entities. Some Systems don't
update Entities, and instead just act as function callbacks every update. Tiny-ecs update Entities, and instead just act as function callbacks every update. Tiny-ecs
provides functions for creating Systems easily, as well as creating Systems that provides functions for creating Systems easily, as well as creating Systems that
can be used in an Object Orientented fashion. can be used in an object oriented fashion.
### Filters ### ### Filters ###
Filters are used to select Entities. Filters can be any lua function, but Filters are used to select Entities. Filters can be any lua function, but
@@ -64,17 +66,26 @@ for i = 1, 20 do
end end
``` ```
## Use It ##
Copy paste tiny.lua into your source folder. For stability and consistent API,
please use a tagged release or use luarocks. Tagged releases will have a version
number in `tiny._VERSION`, while other commits will just have the string 'scm'.
## Luarocks ##
Tiny-ecs is also on [Luarocks](https://luarocks.org/) and can be installed with
`luarocks install tiny-ecs`.
## Demo ##
Check out the [demo](https://github.com/bakpakin/tiny-ecs/tree/demo-commandokibbles), a game
originally written for Ludum Dare 32 with the theme 'An Unconventional Weapon'. The demo uses
[LÖVE](https://love2d.org/), an amazing game framework for lua.
## Testing ## ## Testing ##
Tiny-ecs uses [busted](http://olivinelabs.com/busted/) for testing. Install and run Tiny-ecs uses [busted](http://olivinelabs.com/busted/) for testing. Install and run
`busted` from the command line to test. `busted` from the command line to test.
## Documentation ## ## Documentation ##
See API [here](http://bakpakin.github.io/tiny-ecs/doc/). See API [here](http://bakpakin.github.io/tiny-ecs/doc/).
Documentation can be generated locally with [LDoc](http://stevedonovan.github.io/ldoc/). For the most up-to-date documentation, read the source code, or generate the HTML
locally with [LDoc](http://stevedonovan.github.io/ldoc/).
## TODO ## See the original forum thread [here](https://love2d.org/forums/viewtopic.php?f=5&t=79937&p=182589).
* More testing
* Performance testing / optimization
* Improve Documentation
* Add more complete examples
+8 -2
View File
@@ -2,6 +2,12 @@ file = "tiny.lua"
project = "tiny-ecs" project = "tiny-ecs"
description = "Entity Component System for lua." description = "Entity Component System for lua."
backtick_references = true backtick_references = true
one = false format = 'discount'
title = "tiny-ecs API"
one = true
dir = doc dir = doc
sort = true style = '!fixed'
package = 'tiny-ecs'
not_luadoc = true
boilerplate = true
no_return_or_parms = true
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-1"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-1"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-2"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-2"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-3"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-3"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-4"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-4"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-5"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-5"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-6"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-6"
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+48 -25
View File
@@ -1,24 +1,15 @@
local tiny = require "tiny" local tiny = require "tiny"
-- Taken from answer at http://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value local function deep_copy(x)
local function deep_copy(o, seen) if type(x) == 'table' then
seen = seen or {} local nx = {}
if o == nil then return nil end for k, v in next, x, nil do
if seen[o] then return seen[o] end nx[deep_copy(k)] = deep_copy(v)
end
local no return nx
if type(o) == 'table' then else
no = {} return x
seen[o] = no
for k, v in next, o, nil do
no[deep_copy(k, seen)] = deep_copy(v, seen)
end end
setmetatable(no, deep_copy(getmetatable(o), seen))
else -- number, string, boolean, etc
no = o
end
return no
end end
local entityTemplate1 = { local entityTemplate1 = {
@@ -65,7 +56,14 @@ describe('tiny-ecs:', function()
local ftap = tiny.requireAll("spinalTap") local ftap = tiny.requireAll("spinalTap")
local fvel = tiny.requireAll("vel") local fvel = tiny.requireAll("vel")
local fxform = tiny.requireAll("xform") local fxform = tiny.requireAll("xform")
local fall = tiny.requireOne("spinalTap", "onlyTen", "littleMan") local fall = tiny.requireAny("spinalTap", "onlyTen", "littleMan")
-- Only select Entities without "spinalTap"
local frtap = tiny.rejectAny("spinalTap")
-- Select Entities without all three: "spinalTap", "onlyTen", and
-- "littleMan"
local frall = tiny.rejectAll("spinalTap", "onlyTen", "littleMan")
assert.truthy(fall(nil, entity1)) assert.truthy(fall(nil, entity1))
assert.truthy(ftap(nil, entity1)) assert.truthy(ftap(nil, entity1))
@@ -77,6 +75,14 @@ describe('tiny-ecs:', function()
assert.truthy(fall(nil, entity2)) assert.truthy(fall(nil, entity2))
assert.truthy(fall(nil, entity3)) assert.truthy(fall(nil, entity3))
assert.falsy(frtap(nil, entity1))
assert.truthy(frtap(nil, entity2))
assert.truthy(frtap(nil, entity3))
assert.truthy(frall(nil, entity1))
assert.truthy(frall(nil, entity2))
assert.truthy(frall(nil, entity3))
end) end)
end) end)
@@ -97,7 +103,7 @@ describe('tiny-ecs:', function()
local timePassed = 0 local timePassed = 0
local oneTimeSystem = tiny.system() local oneTimeSystem = tiny.system()
function oneTimeSystem:update(entities, dt) function oneTimeSystem:update(dt)
timePassed = timePassed + dt timePassed = timePassed + dt
end end
@@ -138,15 +144,19 @@ describe('tiny-ecs:', function()
assert.equals(0, world:getSystemCount()) assert.equals(0, world:getSystemCount())
end) end)
it("Disable and Enable Systems", function() it("Deactivate and Activate Systems", function()
world:deactivate(moveSystem, oneTimeSystem) moveSystem.active = false
oneTimeSystem.active = false
world:update(1) world:update(1)
assert.equals(world:getSystemCount(), 2)
assert.equals(timePassed, 0) assert.equals(timePassed, 0)
assert.equals(entity1.xform.x, entityTemplate1.xform.x) assert.equals(entity1.xform.x, entityTemplate1.xform.x)
world:activate(moveSystem, oneTimeSystem) moveSystem.active = true
oneTimeSystem.active = true
world:update(1) world:update(1)
assert.equals(timePassed, 1) assert.equals(timePassed, 1)
assert.are_not.equal(entity1.xform.x, entityTemplate1.xform.x) assert.are_not.equal(entity1.xform.x, entityTemplate1.xform.x)
assert.equals(world:getSystemCount(), 2)
end) end)
it("Clear Entities", function() it("Clear Entities", function()
@@ -201,8 +211,21 @@ describe('tiny-ecs:', function()
world:update(1) world:update(1)
world:setSystemIndex(moveSystem, 2) world:setSystemIndex(moveSystem, 2)
world:update(1) world:update(1)
assert.equals(2, world:getSystemIndex(moveSystem)) assert.equals(2, moveSystem.index)
assert.equals(1, world:getSystemIndex(oneTimeSystem)) assert.equals(1, oneTimeSystem.index)
end)
it("Sorts Entities in Sorting Systems", function()
local sortsys = tiny.sortedProcessingSystem()
sortsys.filter = tiny.requireAll("vel")
function sortsys:compare(e1, e2)
return e1.vel.x < e2.vel.x
end
world:add(sortsys)
world:update(0)
assert.equals(sortsys.entities[1], entity2)
assert.equals(sortsys.entities[2], entity3)
assert.equals(sortsys.entities[3], entity1)
end) end)
end) end)
+23
View File
@@ -0,0 +1,23 @@
package = "tiny-ecs"
version = "scm-0"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
}
description = {
summary = "Entity Component System for Lua.",
detailed = [[
Pure Lua implementation of an easy to use, compact, fast, and flexible
Entity Component System. Works well with Object Orientation.
]],
homepage = "https://github.com/bakpakin/tiny-ecs",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
tiny = "tiny.lua"
}
}
+389 -222
View File
@@ -1,14 +1,38 @@
--[[
Copyright (c) 2015 Calvin Rose
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.
]]
--- @module tiny-ecs --- @module tiny-ecs
-- @author Calvin Rose -- @author Calvin Rose
local tiny = { _VERSION = "1.0-2" } -- @license MIT
-- @copyright 2015
local tiny = { _VERSION = "1.1-6" }
-- Local versions of standard lua functions -- Local versions of standard lua functions
local tinsert = table.insert local tinsert = table.insert
local tremove = table.remove local tremove = table.remove
local tsort = table.sort
local pairs = pairs local pairs = pairs
local ipairs = ipairs
local setmetatable = setmetatable local setmetatable = setmetatable
local type = type local type = type
local select = select
-- Local versions of the library functions -- Local versions of the library functions
local tiny_manageEntities local tiny_manageEntities
@@ -22,20 +46,56 @@ local tiny_remove
--- Filter functions. --- Filter functions.
-- A Filter is a function that selects which Entities apply to a System. -- A Filter is a function that selects which Entities apply to a System.
-- Filters take two parameters, the System and the Entity, and return a boolean
-- value indicating if the Entity should be processed by the System.
--
-- Filters must be added to Systems by setting the `filter` field of the System.
-- Filter's returned by tiny-ecs's Filter functions are immutable and can be
-- used by multiple Systems.
--
-- local f1 = tiny.requireAll("position", "velocity", "size")
-- local f2 = tiny.requireAny("position", "velocity", "size")
--
-- local e1 = {
-- position = {2, 3},
-- velocity = {3, 3},
-- size = {4, 4}
-- }
--
-- local entity2 = {
-- position = {4, 5},
-- size = {4, 4}
-- }
--
-- local e3 = {
-- position = {2, 3},
-- velocity = {3, 3}
-- }
--
-- print(f1(nil, e1), f1(nil, e2), f1(nil, e3)) -- prints true, false, false
-- print(f2(nil, e1), f2(nil, e2), f2(nil, e3)) -- prints true, true, true
--
-- Filters can also be passed as arguments to other Filter constructors. This is
-- a powerful way to create complex, custom Filters that select a very specific
-- set of Entities.
--
-- -- Selects Entities with an "image" Component, but not Entities with a
-- -- "Player" or "Enemy" Component.
-- filter = tiny.requireAll("image", tiny.rejectAny("Player", "Enemy"))
--
-- @section Filter -- @section Filter
--- Makes a Filter that filters Entities with specified Components. --- Makes a Filter that selects Entities with all specified Components and
-- An Entity must have all Components to match the filter. -- Filters.
-- @param ... List of Components
function tiny.requireAll(...) function tiny.requireAll(...)
local components = {...} local components = {...}
local len = #components local len = #components
return function(_, e) return function(system, e)
local c local c
for i = 1, len do for i = 1, len do
c = components[i] c = components[i]
if type(c) == 'function' then if type(c) == 'function' then
if not c(_, e) then if not c(system, e) then
return false return false
end end
elseif e[c] == nil then elseif e[c] == nil then
@@ -46,18 +106,17 @@ function tiny.requireAll(...)
end end
end end
--- Makes a Filter that filters Entities with specified Components. --- Makes a Filter that selects Entities with at least one of the specified
-- An Entity must have at least one specified Component to match the filter. -- Components and Filters.
-- @param ... List of Components function tiny.requireAny(...)
function tiny.requireOne(...)
local components = {...} local components = {...}
local len = #components local len = #components
return function(_, e) return function(system, e)
local c local c
for i = 1, len do for i = 1, len do
c = components[i] c = components[i]
if type(c) == 'function' then if type(c) == 'function' then
if c(_, e) then if c(system, e) then
return true return true
end end
elseif e[c] ~= nil then elseif e[c] ~= nil then
@@ -68,47 +127,122 @@ function tiny.requireOne(...)
end end
end end
--- Makes a Filter that rejects Entities with all specified Components and
-- Filters, and selects all other Entities.
function tiny.rejectAll(...)
local components = {...}
local len = #components
return function(system, e)
local c
for i = 1, len do
c = components[i]
if type(c) == 'function' then
if not c(system, e) then
return true
end
elseif e[c] == nil then
return true
end
end
return false
end
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(...)
local components = {...}
local len = #components
return function(system, e)
local c
for i = 1, len do
c = components[i]
if type(c) == 'function' then
if c(system, e) then
return false
end
elseif e[c] ~= nil then
return false
end
end
return true
end
end
--- System functions. --- System functions.
-- A System is a wrapper around function callbacks for manipulating Entities. -- A System is a wrapper around function callbacks for manipulating Entities.
-- Systems are implemented as tables that contain at least one method;
-- an update function that takes parameters like so:
--
-- * `function system:update(dt)`.
--
-- There are also a few other optional callbacks:
--
-- * `function system:filter(entity)` - Returns true if this System should
-- include this Entity, otherwise should return false. If this isn't specified,
-- no Entities are included in the System.
-- * `function system:onAdd(entity)` - Called when an Entity is added to the
-- System.
-- * `function system:onRemove(entity)` - Called when an Entity is removed
-- from the System.
-- * `function system:onModify(dt)` - Called when the System is modified by
-- adding or removing Entities from the System.
--
-- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`,
-- but one can write their own filters as well. Set the Filter of a System like
-- so:
-- system.filter = tiny.requireAll("a", "b", "c")
-- or
-- function system:filter(entity)
-- return entity.myRequiredComponentName ~= nil
-- end
--
-- All Systems also have a few important fields that are initialized when the
-- system is added to the World. A few are important, and few should be less
-- commonly used.
--
-- * The `world` field points to the World that the System belongs to. Useful
-- for adding and removing Entities from the world dynamically via the System.
-- * The `active` flag is 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.
-- * The `entities` field is an ordered list of Entities in the System. This
-- list can be used to quickly iterate through all Entities in a System.
-- * The `interval` field 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.
-- * The `index` field 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)`.
-- * The `indices` field is a table of Entity keys to their indices in the
-- `entities` list. Most Systems can ignore this.
-- * The `modified` flag is an indicator 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.
--
-- @section System -- @section System
-- Use an empty table as a key for identifying Systems. Any table that contains -- Use an empty table as a key for identifying Systems. Any table that contains
-- this key is considered a System rather than an Entity. -- this key is considered a System rather than an Entity.
local systemTableKey = { "SYSTEM_TABLE_KEY" } local systemTableKey = { "SYSTEM_TABLE_KEY" }
-- Check if tables are systems. -- Checks if a table is a System.
local function isSystem(table) local function isSystem(table)
return table[systemTableKey] return table[systemTableKey]
end end
--- Creates a System. Systems are tables that contain at least one field;
-- an update function that takes parameters like so:
-- `function system:update(entities, dt)`. `entities` is an unordered table of
-- Entities with Entities as KEYS, and `dt` is the delta time. There are also a
-- few other optional callbacks:
-- `function system:filter(entity)` - returns a boolean,
-- `function system:onAdd(entity)` - returns nil,
-- `function system:onRemove(entity)` - returns nil.
-- For Filters, it is conveient to use `tiny.requireAll` or `tiny.requireOne`,
-- but one can write their own filters as well.
-- @param table A table to be used as a System, or `nil` to create a new System.
function tiny.system(table)
if table == nil then
table = {}
end
table[systemTableKey] = true
return table
end
-- Update function for all Processing Systems. -- Update function for all Processing Systems.
local function processingSystemUpdate(system, entities, dt) local function processingSystemUpdate(system, dt)
local entities = system.entities
local preProcess = system.preProcess local preProcess = system.preProcess
local process = system.process local process = system.process
local postProcess = system.postProcess local postProcess = system.postProcess
local entity local entity
if preProcess then if preProcess then
preProcess(system, entities, dt) preProcess(system, dt)
end end
if process then if process then
@@ -120,48 +254,100 @@ local function processingSystemUpdate(system, entities, dt)
end end
if postProcess then if postProcess then
postProcess(system, entities, dt) postProcess(system, dt)
end end
end end
--- Creates a Processing System. A Processing System iterates through its -- Sorts Systems by a function system.sort(entity1, entity2) on modify.
-- Entities in no particluar order, and updates them individually. It has two local function sortedSystemOnModify(system, dt)
-- important fields, `function system:process(entity, dt)`, and `function local entities = system.entities
-- system:filter(entity)`. `entities` is Entities, local indices = system.indices
-- and `dt` is the delta time. There are also a few other local sortDelegate = system.sortDelegate
-- optional callbacks: if not sortDelegate then
-- `function system:preProcess(entities, dt)` - returns nil, local compare = system.compare
-- `function system:postProcess(entities, dt)` - returns nil, sortDelegate = function(e1, e2)
-- `function system:onAdd(entity)` - returns nil, return compare(system, e1, e2)
-- `function system:onRemove(entity)` - returns nil. end
-- For Filters, it is conveient to use `tiny.requireAll` or `tiny.requireOne`, system.sortDelegate = sortDelegate
-- but one can write their own filters as well.
-- @param table A table to be used as a System, or `nil` to create a new
-- Processing System.
function tiny.processingSystem(table)
if table == nil then
table = {}
end end
tsort(entities, sortDelegate)
for i = 1, #entities do
local entity = entities[i]
indices[entity] = i
end
end
--- Creates a new System or System class from the supplied table. If `table` is
-- nil, creates a new table.
function tiny.system(table)
table = table or {}
table[systemTableKey] = true
return table
end
--- Creates a new Processing System or Processing System class. Processing
-- Systems process each entity individual, and are usually what is needed.
-- Processing Systems have three extra callbacks besides those inheritted from
-- vanilla Systems.
--
-- function system:preProcess(entities, dt) -- Called before iteration.
-- function system:process(entities, dt) -- Process each entity.
-- function system:postProcess(entity, dt) -- Called after iteration.
--
-- Processing Systems have their own `update` method, so don't implement a
-- a custom `update` callback for Processing Systems.
-- @see system
function tiny.processingSystem(table)
table = table or {}
table[systemTableKey] = true table[systemTableKey] = true
table.update = processingSystemUpdate table.update = processingSystemUpdate
return table return table
end end
--- Creates a new Sorted System or Sorted System class. Sorted Systems sort
-- their Entities according to a user-defined method, `system:compare(e1, e2)`,
-- which should return true if `e1` should come before `e2` and false otherwise.
-- Sorted Systems also override the default System's `onModify` callback, so be
-- careful if defining a custom callback. However, for processing the sorted
-- entities, consider `tiny.sortedProcessingSystem(table)`.
-- @see system
function tiny.sortedSystem(table)
table = table or {}
table[systemTableKey] = true
table.onModify = sortedSystemOnModify
return table
end
--- Creates a new Sorted Processing System or Sorted Processing System class.
-- Sorted Processing Systems have both the aspects of Processing Systems and
-- Sorted Systems.
-- @see system
-- @see processingSystem
-- @see sortedSystem
function tiny.sortedProcessingSystem(table)
table = table or {}
table[systemTableKey] = true
table.update = processingSystemUpdate
table.onModify = sortedSystemOnModify
return table
end
--- World functions. --- World functions.
-- A World is a container that manages Entities and Systems. The tiny-ecs module -- A World is a container that manages Entities and Systems. Typically, a
-- is set to be the `__index` of all World tables, so the often clearer syntax of -- program uses one World at a time.
-- 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).` -- For all World functions except `tiny.world(...)`, object-oriented syntax can
-- be used instead of the documented syntax. For example,
-- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3)`.
-- @section World -- @section World
local worldMetaTable = { __index = tiny } -- Forward declaration
local worldMetaTable
--- Creates a new World. --- Creates a new World.
-- Can optionally add default Systems and Entities. -- Can optionally add default Systems and Entities. Returns the new World along
-- @param ... Systems and Entities to add to the World -- with default Entities and Systems.
-- @return A new World
function tiny.world(...) function tiny.world(...)
local ret = { local ret = {
-- List of Entities to add -- List of Entities to add
@@ -179,120 +365,95 @@ function tiny.world(...)
-- Set of Entities -- Set of Entities
entities = {}, entities = {},
-- Number of Entities in World. -- Number of Entities in World
entityCount = 0, entityCount = 0,
-- List of System Data. A data element is a table with 4 -- List of Systems
-- keys: system, indices, entities, and active. systems = {}
systems = {},
-- Table of Systems to System Indices
systemIndices = {}
} }
tiny_add(ret, ...) tiny_add(ret, ...)
tiny_manageSystems(ret) tiny_manageSystems(ret)
tiny_manageEntities(ret) tiny_manageEntities(ret)
return setmetatable(ret, worldMetaTable) return setmetatable(ret, worldMetaTable), ...
end end
--- Adds an Entity to the world. --- Adds an Entity to the world.
-- The new Entity will enter the world next time World:update is called. -- Also call this on Entities that have changed Components such that they
-- Also call this on Entities that have changed Components such that it -- match different Filters. Returns the Entity.
-- matches different systems.
-- @param world
-- @param entity
function tiny.addEntity(world, entity) function tiny.addEntity(world, entity)
local e2a = world.entitiesToAdd local e2a = world.entitiesToAdd
e2a[#e2a + 1] = entity e2a[#e2a + 1] = entity
if world.entities[entity] then if world.entities[entity] then
tiny_removeEntity(world, entity) tiny_removeEntity(world, entity)
end end
return entity
end end
tiny_addEntity = tiny.addEntity tiny_addEntity = tiny.addEntity
--- Adds a System to the world. --- Adds a System to the world. Returns the System.
-- The new System will enter the world next time World:update is called.
-- @param world
-- @param system
function tiny.addSystem(world, system) function tiny.addSystem(world, system)
local s2a = world.systemsToAdd local s2a = world.systemsToAdd
s2a[#s2a + 1] = system s2a[#s2a + 1] = system
return system
end end
tiny_addSystem = tiny.addSystem tiny_addSystem = tiny.addSystem
--- Shortcut for adding multiple Entities and Systems to the World. --- Shortcut for adding multiple Entities and Systems to the World. Returns all
-- New objects will enter the World the next time World:update(dt) is called. -- added Entities and Systems.
-- 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, ...) function tiny.add(world, ...)
local args = {...} local obj
for _, obj in ipairs(args) do for i = 1, select("#", ...) do
if isSystem(obj) then obj = select(i, ...)
tiny_addSystem(world, obj) if obj then
else -- Assume obj is an Entity if isSystem(obj) then
tiny_addEntity(world, obj) tiny_addSystem(world, obj)
else -- Assume obj is an Entity
tiny_addEntity(world, obj)
end
end end
end end
return ...
end end
tiny_add = tiny.add tiny_add = tiny.add
--- Removes an Entity to the World. --- Removes an Entity to the World. Returns the Entity.
-- The Entity will exit the World next time World:update is called.
-- Also call this on Entities that have changed Components such that it
-- matches different systems.
-- @param world
-- @param entity
function tiny.removeEntity(world, entity) function tiny.removeEntity(world, entity)
local e2r = world.entitiesToRemove local e2r = world.entitiesToRemove
e2r[#e2r + 1] = entity e2r[#e2r + 1] = entity
return entity
end end
tiny_removeEntity = tiny.removeEntity tiny_removeEntity = tiny.removeEntity
--- Removes a System from the world. --- Removes a System from the world. Returns the System.
-- The System will exit the World next time World:update is called.
-- @param world
-- @param system
function tiny.removeSystem(world, system) function tiny.removeSystem(world, system)
local s2r = world.systemsToRemove local s2r = world.systemsToRemove
s2r[#s2r + 1] = system s2r[#s2r + 1] = system
return system
end end
tiny_removeSystem = tiny.removeSystem tiny_removeSystem = tiny.removeSystem
--- Shortcut for removing multiple Entities and Systems from the World. --- Shortcut for removing multiple Entities and Systems from the World. Returns
-- Objects will exit the World the next time World:update(dt) is called. -- all rmeove Systems and Entities
-- @param world
-- @param ... Systems and Entities
function tiny.remove(world, ...) function tiny.remove(world, ...)
local args = {...} local obj
for _, obj in ipairs(args) do for i = 1, select("#", ...) do
if isSystem(obj) then obj = select(i, ...)
tiny_removeSystem(world, obj) if obj then
else -- Assume obj is an Entity if isSystem(obj) then
tiny_removeEntity(world, obj) tiny_removeSystem(world, obj)
else -- Assume obj is an Entity
tiny_removeEntity(world, obj)
end
end end
end end
return ...
end end
tiny_remove = tiny.remove tiny_remove = tiny.remove
--- Updates a System. -- Adds and removes Systems that have been marked from the World.
-- @param world function tiny_manageSystems(world)
-- @param system A System in the World to update
-- @param dt Delta time
function tiny.updateSystem(world, system, dt)
local es = world.systemEntities[system]
system:update(es, dt)
end
--- 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 s2a, s2r = world.systemsToAdd, world.systemsToRemove local s2a, s2r = world.systemsToAdd, world.systemsToRemove
-- Early exit -- Early exit
@@ -300,44 +461,53 @@ function tiny.manageSystems(world)
return return
end end
local systemIndices = world.systemIndices
local entities = world.entities local entities = world.entities
local systems = world.systems local systems = world.systems
local system, index, filter
local system, systemData, index, filter, entityList, entityIndices, entityIndex, onRemove, onAdd local entityList, entityIndices, entityIndex, onRemove, onAdd
-- Remove Systems -- Remove Systems
for i = 1, #s2r do for i = 1, #s2r do
system = s2r[i] system = s2r[i]
index = systemIndices[system] index = system.index
if index then if system.world == world then
systemData = systems[index]
onRemove = system.onRemove onRemove = system.onRemove
if onRemove then if onRemove then
entityList = systemData.entities entityList = system.entities
for j = 1, #entityList do for j = 1, #entityList do
onRemove(system, entityList[j]) onRemove(system, entityList[j])
end end
end end
systemIndices[system] = nil
tremove(systems, index) tremove(systems, index)
for j = index, #systems do for j = index, #systems do
systemIndices[systems[j].system] = j systems[j].index = j
end end
end end
s2r[i] = nil s2r[i] = nil
-- Clean up System
system.world = nil
system.entities = nil
system.indices = nil
system.index = nil
end end
-- Add Systems -- Add Systems
for i = 1, #s2a do for i = 1, #s2a do
system = s2a[i] system = s2a[i]
if not systemIndices[system] then if systems[system.index] ~= system then
entityList = {} entityList = {}
entityIndices = {} entityIndices = {}
systemData = { system = system, entities = entityList, indices = entityIndices, active = true } system.entities = entityList
system.indices = entityIndices
if system.active == nil then
system.active = true
end
system.modified = true
system.world = world
index = #systems + 1 index = #systems + 1
systemIndices[system] = index system.index = index
systems[index] = systemData systems[index] = system
-- Try to add Entities -- Try to add Entities
onAdd = system.onAdd onAdd = system.onAdd
@@ -357,15 +527,10 @@ function tiny.manageSystems(world)
end end
s2a[i] = nil s2a[i] = nil
end end
end 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)
-- Adds and removes Entities that have been marked.
function tiny_manageEntities(world)
local e2a, e2r = world.entitiesToAdd, world.entitiesToRemove local e2a, e2r = world.entitiesToAdd, world.entitiesToRemove
-- Early exit -- Early exit
@@ -376,28 +541,29 @@ function tiny.manageEntities(world)
local entities = world.entities local entities = world.entities
local systems = world.systems local systems = world.systems
local entityCount = world.entityCount local entityCount = world.entityCount
local entity, system, systemData, index, onRemove, onAdd, ses, seis, filter, tmpEntity local entity, system, index
local onRemove, onAdd, ses, seis, filter, tmpEntity
-- Remove Entities -- Remove Entities
for i = 1, #e2r do for i = 1, #e2r do
entity = e2r[i] entity = e2r[i]
if entities[entity] then if entities[entity] then
entities[entity] = nil entities[entity] = nil
entityCount = entityCount - 1
for j = 1, #systems do for j = 1, #systems do
systemData = systems[j] system = systems[j]
system = systemData.system ses = system.entities
ses = systemData.entities seis = system.indices
seis = systemData.indices
index = seis[entity] index = seis[entity]
if index then if index then
system.modified = true
tmpEntity = ses[#ses] tmpEntity = ses[#ses]
ses[index] = tmpEntity ses[index] = tmpEntity
seis[tmpEntity] = index seis[tmpEntity] = index
seis[entity] = nil seis[entity] = nil
ses[#ses] = nil ses[#ses] = nil
entityCount = entityCount - 1
onRemove = system.onRemove onRemove = system.onRemove
if onRemove then if onRemove then
onRemove(system, entity) onRemove(system, entity)
@@ -410,24 +576,23 @@ function tiny.manageEntities(world)
e2r[i] = nil e2r[i] = nil
end end
-- Add Entities -- Add Entities
for i = 1, #e2a do for i = 1, #e2a do
entity = e2a[i] entity = e2a[i]
if not entities[entity] then if not entities[entity] then
entities[entity] = true entities[entity] = true
entityCount = entityCount + 1
for j = 1, #systems do for j = 1, #systems do
systemData = systems[j] system = systems[j]
system = systemData.system ses = system.entities
ses = systemData.entities seis = system.indices
seis = systemData.indices
filter = system.filter filter = system.filter
if filter and filter(system, entity) then if filter and filter(system, entity) then
system.modified = true
index = #ses + 1 index = #ses + 1
ses[index] = entity ses[index] = entity
seis[entity] = index seis[entity] = index
entityCount = entityCount + 1
onAdd = system.onAdd onAdd = system.onAdd
if onAdd then if onAdd then
onAdd(system, entity) onAdd(system, entity)
@@ -442,36 +607,55 @@ function tiny.manageEntities(world)
-- Update Entity count -- Update Entity count
world.entityCount = entityCount world.entityCount = entityCount
end end
tiny_manageEntities = tiny.manageEntities
--- Updates the World. --- Updates the World by dt (delta time). Takes an optional parameter, `filter`,
-- Frees Entities that have been marked for freeing, adds -- which is a Filter that selects Systems from the World, and updates only those
-- entities that have been marked for adding, etc. -- Systems. If `filter` is not supplied, all Systems are updated. Put this
-- @param world -- function in your main loop.
-- @param dt Delta time function tiny.update(world, dt, filter)
function tiny.update(world, dt)
tiny_manageSystems(world) tiny_manageSystems(world)
tiny_manageEntities(world) tiny_manageEntities(world)
local systems = world.systems local systems = world.systems
local systemData local system, update, interval, onModify
-- Iterate through Systems IN ORDER -- Iterate through Systems IN ORDER
for i = 1, #systems do for i = 1, #systems do
systemData = systems[i] system = systems[i]
if systemData.active then if system.active and ((not filter) or filter(world, system)) then
systemData.system:update(systemData.entities, dt)
-- Call the modify callback on Systems that have been modified.
onModify = system.onModify
if onModify and system.modified then
onModify(system, dt)
end
-- Update Systems that have an update method (most Systems)
update = system.update
if update then
interval = system.interval
if interval then
local bufferedTime = (system.bufferedTime or 0) + dt
while bufferedTime >= interval do
bufferedTime = bufferedTime - interval
if update then
update(system, interval)
end
end
system.bufferedTime = bufferedTime
else
update(system, dt)
end
end
system.modified = false
end end
end end
end end
--- Removes all Entities from the World. --- Removes all Entities from the World.
-- When World:update(dt) is next called,
-- all Entities will be removed.
-- @param world
function tiny.clearEntities(world) function tiny.clearEntities(world)
for e in pairs(world.entities) do for e in pairs(world.entities) do
tiny_removeEntity(world, e) tiny_removeEntity(world, e)
@@ -479,80 +663,63 @@ function tiny.clearEntities(world)
end end
--- Removes all Systems from the World. --- Removes all Systems from the World.
-- When World:update(dt) is next called,
-- all Systems will be removed.
-- @param world
function tiny.clearSystems(world) function tiny.clearSystems(world)
local systems = world.systems local systems = world.systems
for i = #systems, 1, -1 do for i = #systems, 1, -1 do
tiny_removeSystem(world, systems[i].system) tiny_removeSystem(world, systems[i])
end end
end end
--- Gets count of Entities in World. --- Gets number of Entities in the World.
-- @param world
function tiny.getEntityCount(world) function tiny.getEntityCount(world)
return world.entityCount return world.entityCount
end end
--- Gets count of Systems in World. --- Gets number of Systems in World.
-- @param world
function tiny.getSystemCount(world) function tiny.getSystemCount(world)
return #(world.systems) return #(world.systems)
end end
--- Gets the index of a System in the world. Lower indexed Systems are processed --- Gets the index of the System in the World.
-- before higher indexed systems. -- A simpler alternative is `system.index`.
-- @param world
-- @param system
function tiny.getSystemIndex(world, system) function tiny.getSystemIndex(world, system)
return world.systemIndices[system] return system.index
end end
--- Sets the index of a System in the world. Changes the order in --- Sets the index of a System in the World, and returns the old index. Changes
-- which they Systems processed, because lower indexed Systems are processed -- the order in which they Systems processed, because lower indexed Systems are
-- first. -- processed first. Returns the old system.index.
-- @param world
-- @param system
-- @param index
function tiny.setSystemIndex(world, system, index) function tiny.setSystemIndex(world, system, index)
local systemIndices = world.systemIndices local oldIndex = system.index
local oldIndex = systemIndices[system]
local systems = world.systems local systems = world.systems
local systemData = systems[oldIndex]
tremove(systems, oldIndex) tremove(systems, oldIndex)
tinsert(systems, index, systemData) tinsert(systems, index, system)
for i = oldIndex, index, index >= oldIndex and 1 or -1 do for i = oldIndex, index, index >= oldIndex and 1 or -1 do
systemIndices[systems[i].system] = i systems[i].index = i
end end
return oldIndex
end end
--- Activates Systems in the World. -- Construct world metatable.
-- Activated Systems will be update whenever tiny.update(world, dt) is called. worldMetaTable = {
-- @param world __index = {
-- @param ... Systems to activate. The Systems must already be added to the add = tiny.add,
-- World. addEntity = tiny.addEntity,
function tiny.activate(world, ...) addSystem = tiny.addSystem,
local args = {...} remove = tiny.remove,
for _, system in ipairs(args) do removeEntity = tiny.removeEntity,
world.systems[world.systemIndices[system]].active = true removeSystem = tiny.removeSystem,
end update = tiny.update,
end clearEntities = tiny.clearEntities,
clearSystems = tiny.clearSystems,
--- Deactivates Systems in the World. getEntityCount = tiny.getEntityCount,
-- Deactivated Systems must be update manually, and will not update when the getSystemCount = tiny.getSystemCount,
-- rest of World updates. They will, however, process new Entities added while getSystemIndex = tiny.getSystemIndex,
-- the System is deactivated. setSystemIndex = tiny.setSystemIndex
-- @param world }
-- @param ... Systems to deactivate. The Systems must already be added to the }
-- World.
function tiny.deactivate(world, ...)
local args = {...}
for _, system in ipairs(args) do
world.systems[world.systemIndices[system]].active = false
end
end
return tiny return tiny