27 Commits

Author SHA1 Message Date
bakpakin
08e5f13d85 Begin experimental option to not use caching. 2016-03-06 14:42:48 -05:00
bakpakin
cbf71d2cb2 Add rockspec for version to 1.3-1 2016-03-05 16:42:14 -05:00
bakpakin
93a19d93c8 Wrap error throwing functions in pcalls and make doc look cleaner for tiny.filter. 2016-03-05 16:35:06 -05:00
bakpakin
4148855233 Bump version. 2016-03-05 16:20:09 -05:00
bakpakin
27f881444a Fix string filter generator. 2016-03-05 16:06:56 -05:00
bakpakin
3b4bd9920b Add generic filter. 2016-03-05 15:34:21 -05:00
bakpakin
206361ca38 Make filters compile. Add some filter tests. 2016-03-05 13:34:44 -05:00
bakpakin
29158282bd Update Copyright Date. 2016-02-29 19:19:34 -05:00
bakpakin
2403a9019c Release version 1.2-1. 2015-09-08 16:25:10 -04:00
bakpakin
6234e4b741 Document onAddToWorld and onRemoveFromWorld. 2015-09-08 16:19:07 -04:00
bakpakin
b870b5a34e Add onAddToWorld and onRemoveFromWorld callbacks to systems. Also, adding or removing systems to a world twice raises an error. 2015-09-08 13:37:53 -04:00
bakpakin
f3b16a40f6 Add some simple assertions to adding and removing systems. 2015-09-08 13:22:07 -04:00
bakpakin
5035d2c8a7 Make the scope of many local variables smaller. 2015-08-27 14:55:24 -04:00
bakpakin
18270cd60a Adding already added entities no longer removes them from the World first, just updates them to relavent Systems. 2015-08-27 14:31:45 -04:00
bakpakin
dba8e7c89f Remove indication of a versioned release in tiny.lua. 2015-08-27 13:51:13 -04:00
bakpakin
fccff0329e Add travis to tiny-ecs. 2015-08-27 13:47:28 -04:00
bakpakin
8a759edca2 Fix docs again. *sigh* 2015-08-27 13:32:51 -04:00
bakpakin
d9d311d858 Fix system docs. 2015-08-27 13:26:09 -04:00
bakpakin
9834dfa9d6 Fix Adding systems and items to the world inside a call to system:onAdd() 2015-08-24 12:52:11 -04:00
bakpakin
a5af4b6ecd Index systems from the end like string.sub. 2015-08-22 22:45:07 -04:00
bakpakin
f0e6ce7880 Update to version 1.1-7. 2015-08-05 21:01:53 -04:00
bakpakin
26f8259f2b Add tiny.refresh for updating a World without updating Systems. 2015-07-29 12:40:13 -04:00
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
9 changed files with 573 additions and 227 deletions
+39
View File
@@ -0,0 +1,39 @@
language: erlang
env:
global:
- LUAROCKS_BASE=luarocks-2.2.2
matrix:
- LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_VER=5.1 LUA_SFX=5.1 LUA_INCDIR=/usr/include/lua5.1
- LUA=lua5.2 LUA_DEV=liblua5.2-dev LUA_VER=5.2 LUA_SFX=5.2 LUA_INCDIR=/usr/include/lua5.2
- LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_VER=5.1 LUA_SFX=jit LUA_INCDIR=/usr/include/luajit-2.0
before_install:
- if [ $LUA = "luajit" ]; then
sudo add-apt-repository ppa:mwild1/ppa -y && sudo apt-get update -y;
sudo apt-get install lua5.1;
sudo apt-get install liblua5.1-dev;
fi
- sudo apt-get install $LUA
- sudo apt-get install $LUA_DEV
- lua$LUA_SFX -v
# Install a recent luarocks release
- wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz
- tar zxvpf $LUAROCKS_BASE.tar.gz
- cd $LUAROCKS_BASE
- ./configure
--lua-version=$LUA_VER --lua-suffix=$LUA_SFX --with-lua-include="$LUA_INCDIR"
- sudo make
- sudo make install
- cd $TRAVIS_BUILD_DIR
install:
- sudo -E luarocks install busted
script:
- sudo -E busted -v
notifications:
email:
on_success: change
on_failure: always
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2015 Calvin Rose Copyright (c) 2016 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: 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:
+2
View File
@@ -1,5 +1,7 @@
# tiny-ecs # # tiny-ecs #
[![Build Status](https://travis-ci.org/bakpakin/tiny-ecs.png?branch=master)](https://travis-ci.org/bakpakin/tiny-ecs)
[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) [![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.
+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"
}
}
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-7"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-7"
}
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.2-1"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.2-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.3-1"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.3-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"
}
}
+65 -6
View File
@@ -1,3 +1,8 @@
local GLOBALS = {}
for k, v in pairs(_G) do
GLOBALS[k] = v
end
local tiny = require "tiny" local tiny = require "tiny"
local function deep_copy(x) local function deep_copy(x)
@@ -85,6 +90,49 @@ describe('tiny-ecs:', function()
end) end)
it("Can use functions as subfilters", function()
local f1 = tiny.requireAny('a', 'b', 'c')
local f2 = tiny.requireAll('x', 'y', 'z')
local f = tiny.requireAll(f1, f2)
assert.truthy(f(nil, {
x = true, y = true, z = true, a = true, b = true, c = true
}))
assert.truthy(f(nil, {
x = true, y = true, z = true, a = true
}))
assert.falsy(f(nil, {
x = true, y = true, a = true
}))
assert.falsy(f(nil, {
x = true, y = true, z = true
}))
end)
it("Can use string filters", function()
local f = tiny.filter('a|b|c')
assert.truthy(f(nil, {
a = true, b = true, c = true
}))
assert.truthy(f(nil, {
a = true
}))
assert.truthy(f(nil, {
b = true
}))
assert.truthy(f(nil, {
c = true
}))
assert.falsy(f(nil, {
x = true, y = true, z = true
}))
end)
end) end)
describe('World:', function() describe('World:', function()
@@ -115,6 +163,11 @@ describe('tiny-ecs:', function()
timePassed = 0 timePassed = 0
end) end)
after_each(function()
world:clearSystems()
world:refresh()
end)
it("Create World", function() it("Create World", function()
assert.equals(world:getEntityCount(), 3) assert.equals(world:getEntityCount(), 3)
assert.equals(world:getSystemCount(), 2) assert.equals(world:getSystemCount(), 2)
@@ -180,9 +233,11 @@ describe('tiny-ecs:', function()
end) end)
it("Remove Entities Multiple Times", function() it("Remove Entities Multiple Times", function()
assert.equals(3, world:getEntityCount())
world:update(1) world:update(1)
world:remove(entity1, entity2, entity3) world:remove(entity1, entity2, entity3)
world:update(2) world:update(2)
assert.equals(0, world:getEntityCount())
world:remove(entity1, entity2, entity3) world:remove(entity1, entity2, entity3)
world:update(2) world:update(2)
assert.equals(2, world:getSystemCount()) assert.equals(2, world:getSystemCount())
@@ -191,7 +246,7 @@ describe('tiny-ecs:', function()
it("Add Systems Multiple Times", function() it("Add Systems Multiple Times", function()
world:update(1) world:update(1)
world:add(moveSystem, oneTimeSystem) assert.has_error(function() world:add(moveSystem, oneTimeSystem) end, "System already belongs to a World.")
world:update(2) world:update(2)
assert.equals(2, world:getSystemCount()) assert.equals(2, world:getSystemCount())
assert.equals(3, world:getEntityCount()) assert.equals(3, world:getEntityCount())
@@ -201,7 +256,7 @@ describe('tiny-ecs:', function()
world:update(1) world:update(1)
world:remove(moveSystem) world:remove(moveSystem)
world:update(2) world:update(2)
world:remove(moveSystem) assert.has_error(function() world:remove(moveSystem) end, "System does not belong to this World.")
world:update(2) world:update(2)
assert.equals(1, world:getSystemCount()) assert.equals(1, world:getSystemCount())
assert.equals(3, world:getEntityCount()) assert.equals(3, world:getEntityCount())
@@ -211,18 +266,18 @@ 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) end)
it("Sorts Entities in Sorting Systems", function() it("Sorts Entities in Sorting Systems", function()
local sortsys = tiny.sortedProcessingSystem() local sortsys = tiny.sortedProcessingSystem()
sortsys.filter = tiny.requireAll("vel") sortsys.filter = tiny.filter("vel|xform")
function sortsys:compare(e1, e2) function sortsys:compare(e1, e2)
return e1.vel.x < e2.vel.x return e1.vel.x < e2.vel.x
end end
world:add(sortsys) world:add(sortsys)
world:update(0) world:refresh()
assert.equals(sortsys.entities[1], entity2) assert.equals(sortsys.entities[1], entity2)
assert.equals(sortsys.entities[2], entity3) assert.equals(sortsys.entities[2], entity3)
assert.equals(sortsys.entities[3], entity1) assert.equals(sortsys.entities[3], entity1)
@@ -230,4 +285,8 @@ describe('tiny-ecs:', function()
end) end)
it("Doesn't pollute the global namespace", function()
assert.are.same(_G, GLOBALS)
end)
end) end)
+370 -220
View File
@@ -1,5 +1,5 @@
--[[ --[[
Copyright (c) 2015 Calvin Rose Copyright (c) 2016 Calvin Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 this software and associated documentation files (the "Software"), to deal in
@@ -23,13 +23,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- @author Calvin Rose -- @author Calvin Rose
-- @license MIT -- @license MIT
-- @copyright 2015 -- @copyright 2015
local tiny = { _VERSION = "1.1-5" } local tiny = { _VERSION = "scm" }
-- 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 tsort = table.sort
local pairs = pairs
local setmetatable = setmetatable local setmetatable = setmetatable
local type = type local type = type
local select = select local select = select
@@ -85,88 +84,121 @@ local tiny_remove
-- --
-- @section Filter -- @section Filter
-- A helper function to compile filters.
local filterJoin
-- A helper function to filters from string
local filterBuildString
do
local loadstring = loadstring or load
local function getchr(c)
return "\\" .. c:byte()
end
local function make_safe(text)
return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
end
local function filterJoinRaw(prefix, seperator, ...)
local accum = {}
local build = {}
for i = 1, select('#', ...) do
local item = select(i, ...)
if type(item) == 'string' then
accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item))
elseif type(item) == 'function' then
build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)'):format(i, i)
accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i)
else
error 'Filter token must be a string or a filter function.'
end
end
local source = ('do %s\n return function(system, e) return %s(%s) end end'):format(
table.concat(build, '\n'),
prefix,
table.concat(accum, seperator)
)
local loader, err = loadstring(source)
if err then error(err) end
return loader(...)
end
function filterJoin(...)
local state, value = pcall(filterJoinRaw, ...)
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)
subParts[#subParts + 1] = buildPart(p:sub(2, -2))
return ('\255%d'):format(#subParts)
end)
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])
else
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 ')
end
end
return table.concat(accum)
end
function filterBuildString(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 --- Makes a Filter that selects Entities with all specified Components and
-- Filters. -- Filters.
function tiny.requireAll(...) function tiny.requireAll(...)
local components = {...} return filterJoin('', ' and ', ...)
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 false
end
elseif e[c] == nil then
return false
end
end
return true
end
end end
--- Makes a Filter that selects Entities with at least one of the specified --- Makes a Filter that selects Entities with at least one of the specified
-- Components and Filters. -- Components and Filters.
function tiny.requireAny(...) function tiny.requireAny(...)
local components = {...} return filterJoin('', ' or ', ...)
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 true
end
elseif e[c] ~= nil then
return true
end
end
return false
end
end end
--- Makes a Filter that rejects Entities with all specified Components and --- Makes a Filter that rejects Entities with all specified Components and
-- Filters, and selects all other Entities. -- Filters, and selects all other Entities.
function tiny.rejectAll(...) function tiny.rejectAll(...)
local components = {...} return filterJoin('not', ' and ', ...)
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 end
--- Makes a Filter that rejects Entities with at least one of the specified --- Makes a Filter that rejects Entities with at least one of the specified
-- Components and Filters, and selects all other Entities. -- Components and Filters, and selects all other Entities.
function tiny.rejectAny(...) function tiny.rejectAny(...)
local components = {...} return filterJoin('not', ' or ', ...)
local len = #components end
return function(system, e)
local c --- Makes a Filter from a string. Syntax of `pattern` is as follows.
for i = 1, len do --
c = components[i] -- * Tokens are alphanumeric strings including underscores.
if type(c) == 'function' then -- * Tokens can be separated by |, &, or surrounded by parentheses.
if c(system, e) then -- * Tokens can be prefixed with !, and are then operated on with a boolean 'not'.
return false --
end -- Examples are best:
elseif e[c] ~= nil then -- 'a|b|c' - Matches entities with an 'a' component OR a 'b' component or a 'c' component.
return false -- 'a&!b&c' - Matches entities with an 'a' component AND NOT a 'b' component AND a 'c' component.
end -- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e'
end -- @param pattern
return true function tiny.filter(pattern)
end local state, value = pcall(filterBuildString, pattern)
if state then return value else return nil, value end
end end
--- System functions. --- System functions.
@@ -187,6 +219,10 @@ end
-- from the System. -- from the System.
-- * `function system:onModify(dt)` - Called when the System is modified by -- * `function system:onModify(dt)` - Called when the System is modified by
-- adding or removing Entities from the System. -- adding or removing Entities from the System.
-- * `function system:onAddToWorld(world)` - Called when the System is added
-- to the World, before any entities are added to the system.
-- * `function system:onRemoveFromWorld(world)` - Called when the System is
-- removed from the world, after all Entities are removed from the System.
-- --
-- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`, -- 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 -- but one can write their own filters as well. Set the Filter of a System like
@@ -206,12 +242,15 @@ end
-- * The `active` flag is whether or not the System is updated automatically. -- * The `active` flag is whether or not the System is updated automatically.
-- Inactive Systems should be updated manually or not at all via -- Inactive Systems should be updated manually or not at all via
-- `system:update(dt)`. Defaults to true. -- `system:update(dt)`. Defaults to true.
-- * The 'entities' field is an ordered list of Entities in the System. This -- * 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. -- 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 -- * The `interval` field is an optional field that makes Systems update at
-- certain intervals using buffered time, regardless of World update frequency. -- 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 -- For example, to make a System update once a second, set the System's interval
-- to 1. -- 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 -- * The `indices` field is a table of Entity keys to their indices in the
-- `entities` list. Most Systems can ignore this. -- `entities` list. Most Systems can ignore this.
-- * The `modified` flag is an indicator if the System has been modified in -- * The `modified` flag is an indicator if the System has been modified in
@@ -232,21 +271,31 @@ end
-- Update function for all Processing Systems. -- Update function for all Processing Systems.
local function processingSystemUpdate(system, 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
if preProcess then if preProcess then
preProcess(system, dt) preProcess(system, dt)
end end
if process then if process then
local len = #entities if system.nocache then
for i = 1, len do local entities = system.world.entityList
entity = entities[i] local filter = system.filter
process(system, entity, dt) if filter then
for i = 1, #entities do
local entity = entities[i]
if filter(system, entity) then
process(system, entity, dt)
end
end
end
else
local entities = system.entities
for i = 1, #entities do
process(system, entities[i], dt)
end
end end
end end
@@ -255,7 +304,7 @@ local function processingSystemUpdate(system, dt)
end end
end end
-- Sorts Systems by a function system.sort(entity1, entity2) on modify. -- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify.
local function sortedSystemOnModify(system, dt) local function sortedSystemOnModify(system, dt)
local entities = system.entities local entities = system.entities
local indices = system.indices local indices = system.indices
@@ -269,8 +318,7 @@ local function sortedSystemOnModify(system, dt)
end end
tsort(entities, sortDelegate) tsort(entities, sortDelegate)
for i = 1, #entities do for i = 1, #entities do
local entity = entities[i] indices[entities[i]] = i
indices[entity] = i
end end
end end
@@ -287,9 +335,9 @@ end
-- Processing Systems have three extra callbacks besides those inheritted from -- Processing Systems have three extra callbacks besides those inheritted from
-- vanilla Systems. -- vanilla Systems.
-- --
-- function system:preProcess(entities, dt) -- Called before iteration. -- function system:preProcess(dt) -- Called before iteration.
-- function system:process(entities, dt) -- Process each entity. -- function system:process(entity, dt) -- Process each entity.
-- function system:postProcess(entity, dt) -- Called after iteration. -- function system:postProcess(dt) -- Called after iteration.
-- --
-- Processing Systems have their own `update` method, so don't implement a -- Processing Systems have their own `update` method, so don't implement a
-- a custom `update` callback for Processing Systems. -- a custom `update` callback for Processing Systems.
@@ -342,9 +390,10 @@ end
local worldMetaTable 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
-- with default Entities and Systems.
function tiny.world(...) function tiny.world(...)
local ret = { local ret = setmetatable({
-- List of Entities to add -- List of Entities to add
entitiesToAdd = {}, entitiesToAdd = {},
@@ -352,6 +401,9 @@ function tiny.world(...)
-- List of Entities to remove -- List of Entities to remove
entitiesToRemove = {}, entitiesToRemove = {},
-- List of Entities to change
entitiesToChange = {},
-- List of Entities to add -- List of Entities to add
systemsToAdd = {}, systemsToAdd = {},
@@ -361,48 +413,53 @@ function tiny.world(...)
-- Set of Entities -- Set of Entities
entities = {}, entities = {},
-- Number of Entities in World. -- List of Entities
entityList = {},
-- 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 = {}, }, worldMetaTable)
-- 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 ret, ...
end end
--- Adds an Entity to the world. --- Adds an Entity to the world.
-- Also call this on Entities that have changed Components such that they -- Also call this on Entities that have changed Components such that they
-- match different Filters. -- match different Filters. Returns the Entity.
function tiny.addEntity(world, entity) function tiny.addEntity(world, entity)
local e2a = world.entitiesToAdd
e2a[#e2a + 1] = entity
if world.entities[entity] then if world.entities[entity] then
tiny_removeEntity(world, entity) local e2c = world.entitiesToChange
e2c[#e2c + 1] = entity
else
local e2a = world.entitiesToAdd
e2a[#e2a + 1] = 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.
function tiny.addSystem(world, system) function tiny.addSystem(world, system)
assert(system.world == nil, "System already belongs to a World.")
local s2a = world.systemsToAdd local s2a = world.systemsToAdd
s2a[#s2a + 1] = system s2a[#s2a + 1] = system
system.world = world
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
-- added Entities and Systems.
function tiny.add(world, ...) function tiny.add(world, ...)
local obj
for i = 1, select("#", ...) do for i = 1, select("#", ...) do
obj = select(i, ...) local obj = select(i, ...)
if obj then if obj then
if isSystem(obj) then if isSystem(obj) then
tiny_addSystem(world, obj) tiny_addSystem(world, obj)
@@ -411,104 +468,119 @@ function tiny.add(world, ...)
end 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.
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.
function tiny.removeSystem(world, system) function tiny.removeSystem(world, system)
assert(system.world == world, "System does not belong to this World.")
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
-- all removed Systems and Entities
function tiny.remove(world, ...) function tiny.remove(world, ...)
local obj
for i = 1, select("#", ...) do for i = 1, select("#", ...) do
obj = select(i, ...) local obj = select(i, ...)
if obj then if obj then
if isSystem(obj) then if isSystem(obj) then
tiny_removeSystem(world, obj) tiny_removeSystem(world, obj)
else -- Assume obj is an Entity else -- Assume obj is an Entity
tiny_removeEntity(world, obj) tiny_removeEntity(world, obj)
end end
end end
end end
return ...
end end
tiny_remove = tiny.remove tiny_remove = tiny.remove
-- Adds and removes Systems that have been marked from the World. -- Adds and removes Systems that have been marked from the World.
function tiny_manageSystems(world) function tiny_manageSystems(world)
local s2a, s2r = world.systemsToAdd, world.systemsToRemove local s2a, s2r = world.systemsToAdd, world.systemsToRemove
-- Early exit -- Early exit
if #s2a == 0 and #s2r == 0 then if #s2a == 0 and #s2r == 0 then
return return
end end
local systemIndices = world.systemIndices world.systemsToAdd = {}
local entities = world.entities world.systemsToRemove = {}
local systems = world.systems
local system, index, filter
local entityList, entityIndices, entityIndex, onRemove, onAdd
-- Remove Systems local worldEntityList = world.entityList
for i = 1, #s2r do local systems = world.systems
system = s2r[i]
index = systemIndices[system] -- Remove Systems
if index then for i = 1, #s2r do
onRemove = system.onRemove local system = s2r[i]
if onRemove then local index = system.index
entityList = system.entities local onRemove = system.onRemove
for j = 1, #entityList do if onRemove and not system.nocache then
onRemove(system, entityList[j]) local entityList = system.entities
end for j = 1, #entityList do
end onRemove(system, entityList[j])
systemIndices[system] = nil
tremove(systems, index)
for j = index, #systems do
systemIndices[systems[j]] = j
end
end end
s2r[i] = nil
-- Clean up System
system.world = nil
system.entities = nil
system.indices = nil
end end
tremove(systems, index)
for j = index, #systems do
systems[j].index = j
end
local onRemoveFromWorld = system.onRemoveFromWorld
if onRemoveFromWorld then
onRemoveFromWorld(system, world)
end
s2r[i] = nil
-- Add Systems -- Clean up System
for i = 1, #s2a do system.world = nil
system = s2a[i] system.entities = nil
if not systemIndices[system] then system.indices = nil
entityList = {} system.index = nil
entityIndices = {} end
system.entities = entityList
system.indices = entityIndices
if system.active == nil then
system.active = true
end
system.modified = true
system.world = world
index = #systems + 1
systemIndices[system] = index
systems[index] = system
-- Try to add Entities -- Add Systems
onAdd = system.onAdd for i = 1, #s2a do
filter = system.filter local system = s2a[i]
if systems[system.index or 0] ~= system then
if not system.nocache then
system.entities = {}
system.indices = {}
end
if system.active == nil then
system.active = true
end
system.modified = true
system.world = world
local index = #systems + 1
system.index = index
systems[index] = system
local onAddToWorld = system.onAddToWorld
if onAddToWorld then
onAddToWorld(system, world)
end
-- Try to add Entities
if not system.nocache then
local entityList = system.entities
local entityIndices = system.indices
local onAdd = system.onAdd
local filter = system.filter
if filter then if filter then
for entity in pairs(entities) do for j = 1, #worldEntityList do
local entity = worldEntityList[j]
if filter(system, entity) then if filter(system, entity) then
entityIndex = #entityList + 1 local entityIndex = #entityList + 1
entityList[entityIndex] = entity entityList[entityIndex] = entity
entityIndices[entity] = entityIndex entityIndices[entity] = entityIndex
if onAdd then if onAdd then
@@ -518,82 +590,135 @@ function tiny_manageSystems(world)
end end
end end
end end
s2a[i] = nil
end end
s2a[i] = nil
end
end end
-- Adds and removes Entities that have been marked. -- Adds, removes, and changes Entities that have been marked.
function tiny_manageEntities(world) function tiny_manageEntities(world)
local e2a, e2r = world.entitiesToAdd, world.entitiesToRemove
local e2a = world.entitiesToAdd
local e2r = world.entitiesToRemove
local e2c = world.entitiesToChange
-- Early exit -- Early exit
if #e2a == 0 and #e2r == 0 then if #e2a == 0 and #e2r == 0 and #e2c == 0 then
return return
end end
world.entitiesToChange = {}
world.entitiesToAdd = {}
world.entitiesToRemove = {}
local entities = world.entities local entities = world.entities
local entityList = world.entityList
local systems = world.systems local systems = world.systems
local entityCount = world.entityCount local entityCount = world.entityCount
local entity, system, index
local onRemove, onAdd, ses, seis, filter, tmpEntity -- Change Entities
for i = 1, #e2c do
local entity = e2c[i]
if entities[entity] then
for j = 1, #systems do
local system = systems[j]
if not system.nocache then
local ses = system.entities
local seis = system.indices
local index = seis[entity]
local filter = system.filter
if filter and filter(system, entity) then
if not index then
system.modified = true
index = #ses + 1
ses[index] = entity
seis[entity] = index
local onAdd = system.onAdd
if onAdd then
onAdd(system, entity)
end
end
elseif index then
system.modified = true
local tmpEntity = ses[#ses]
ses[index] = tmpEntity
seis[tmpEntity] = index
seis[entity] = nil
ses[#ses] = nil
local onRemove = system.onRemove
if onRemove then
onRemove(system, entity)
end
end
end
end
end
e2c[i] = nil
end
-- Remove Entities -- Remove Entities
for i = 1, #e2r do for i = 1, #e2r do
entity = e2r[i] local entity = e2r[i]
if entities[entity] then e2r[i] = nil
local listIndex = entities[entity]
if listIndex then
-- Remove Entity from world state
local lastEntity = entityList[#entityList]
entities[lastEntity] = listIndex
entities[entity] = nil entities[entity] = nil
entityList[listIndex] = lastEntity
entityList[#entityList] = nil
entityCount = entityCount - 1 entityCount = entityCount - 1
-- Remove from cached systems
for j = 1, #systems do for j = 1, #systems do
system = systems[j] local system = systems[j]
ses = system.entities if not system.nocache then
seis = system.indices local ses = system.entities
index = seis[entity] local seis = system.indices
local index = seis[entity]
if index then if index then
system.modified = true system.modified = true
tmpEntity = ses[#ses] local 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
onRemove = system.onRemove local onRemove = system.onRemove
if onRemove then if onRemove then
onRemove(system, entity) onRemove(system, entity)
end
end end
end end
end end
end end
e2r[i] = nil
end end
-- Add Entities -- Add Entities
for i = 1, #e2a do for i = 1, #e2a do
entity = e2a[i] local entity = e2a[i]
if not entities[entity] then if not entities[entity] then
entities[entity] = true local listIndex = #entityList + 1
entities[entity] = listIndex
entityList[listIndex] = entity
entityCount = entityCount + 1 entityCount = entityCount + 1
for j = 1, #systems do for j = 1, #systems do
system = systems[j] local system = systems[j]
ses = system.entities if not system.nocache then
seis = system.indices local ses = system.entities
filter = system.filter local seis = system.indices
if filter and filter(system, entity) then local filter = system.filter
system.modified = true if filter and filter(system, entity) then
index = #ses + 1 system.modified = true
ses[index] = entity local index = #ses + 1
seis[entity] = index ses[index] = entity
onAdd = system.onAdd seis[entity] = index
if onAdd then local onAdd = system.onAdd
onAdd(system, entity) if onAdd then
onAdd(system, entity)
end
end end
end end
end end
end end
e2a[i] = nil e2a[i] = nil
end end
@@ -602,6 +727,25 @@ function tiny_manageEntities(world)
world.entityCount = entityCount world.entityCount = entityCount
end 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 tiny.refresh(world)
tiny_manageSystems(world)
tiny_manageEntities(world)
local systems = world.systems
for i = 1, #systems do
local system = systems[i]
if system.active then
local onModify = system.onModify
if onModify and system.modified then
onModify(system, 0)
end
system.modified = false
end
end
end
--- Updates the World by dt (delta time). Takes an optional parameter, `filter`, --- Updates the World by dt (delta time). Takes an optional parameter, `filter`,
-- which is a Filter that selects Systems from the World, and updates only those -- which is a Filter that selects Systems from the World, and updates only those
-- Systems. If `filter` is not supplied, all Systems are updated. Put this -- Systems. If `filter` is not supplied, all Systems are updated. Put this
@@ -612,23 +756,22 @@ function tiny.update(world, dt, filter)
tiny_manageEntities(world) tiny_manageEntities(world)
local systems = world.systems local systems = world.systems
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
system = systems[i] local system = systems[i]
if system.active and ((not filter) or filter(world, system)) then if system.active and ((not filter) or filter(world, system)) then
-- Call the modify callback on Systems that have been modified. -- Call the modify callback on Systems that have been modified.
onModify = system.onModify local onModify = system.onModify
if onModify and system.modified then if onModify and system.modified then
onModify(system, dt) onModify(system, dt)
end end
--Update Systems that have an update method (most Systems) -- Update Systems that have an update method (most Systems)
update = system.update local update = system.update
if update then if update then
interval = system.interval local interval = system.interval
if interval then if interval then
local bufferedTime = (system.bufferedTime or 0) + dt local bufferedTime = (system.bufferedTime or 0) + dt
while bufferedTime >= interval do while bufferedTime >= interval do
@@ -650,8 +793,9 @@ end
--- Removes all Entities from the World. --- Removes all Entities from the World.
function tiny.clearEntities(world) function tiny.clearEntities(world)
for e in pairs(world.entities) do local el = world.entityList
tiny_removeEntity(world, e) for i = 1, #el do
tiny_removeEntity(world, el[i])
end end
end end
@@ -673,26 +817,28 @@ 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`.
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, and returns the old index. Changes --- 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 -- the order in which they Systems processed, because lower indexed Systems are
-- processed first. -- processed first. Returns the old system.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 system = systems[oldIndex]
if index < 0 then
index = tiny.getSystemCount(world) + 1 + index
end
tremove(systems, oldIndex) tremove(systems, oldIndex)
tinsert(systems, index, system) 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]] = i systems[i].index = i
end end
return oldIndex return oldIndex
@@ -707,6 +853,7 @@ worldMetaTable = {
remove = tiny.remove, remove = tiny.remove,
removeEntity = tiny.removeEntity, removeEntity = tiny.removeEntity,
removeSystem = tiny.removeSystem, removeSystem = tiny.removeSystem,
refresh = tiny.refresh,
update = tiny.update, update = tiny.update,
clearEntities = tiny.clearEntities, clearEntities = tiny.clearEntities,
clearSystems = tiny.clearSystems, clearSystems = tiny.clearSystems,
@@ -714,7 +861,10 @@ worldMetaTable = {
getSystemCount = tiny.getSystemCount, getSystemCount = tiny.getSystemCount,
getSystemIndex = tiny.getSystemIndex, getSystemIndex = tiny.getSystemIndex,
setSystemIndex = tiny.setSystemIndex setSystemIndex = tiny.setSystemIndex
} },
__tostring = function(self)
return "<tiny-ecs_World>"
end
} }
return tiny return tiny