54 Commits

Author SHA1 Message Date
Calvin Rose
821914795d Merge pull request #25 from yangruihan/master
fix README.md example error
2020-11-03 07:18:04 -06:00
C.y
2beca9a0ad fix README.md example error 2020-11-03 19:35:44 +08:00
Calvin Rose
afd7d326d7 Fix #22 - manage systems before setSystemIndex 2020-08-12 08:28:57 -05:00
Calvin Rose
e095e10d44 Merge pull request #21 from RantingBob/patch-1
Fixed nil entitylist in nocache system
2020-02-23 21:21:05 -06:00
Bob Gardner
3c4b3a7f68 Fixed nil entitylist in nocache system
Systems that set nocache to true would fail as the entity list was set to system.world.entityList but should have been system.world.entities.
2020-02-23 16:45:01 -08:00
Calvin Rose
0f17f116ab Update .travis.yml 2019-10-13 00:14:12 -05:00
Calvin Rose
584be2c952 Merge pull request #20 from firoxer/patch-1 2019-10-13 01:09:55 -04:00
Oliver Vartiainen
36364df4e6 Fix a few spellings in the README
Hi! While reading the README I noticed a few things that could be corrected. Here they are. Thanks for the project, it's very interesting!
2019-10-12 22:02:37 +03:00
Calvin Rose
65de2a9c6f Merge pull request #19 from wqferr/fix-init-lua
Fix init.lua
2019-07-23 08:40:27 -05:00
William Ferreira
4631216c27 Fix init.lua 2019-07-23 00:40:11 -03:00
Calvin Rose
7c7de1db0c Update README.md 2019-01-23 16:57:20 -05:00
bakpakin
fe7e2854de Update doc. 2016-08-10 21:48:20 -04:00
bakpakin
311943e070 Fix doc bug. 2016-08-10 21:42:18 -04:00
bakpakin
8a3c22e74f Add new rockspec. 2016-08-10 19:45:58 -04:00
bakpakin
a0fa0904f6 Update filer documentation to be more explicit. 2016-08-10 19:36:20 -04:00
Calvin Rose
80bb6b8d31 Remove world.entityCount and world.entityList. 2016-08-10 17:23:48 -04:00
bakpakin
83d806011b Combine the 'add entities' procedure in manage_entities with 'change
entities'
2016-08-10 07:23:05 -04:00
Calvin Rose
d720d361e6 Remove redundant non-nil check 2016-07-13 09:43:36 -04:00
Calvin Rose
d94b178670 Make source pass luacheck. Remove tiny.getSystemIndex(). 2016-07-05 17:15:18 -04:00
Calvin Rose
5997f949cd Update rockspecs. 2016-06-19 11:01:57 -04:00
Calvin Rose
ea6009a8d2 Add a rockspec. 2016-06-18 22:47:06 -04:00
Calvin Rose
7da4efb7df Edit README and remove version from library code for easier release cycle.
I don't like git hooks for that kind of thing and its not really necessary.
2016-06-18 13:35:26 -04:00
Calvin Rose
9bc366a2fb Merge branch 'master' of https://github.com/bakpakin/tiny-ecs 2016-06-18 13:22:08 -04:00
Calvin Rose
80b7b48703 Conform to 80 column lines. 2016-06-18 13:21:34 -04:00
bakpakin
0488bff1d9 Fix slightly incorrect pattern for matching filters (only matters on incorrect filters). 2016-05-19 18:18:39 +08:00
bakpakin
ee192d94af Add init.lua for easy require if used as a submodule. 2016-05-19 17:50:54 +08:00
Calvin Rose
a518e15e44 Merge pull request #7 from tst2005/patch-1
typo fix
2016-04-27 10:30:25 -04:00
TsT
37c2c49300 typo fix
Currenty the `doc = doc` is equal to `doc = nil`.
The "doc" directory is used because it's the internal LDoc default.
2016-04-27 13:55:10 +02:00
Calvin Rose
195c882bbd Add documentation for preWrap and postWrap. 2016-04-13 15:01:53 -04:00
Calvin Rose
29a4a02f30 Add tests for postWrap and preWrap. 2016-04-13 14:39:39 -04:00
Calvin Rose
df5c84033b Add code for preWrap and postWrap to system. 2016-04-13 14:28:38 -04:00
bakpakin
d9c296dae6 Update documentation. 2016-03-06 18:20:18 -05:00
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
20 changed files with 669 additions and 261 deletions
+39
View File
@@ -0,0 +1,39 @@
language: c
env:
global:
- LUAROCKS_BASE=luarocks-3.2.1
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:
+22 -16
View File
@@ -1,27 +1,34 @@
# tiny-ecs #
[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE)
## NOTE
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
Although there have been almost no commits in several years, this
project is not abandoned. tiny-ecs is, for most intents and
purposes, finished, and no bugs have been brought to my attention in a while.
New issues on GitHub will be addressed.
[![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)
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
for simulating large and complex systems. For more explanation on Entity
Component Systems, here is some
[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 object-oriented programming in Lua because
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.
For an example on how to use tiny-ecs with object-oriented lua, take a look at the
demo branch, specifically the systems and entities sub-directories.
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
demo branch, specifically the systems and entities subdirectories.
## Overview ##
Tiny-ecs has four important types: Worlds, Filters, Systems, and Entities.
Entities, however, can be any lua table, and Filters are just functions that
Entities, however, can be any Lua table, and Filters are just functions that
take an Entity as a parameter.
### Entities ###
Entities are simply lua tables of data that gets processed by Systems. Entities
should contain primarily data rather than code, as it is the Systems's job to
Entities are simply Lua tables of data that gets processed by Systems. Entities
should contain primarily data rather than code, as it is the System's job to
do logic on data. Henceforth, a key-value pair in an Entity will
be referred to as a Component.
@@ -37,7 +44,7 @@ provides functions for creating Systems easily, as well as creating Systems that
can be used in an object oriented fashion.
### 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
tiny-ecs provides some functions for generating common ones, like selecting
only Entities that have all required components.
@@ -49,7 +56,7 @@ local talkingSystem = tiny.processingSystem()
talkingSystem.filter = tiny.requireAll("name", "mass", "phrase")
function talkingSystem:process(e, dt)
e.mass = e.mass + dt * 3
print(e.name .. ", who weighs " .. e.mass .. " pounds, says, \"" .. e.phrase .. "\"")
print(("%s who weighs %d pounds, says %q."):format(e.name, e.mass, e.phrase))
end
local joe = {
@@ -68,17 +75,16 @@ 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'.
please use a tagged release or use LuaRocks.
## Luarocks ##
Tiny-ecs is also on [Luarocks](https://luarocks.org/) and can be installed with
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.
[LÖVE](https://love2d.org/), an amazing game framework for Lua.
## Testing ##
Tiny-ecs uses [busted](http://olivinelabs.com/busted/) for testing. Install and run
+1 -1
View File
@@ -5,7 +5,7 @@ backtick_references = true
format = 'discount'
title = "tiny-ecs API"
one = true
dir = doc
dir = "doc"
style = '!fixed'
package = 'tiny-ecs'
not_luadoc = true
+6
View File
@@ -0,0 +1,6 @@
-- Helper Lua file for easy require if tiny-ecs is used as a git submodule or
-- folder. Not needed in many cases, including luarocks distribution.
local args = {...}
local directory = args[1]
return require(directory .. '.tiny')
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.0-2"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.0-2"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-1"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-1"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-2"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-2"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-3"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-3"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-4"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-4"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-5"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-5"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+1 -1
View File
@@ -2,7 +2,7 @@ package = "tiny-ecs"
version = "1.1-6"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = "1.1-6"
tag = version
}
description = {
summary = "Entity Component System for Lua.",
+24
View File
@@ -0,0 +1,24 @@
package = "tiny-ecs"
version = "1.1-7"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = version
}
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 = version
}
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 = version
}
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-2"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = version
}
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-3"
source = {
url = "git://github.com/bakpakin/tiny-ecs",
tag = version
}
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"
}
}
+94 -4
View File
@@ -1,3 +1,8 @@
local GLOBALS = {}
for k, v in pairs(_G) do
GLOBALS[k] = v
end
local tiny = require "tiny"
local function deep_copy(x)
@@ -85,6 +90,49 @@ describe('tiny-ecs:', function()
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)
describe('World:', function()
@@ -115,6 +163,11 @@ describe('tiny-ecs:', function()
timePassed = 0
end)
after_each(function()
world:clearSystems()
world:refresh()
end)
it("Create World", function()
assert.equals(world:getEntityCount(), 3)
assert.equals(world:getSystemCount(), 2)
@@ -180,9 +233,11 @@ describe('tiny-ecs:', function()
end)
it("Remove Entities Multiple Times", function()
assert.equals(3, world:getEntityCount())
world:update(1)
world:remove(entity1, entity2, entity3)
world:update(2)
assert.equals(0, world:getEntityCount())
world:remove(entity1, entity2, entity3)
world:update(2)
assert.equals(2, world:getSystemCount())
@@ -191,7 +246,7 @@ describe('tiny-ecs:', function()
it("Add Systems Multiple Times", function()
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)
assert.equals(2, world:getSystemCount())
assert.equals(3, world:getEntityCount())
@@ -201,7 +256,7 @@ describe('tiny-ecs:', function()
world:update(1)
world:remove(moveSystem)
world:update(2)
world:remove(moveSystem)
assert.has_error(function() world:remove(moveSystem) end, "System does not belong to this World.")
world:update(2)
assert.equals(1, world:getSystemCount())
assert.equals(3, world:getEntityCount())
@@ -217,17 +272,52 @@ describe('tiny-ecs:', function()
it("Sorts Entities in Sorting Systems", function()
local sortsys = tiny.sortedProcessingSystem()
sortsys.filter = tiny.requireAll("vel")
sortsys.filter = tiny.filter("vel|xform")
function sortsys:compare(e1, e2)
return e1.vel.x < e2.vel.x
end
world:add(sortsys)
world:update(0)
world:refresh()
assert.equals(sortsys.entities[1], entity2)
assert.equals(sortsys.entities[2], entity3)
assert.equals(sortsys.entities[3], entity1)
end)
it("Runs preWrap and postWrap for systems.", function()
local str = ""
local sys1 = tiny.system()
local sys2 = tiny.system()
function sys1:preWrap(dt)
str = str .. "<"
end
function sys2:preWrap(dt)
str = str .. "{"
end
function sys1:postWrap(dt)
str = str .. ">"
end
function sys2:postWrap(dt)
str = str .. "}"
end
world:add(sys1, sys2)
world:update(1)
assert.equals(str, "{<>}")
end)
end)
it("Doesn't pollute the global namespace", function()
assert.are.same(_G, GLOBALS)
end)
it("Can set system indices", function()
local world = tiny.world()
local systemA = tiny.system()
local systemB = tiny.system()
world:addSystem(systemA)
world:addSystem(systemB)
world:setSystemIndex(systemA, 1)
assert(true)
end)
end)
+8
View File
@@ -0,0 +1,8 @@
local tiny = require('tiny')
local world = tiny.world()
local systemA = tiny.system()
local systemB = tiny.system()
world:addSystem(systemA)
world:addSystem(systemB)
world:setSystemIndex(systemA, 1)
+371 -232
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
this software and associated documentation files (the "Software"), to deal in
@@ -22,14 +22,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- @module tiny-ecs
-- @author Calvin Rose
-- @license MIT
-- @copyright 2015
local tiny = { _VERSION = "1.1-6" }
-- @copyright 2016
local tiny = {}
-- Local versions of standard lua functions
local tinsert = table.insert
local tremove = table.remove
local tsort = table.sort
local pairs = pairs
local setmetatable = setmetatable
local type = type
local select = select
@@ -42,12 +41,13 @@ local tiny_addSystem
local tiny_add
local tiny_removeEntity
local tiny_removeSystem
local tiny_remove
--- Filter functions.
-- 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.
-- value indicating if the Entity should be processed by the System. A truthy
-- value includes the entity, while a falsey (nil or false) value excludes the
-- entity.
--
-- 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
@@ -85,88 +85,125 @@ local tiny_remove
--
-- @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 = ('%s\nreturn function(system, e) return %s(%s) 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
-- Filters.
function tiny.requireAll(...)
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 false
end
elseif e[c] == nil then
return false
end
end
return true
end
return filterJoin('', ' and ', ...)
end
--- Makes a Filter that selects Entities with at least one of the specified
-- Components and Filters.
function tiny.requireAny(...)
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 true
end
elseif e[c] ~= nil then
return true
end
end
return false
end
return filterJoin('', ' or ', ...)
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
return filterJoin('not', ' and ', ...)
end
--- Makes a Filter that rejects Entities with at least one of the specified
-- Components and Filters, and selects all other Entities.
function tiny.rejectAny(...)
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
return filterJoin('not', ' or ', ...)
end
--- Makes a Filter from a string. Syntax of `pattern` is as follows.
--
-- * Tokens are alphanumeric strings including underscores.
-- * Tokens can be separated by |, &, or surrounded by parentheses.
-- * Tokens can be prefixed with !, and are then inverted.
--
-- Examples are best:
-- 'a|b|c' - Matches entities with an 'a' OR 'b' OR 'c'.
-- 'a&!b&c' - Matches entities with an 'a' AND NOT 'b' AND 'c'.
-- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e'
-- @param pattern
function tiny.filter(pattern)
local state, value = pcall(filterBuildString, pattern)
if state then return value else return nil, value end
end
--- System functions.
@@ -187,6 +224,23 @@ end
-- from the System.
-- * `function system:onModify(dt)` - Called when the System is modified by
-- 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.
-- * `function system:preWrap(dt)` - Called on each system before update is
-- called on any system.
-- * `function system:postWrap(dt)` - Called on each system in reverse order
-- after update is called on each system. The idea behind `preWrap` and
-- `postWrap` is to allow for systems that modify the behavior of other systems.
-- Say there is a DrawingSystem, which draws sprites to the screen, and a
-- PostProcessingSystem, that adds some blur and bloom effects. In the preWrap
-- method of the PostProcessingSystem, the System could set the drawing target
-- for the DrawingSystem to a special buffer instead the screen. In the postWrap
-- method, the PostProcessingSystem could then modify the buffer and render it
-- to the screen. In this setup, the PostProcessingSystem would be added to the
-- World after the drawingSystem (A similar but less flexible behavior could
-- be accomplished with a single custom update function in the DrawingSystem).
--
-- 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
@@ -222,6 +276,16 @@ end
-- in the next update, if it has one. This is usually managed by tiny-ecs, so
-- users should mostly ignore this, too.
--
-- There is another option to (hopefully) increase performance in systems that
-- have items added to or removed from them often, and have lots of entities in
-- them. Setting the `nocache` field of the system might improve performance.
-- It is still experimental. There are some restriction to systems without
-- caching, however.
--
-- * There is no `entities` table.
-- * Callbacks such onAdd, onRemove, and onModify will never be called
-- * Noncached systems cannot be sorted (There is no entities list to sort).
--
-- @section System
-- Use an empty table as a key for identifying Systems. Any table that contains
@@ -235,21 +299,31 @@ end
-- Update function for all Processing Systems.
local function processingSystemUpdate(system, dt)
local entities = system.entities
local preProcess = system.preProcess
local process = system.process
local postProcess = system.postProcess
local entity
if preProcess then
preProcess(system, dt)
end
if process then
local len = #entities
for i = 1, len do
entity = entities[i]
process(system, entity, dt)
if system.nocache then
local entities = system.world.entities
local filter = system.filter
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
@@ -258,8 +332,8 @@ local function processingSystemUpdate(system, dt)
end
end
-- Sorts Systems by a function system.sort(entity1, entity2) on modify.
local function sortedSystemOnModify(system, dt)
-- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify.
local function sortedSystemOnModify(system)
local entities = system.entities
local indices = system.indices
local sortDelegate = system.sortDelegate
@@ -272,8 +346,7 @@ local function sortedSystemOnModify(system, dt)
end
tsort(entities, sortDelegate)
for i = 1, #entities do
local entity = entities[i]
indices[entity] = i
indices[entities[i]] = i
end
end
@@ -290,9 +363,9 @@ end
-- 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.
-- function system:preProcess(dt) -- Called before iteration.
-- function system:process(entity, dt) -- Process each entity.
-- function system:postProcess(dt) -- Called after iteration.
--
-- Processing Systems have their own `update` method, so don't implement a
-- a custom `update` callback for Processing Systems.
@@ -348,14 +421,14 @@ local worldMetaTable
-- Can optionally add default Systems and Entities. Returns the new World along
-- with default Entities and Systems.
function tiny.world(...)
local ret = {
-- List of Entities to add
entitiesToAdd = {},
local ret = setmetatable({
-- List of Entities to remove
entitiesToRemove = {},
-- List of Entities to change
entitiesToChange = {},
-- List of Entities to add
systemsToAdd = {},
@@ -365,37 +438,34 @@ function tiny.world(...)
-- Set of Entities
entities = {},
-- Number of Entities in World
entityCount = 0,
-- List of Systems
systems = {}
}
}, worldMetaTable)
tiny_add(ret, ...)
tiny_manageSystems(ret)
tiny_manageEntities(ret)
return setmetatable(ret, worldMetaTable), ...
return ret, ...
end
--- Adds an Entity to the world.
-- Also call this on Entities that have changed Components such that they
-- match different Filters. Returns the Entity.
function tiny.addEntity(world, entity)
local e2a = world.entitiesToAdd
e2a[#e2a + 1] = entity
if world.entities[entity] then
tiny_removeEntity(world, entity)
end
local e2c = world.entitiesToChange
e2c[#e2c + 1] = entity
return entity
end
tiny_addEntity = tiny.addEntity
--- Adds a System to the world. Returns the System.
function tiny.addSystem(world, system)
assert(system.world == nil, "System already belongs to a World.")
local s2a = world.systemsToAdd
s2a[#s2a + 1] = system
system.world = world
return system
end
tiny_addSystem = tiny.addSystem
@@ -403,9 +473,8 @@ tiny_addSystem = tiny.addSystem
--- Shortcut for adding multiple Entities and Systems to the World. Returns all
-- added Entities and Systems.
function tiny.add(world, ...)
local obj
for i = 1, select("#", ...) do
obj = select(i, ...)
local obj = select(i, ...)
if obj then
if isSystem(obj) then
tiny_addSystem(world, obj)
@@ -418,7 +487,7 @@ function tiny.add(world, ...)
end
tiny_add = tiny.add
--- Removes an Entity to the World. Returns the Entity.
--- Removes an Entity from the World. Returns the Entity.
function tiny.removeEntity(world, entity)
local e2r = world.entitiesToRemove
e2r[#e2r + 1] = entity
@@ -428,6 +497,7 @@ tiny_removeEntity = tiny.removeEntity
--- Removes a System from the world. Returns the System.
function tiny.removeSystem(world, system)
assert(system.world == world, "System does not belong to this World.")
local s2r = world.systemsToRemove
s2r[#s2r + 1] = system
return system
@@ -435,87 +505,96 @@ end
tiny_removeSystem = tiny.removeSystem
--- Shortcut for removing multiple Entities and Systems from the World. Returns
-- all rmeove Systems and Entities
-- all removed Systems and Entities
function tiny.remove(world, ...)
local obj
for i = 1, select("#", ...) do
obj = select(i, ...)
local obj = select(i, ...)
if obj then
if isSystem(obj) then
tiny_removeSystem(world, obj)
else -- Assume obj is an Entity
tiny_removeEntity(world, obj)
tiny_removeEntity(world, obj)
end
end
end
return ...
end
tiny_remove = tiny.remove
-- Adds and removes Systems that have been marked from the World.
function tiny_manageSystems(world)
local s2a, s2r = world.systemsToAdd, world.systemsToRemove
local s2a, s2r = world.systemsToAdd, world.systemsToRemove
-- Early exit
if #s2a == 0 and #s2r == 0 then
return
end
-- Early exit
if #s2a == 0 and #s2r == 0 then
return
end
local entities = world.entities
local systems = world.systems
local system, index, filter
local entityList, entityIndices, entityIndex, onRemove, onAdd
world.systemsToAdd = {}
world.systemsToRemove = {}
-- Remove Systems
for i = 1, #s2r do
system = s2r[i]
index = system.index
if system.world == world then
onRemove = system.onRemove
if onRemove then
entityList = system.entities
for j = 1, #entityList do
onRemove(system, entityList[j])
end
end
tremove(systems, index)
for j = index, #systems do
systems[j].index = j
end
local worldEntityList = world.entities
local systems = world.systems
-- Remove Systems
for i = 1, #s2r do
local system = s2r[i]
local index = system.index
local onRemove = system.onRemove
if onRemove and not system.nocache then
local entityList = system.entities
for j = 1, #entityList do
onRemove(system, entityList[j])
end
s2r[i] = nil
-- Clean up System
system.world = nil
system.entities = nil
system.indices = nil
system.index = nil
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
for i = 1, #s2a do
system = s2a[i]
if systems[system.index] ~= system then
entityList = {}
entityIndices = {}
system.entities = entityList
system.indices = entityIndices
if system.active == nil then
system.active = true
end
system.modified = true
system.world = world
index = #systems + 1
system.index = index
systems[index] = system
-- Clean up System
system.world = nil
system.entities = nil
system.indices = nil
system.index = nil
end
-- Try to add Entities
onAdd = system.onAdd
filter = system.filter
-- Add Systems
for i = 1, #s2a do
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
for entity in pairs(entities) do
for j = 1, #worldEntityList do
local entity = worldEntityList[j]
if filter(system, entity) then
entityIndex = #entityList + 1
local entityIndex = #entityList + 1
entityList[entityIndex] = entity
entityIndices[entity] = entityIndex
if onAdd then
@@ -525,88 +604,126 @@ function tiny_manageSystems(world)
end
end
end
s2a[i] = nil
end
s2a[i] = nil
end
end
-- Adds and removes Entities that have been marked.
-- Adds, removes, and changes Entities that have been marked.
function tiny_manageEntities(world)
local e2a, e2r = world.entitiesToAdd, world.entitiesToRemove
local e2r = world.entitiesToRemove
local e2c = world.entitiesToChange
-- Early exit
if #e2a == 0 and #e2r == 0 then
if #e2r == 0 and #e2c == 0 then
return
end
world.entitiesToChange = {}
world.entitiesToRemove = {}
local entities = world.entities
local systems = world.systems
local entityCount = world.entityCount
local entity, system, index
local onRemove, onAdd, ses, seis, filter, tmpEntity
-- Remove Entities
for i = 1, #e2r do
entity = e2r[i]
if entities[entity] then
entities[entity] = nil
entityCount = entityCount - 1
for j = 1, #systems do
system = systems[j]
ses = system.entities
seis = system.indices
index = seis[entity]
if index then
-- Change Entities
for i = 1, #e2c do
local entity = e2c[i]
-- Add if needed
if not entities[entity] then
local index = #entities + 1
entities[entity] = index
entities[index] = entity
end
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
tmpEntity = ses[#ses]
local tmpEntity = ses[#ses]
ses[index] = tmpEntity
seis[tmpEntity] = index
seis[entity] = nil
ses[#ses] = nil
onRemove = system.onRemove
local onRemove = system.onRemove
if onRemove then
onRemove(system, entity)
end
end
end
end
e2r[i] = nil
e2c[i] = nil
end
-- Add Entities
for i = 1, #e2a do
entity = e2a[i]
if not entities[entity] then
entities[entity] = true
entityCount = entityCount + 1
-- Remove Entities
for i = 1, #e2r do
local entity = e2r[i]
e2r[i] = nil
local listIndex = entities[entity]
if listIndex then
-- Remove Entity from world state
local lastEntity = entities[#entities]
entities[lastEntity] = listIndex
entities[entity] = nil
entities[listIndex] = lastEntity
entities[#entities] = nil
-- Remove from cached systems
for j = 1, #systems do
system = systems[j]
ses = system.entities
seis = system.indices
filter = system.filter
if filter and filter(system, entity) then
system.modified = true
index = #ses + 1
ses[index] = entity
seis[entity] = index
onAdd = system.onAdd
if onAdd then
onAdd(system, entity)
local system = systems[j]
if not system.nocache then
local ses = system.entities
local seis = system.indices
local index = seis[entity]
if 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
e2a[i] = nil
end
end
-- Update Entity count
world.entityCount = entityCount
--- 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 = #systems, 1, -1 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`,
@@ -619,30 +736,38 @@ function tiny.update(world, dt, filter)
tiny_manageEntities(world)
local systems = world.systems
local system, update, interval, onModify
-- Iterate through Systems IN ORDER
for i = 1, #systems do
system = systems[i]
if system.active and ((not filter) or filter(world, system)) then
-- Iterate through Systems IN REVERSE ORDER
for i = #systems, 1, -1 do
local system = systems[i]
if system.active then
-- Call the modify callback on Systems that have been modified.
onModify = system.onModify
local onModify = system.onModify
if onModify and system.modified then
onModify(system, dt)
end
local preWrap = system.preWrap
if preWrap and
((not filter) or filter(world, system)) then
preWrap(system, dt)
end
end
end
-- Iterate through Systems IN ORDER
for i = 1, #systems do
local system = systems[i]
if system.active and ((not filter) or filter(world, system)) then
-- Update Systems that have an update method (most Systems)
update = system.update
local update = system.update
if update then
interval = system.interval
local 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
update(system, interval)
end
system.bufferedTime = bufferedTime
else
@@ -653,12 +778,24 @@ function tiny.update(world, dt, filter)
system.modified = false
end
end
-- Iterate through Systems IN ORDER AGAIN
for i = 1, #systems do
local system = systems[i]
local postWrap = system.postWrap
if postWrap and system.active and
((not filter) or filter(world, system)) then
postWrap(system, dt)
end
end
end
--- Removes all Entities from the World.
function tiny.clearEntities(world)
for e in pairs(world.entities) do
tiny_removeEntity(world, e)
local el = world.entities
for i = 1, #el do
tiny_removeEntity(world, el[i])
end
end
@@ -672,27 +809,26 @@ end
--- Gets number of Entities in the World.
function tiny.getEntityCount(world)
return world.entityCount
return #world.entities
end
--- Gets number of Systems in World.
function tiny.getSystemCount(world)
return #(world.systems)
end
--- Gets the index of the System in the World.
-- A simpler alternative is `system.index`.
function tiny.getSystemIndex(world, system)
return system.index
return #world.systems
end
--- Sets the index of a System in the World, and returns the old index. Changes
-- the order in which they Systems processed, because lower indexed Systems are
-- processed first. Returns the old system.index.
function tiny.setSystemIndex(world, system, index)
tiny_manageSystems(world)
local oldIndex = system.index
local systems = world.systems
if index < 0 then
index = tiny.getSystemCount(world) + 1 + index
end
tremove(systems, oldIndex)
tinsert(systems, index, system)
@@ -712,14 +848,17 @@ worldMetaTable = {
remove = tiny.remove,
removeEntity = tiny.removeEntity,
removeSystem = tiny.removeSystem,
refresh = tiny.refresh,
update = tiny.update,
clearEntities = tiny.clearEntities,
clearSystems = tiny.clearSystems,
getEntityCount = tiny.getEntityCount,
getSystemCount = tiny.getSystemCount,
getSystemIndex = tiny.getSystemIndex,
setSystemIndex = tiny.setSystemIndex
}
},
__tostring = function()
return "<tiny-ecs_World>"
end
}
return tiny