8 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
4 changed files with 295 additions and 148 deletions
+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:
+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"
}
}
+47 -2
View File
@@ -90,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()
@@ -190,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())
@@ -227,12 +272,12 @@ describe('tiny-ecs:', function()
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)
+164 -86
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.2-1" } 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.
@@ -248,20 +280,31 @@ local function processingSystemUpdate(system, dt)
end end
if process then if process then
local entities = system.entities if system.nocache then
local len = #entities local entities = system.world.entityList
for i = 1, len do local filter = system.filter
if filter then
for i = 1, #entities do
local entity = entities[i] local entity = entities[i]
if filter(system, entity) then
process(system, entity, dt) process(system, entity, dt)
end end
end end
end
else
local entities = system.entities
for i = 1, #entities do
process(system, entities[i], dt)
end
end
end
if postProcess then if postProcess then
postProcess(system, dt) postProcess(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
@@ -275,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
@@ -371,6 +413,9 @@ function tiny.world(...)
-- Set of Entities -- Set of Entities
entities = {}, entities = {},
-- List of Entities
entityList = {},
-- Number of Entities in World -- Number of Entities in World
entityCount = 0, entityCount = 0,
@@ -473,7 +518,7 @@ function tiny_manageSystems(world)
world.systemsToAdd = {} world.systemsToAdd = {}
world.systemsToRemove = {} world.systemsToRemove = {}
local entities = world.entities local worldEntityList = world.entityList
local systems = world.systems local systems = world.systems
-- Remove Systems -- Remove Systems
@@ -481,7 +526,7 @@ function tiny_manageSystems(world)
local system = s2r[i] local system = s2r[i]
local index = system.index local index = system.index
local onRemove = system.onRemove local onRemove = system.onRemove
if onRemove then if onRemove and not system.nocache then
local entityList = system.entities local entityList = system.entities
for j = 1, #entityList do for j = 1, #entityList do
onRemove(system, entityList[j]) onRemove(system, entityList[j])
@@ -507,11 +552,11 @@ function tiny_manageSystems(world)
-- Add Systems -- Add Systems
for i = 1, #s2a do for i = 1, #s2a do
local system = s2a[i] local system = s2a[i]
if systems[system.index] ~= system then if systems[system.index or 0] ~= system then
local entityList = {} if not system.nocache then
local entityIndices = {} system.entities = {}
system.entities = entityList system.indices = {}
system.indices = entityIndices end
if system.active == nil then if system.active == nil then
system.active = true system.active = true
end end
@@ -526,10 +571,14 @@ function tiny_manageSystems(world)
end end
-- Try to add Entities -- Try to add Entities
if not system.nocache then
local entityList = system.entities
local entityIndices = system.indices
local onAdd = system.onAdd local onAdd = system.onAdd
local filter = system.filter 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
local entityIndex = #entityList + 1 local entityIndex = #entityList + 1
entityList[entityIndex] = entity entityList[entityIndex] = entity
@@ -541,6 +590,7 @@ function tiny_manageSystems(world)
end end
end end
end end
end
s2a[i] = nil s2a[i] = nil
end end
end end
@@ -562,6 +612,7 @@ function tiny_manageEntities(world)
world.entitiesToRemove = {} 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
@@ -571,6 +622,7 @@ function tiny_manageEntities(world)
if entities[entity] then if entities[entity] then
for j = 1, #systems do for j = 1, #systems do
local system = systems[j] local system = systems[j]
if not system.nocache then
local ses = system.entities local ses = system.entities
local seis = system.indices local seis = system.indices
local index = seis[entity] local index = seis[entity]
@@ -600,17 +652,27 @@ function tiny_manageEntities(world)
end end
end end
end end
end
e2c[i] = nil e2c[i] = nil
end end
-- Remove Entities -- Remove Entities
for i = 1, #e2r do for i = 1, #e2r do
local 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
local system = systems[j] local system = systems[j]
if not system.nocache then
local ses = system.entities local ses = system.entities
local seis = system.indices local seis = system.indices
local index = seis[entity] local index = seis[entity]
@@ -628,17 +690,20 @@ function tiny_manageEntities(world)
end end
end end
end end
e2r[i] = nil end
end end
-- Add Entities -- Add Entities
for i = 1, #e2a do for i = 1, #e2a do
local 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
local system = systems[j] local system = systems[j]
if not system.nocache then
local ses = system.entities local ses = system.entities
local seis = system.indices local seis = system.indices
local filter = system.filter local filter = system.filter
@@ -654,6 +719,7 @@ function tiny_manageEntities(world)
end end
end end
end end
end
e2a[i] = nil e2a[i] = nil
end end
@@ -667,6 +733,17 @@ end
function tiny.refresh(world) function tiny.refresh(world)
tiny_manageSystems(world) tiny_manageSystems(world)
tiny_manageEntities(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 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`,
@@ -716,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