mirror of
https://github.com/Reuh/ubiquitousse.git
synced 2025-10-27 09:09:30 +00:00
ecs: removed .entity in components, components do not need to be tables, pass entity as a new argument in several callbacks, remove System.methods, add System:callback, System:emit and System:reorder, add System.w, improve documentation
The component methods system was awkward and didn't give much benefit compared to just using methods on Systems. Plus now we really only have data in entities. Since we don't have component methods, the callback system had to be replaced; I integrated it with the default System methods since it's a relatively common behavior.
This commit is contained in:
parent
af3bd51cb3
commit
9d2e886609
15 changed files with 407 additions and 246 deletions
|
|
@ -1,35 +0,0 @@
|
|||
--- Callback system
|
||||
-- Allow to call callbacks defined in other systems in entity methods.
|
||||
-- Example:
|
||||
-- entity.callback:onMove(...) -- call onAdd(system, entitySystemTable, ...) on every system the entity belong to (which has this callback)
|
||||
-- Can be also used for onAdd, etc.
|
||||
|
||||
--- Recursively get a list of systems with a certain method.
|
||||
let recGetSystemsWithMethod = (method, systems, l={})
|
||||
for _, s in ipairs(systems) do
|
||||
if s[method] then
|
||||
table.insert(l, s)
|
||||
end
|
||||
recGetSystemsWithMethod(method, s.systems, l)
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
return {
|
||||
name = "callback",
|
||||
filter = true,
|
||||
methods = {
|
||||
__index = :(c, k)
|
||||
let s = recGetSystemsWithMethod(k, {@world})
|
||||
@methods[k] = :(c, ...)
|
||||
let e = c.entity
|
||||
for _, sys in ipairs(s) do
|
||||
if sys._previous[e] then
|
||||
sys[k](sys, e[sys.name], ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
return @_methods_mt[k]
|
||||
end
|
||||
}
|
||||
}
|
||||
|
|
@ -7,43 +7,34 @@ return {
|
|||
filter = true,
|
||||
default = {
|
||||
parent = nil, -- reference to parent entity, if any
|
||||
-- ... list of children
|
||||
-- ... list of children to add when the entity is added to the world
|
||||
children = {}, -- [children]=true,children... map+list of children currently in the entity children (don't set this yourself)
|
||||
},
|
||||
methods = {
|
||||
--- Add a new entity to the world, using this entity as a parent.
|
||||
add = :(c, o)
|
||||
if not o.children then
|
||||
o.children = {}
|
||||
end
|
||||
o.children.parent = c.entity
|
||||
table.insert(c, o)
|
||||
@world:add(o)
|
||||
end,
|
||||
--- Remove an entity from the world and from this entity's children.
|
||||
remove = :(c, o)
|
||||
@world:remove(o)
|
||||
for i=#c, 1, -1 do
|
||||
if c[i] == o then
|
||||
table.remove(c, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
o.children.parent = nil
|
||||
onAdd = :(c, e)
|
||||
if c.parent then -- add to parent
|
||||
let parentcc = c.parent.children.children
|
||||
table.insert(parentcc, e)
|
||||
parentcc[e] = true
|
||||
end
|
||||
},
|
||||
onAdd = :(c)
|
||||
for _, o in ipairs(c) do
|
||||
o.children.parent = c.entity
|
||||
for _, o in ipairs(c) do -- add children
|
||||
if not o.children then o.children = {} end
|
||||
o.children.parent = e
|
||||
@world:add(o)
|
||||
end
|
||||
end,
|
||||
onRemove = :(c)
|
||||
for _, o in ipairs(c) do
|
||||
@world:remove(o)
|
||||
o.children.parent = nil
|
||||
onRemove = :(c, e)
|
||||
for i=#c.children, 1, -1 do -- remove children
|
||||
@world:remove(c.children[i])
|
||||
end
|
||||
if c.parent then
|
||||
c.parent.children:remove(c.entity)
|
||||
if c.parent then -- remove from parent
|
||||
let parentcc = c.parent.children.children
|
||||
for i=#parentcc, 1, -1 do
|
||||
if parentcc[i] == e then
|
||||
table.remove(parentcc, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
parentcc[e] = nil
|
||||
end
|
||||
end
|
||||
}
|
||||
|
|
|
|||
537
ecs/ecs.can
537
ecs/ecs.can
|
|
@ -1,42 +1,101 @@
|
|||
--- ECS (entity compenent system) library
|
||||
--
|
||||
-- Entity Component System library, inspired by the excellent tiny-ecs. Main differences include:
|
||||
--
|
||||
-- * ability to nest systems;
|
||||
-- * instanciation of systems for each world (no shared state);
|
||||
-- * adding and removing entities is done instantaneously
|
||||
-- * ability to add and remove components from entities after they were added to the world.
|
||||
--
|
||||
-- No mandatory dependency.
|
||||
-- Optional dependency: `ubiquitousse.scene`, to allow quick creation of ECS-based scenes.
|
||||
--
|
||||
-- The module returns a table that contains several functions, `world` or `scene` are starting points
|
||||
-- to create your world.
|
||||
--
|
||||
-- @module ecs
|
||||
-- @usage TODO
|
||||
local loaded, scene = pcall(require, (...):match("^(.-)ecs").."scene")
|
||||
--[[-- ECS ([entity compenent system](https://en.wikipedia.org/wiki/Entity_component_system)) library for Lua.
|
||||
|
||||
Entity Component System library, inspired by the excellent [tiny-ecs](https://github.com/bakpakin/tiny-ecs/tree/master) by bakpakin.
|
||||
Main differences include:
|
||||
|
||||
* ability to nest systems (more organisation potential);
|
||||
* instanciation of systems for each world (no shared state) (several worlds can coexist at the same time easily);
|
||||
* adding and removing entities is done instantaneously (no going isane over tiny-ecs cache issues);
|
||||
* ability to add and remove components from entities after they were added to the world (more dynamic entities).
|
||||
|
||||
And a fair amount of other quality-of-life features.
|
||||
|
||||
The goals of this library are similar in spirit to tiny-ecs: simple to use, flexible, and useful.
|
||||
The more advanced features it provides relative to tiny-ecs are made so that you can completely ignore them
|
||||
if you don't use them.
|
||||
|
||||
The module returns a table that contains several functions, `world` or `scene` are starting points
|
||||
to create your world.
|
||||
|
||||
No mandatory dependency.
|
||||
Optional dependency: `ubiquitousse.scene`, to allow quick creation of ECS-based scenes (`ecs.scene`).
|
||||
|
||||
@module ecs
|
||||
@usage
|
||||
-- Same example as tiny-ecs', for comparaison purposes
|
||||
|
||||
local ecs = require("ubiquitousse.ecs")
|
||||
|
||||
local talkingSystem = {
|
||||
filter = { "name", "mass", "phrase" },
|
||||
process = function(self, c, e, dt)
|
||||
e.mass = e.mass + dt * 3
|
||||
print(("%s who weighs %d pounds, says %q."):format(e.name, e.mass, e.phrase))
|
||||
end
|
||||
}
|
||||
|
||||
local joe = {
|
||||
name = "Joe",
|
||||
phrase = "I'm a plumber.",
|
||||
mass = 150,
|
||||
hairColor = "brown"
|
||||
}
|
||||
|
||||
local world = ecs.world(talkingSystem)
|
||||
world:add(joe)
|
||||
|
||||
for i = 1, 20 do
|
||||
world:update(1)
|
||||
end
|
||||
--]]
|
||||
local loaded, scene
|
||||
if ... then loaded, scene = pcall(require, (...):match("^(.-)ecs").."scene") end
|
||||
if not loaded then scene = nil end
|
||||
|
||||
let ecs
|
||||
|
||||
-- TODO: Implement a skip list for faster search.
|
||||
-- better control over system order: process, draw, methods? (for lag reasons and dependencies)
|
||||
-- more generic events?
|
||||
|
||||
-- TODO: :reorder (like refresh but update order)
|
||||
--- Entities are regular tables that get processed by `System`s.
|
||||
--
|
||||
-- The idea is that entities _should_ only contain data and no code; it's the systems that are responsible for the actual processing
|
||||
-- (but it's your game, do as you want).
|
||||
--
|
||||
-- This data is referred to, and organized in, "components".
|
||||
-- @type Entity
|
||||
|
||||
-- TODO: clarify documentation on entity tables and instanciated system table
|
||||
--[[-- Components.
|
||||
|
||||
--- Entity table.
|
||||
-- TODO
|
||||
-- @section Entity
|
||||
Entities are Lua tables, and thus contain key-values pairs: each one of these pairs is called a "component". The data can be
|
||||
whatever you want, and ideally each component _should_ store the data for one singular aspect of the entity, for example its position, name, etc.
|
||||
|
||||
--- Entity system table.
|
||||
-- @doc entity
|
||||
This library does not do any kind of special processing by itself on the entity tables and take them as is (no metatable, no methamethods, etc.),
|
||||
so you are free to handle them as you want in your systems or elsewhere.
|
||||
|
||||
--- @table entity
|
||||
-- @field .. d
|
||||
Since it's relatively common for systems to only operate on a single component, as a shortcut the library often consider what it calls the "system component":
|
||||
that is, the component in the entity that has the same name as the system (if it exists).
|
||||
|
||||
@doc Component
|
||||
@usage
|
||||
-- example entity
|
||||
local entity = {
|
||||
position = { x = 0, y = 52 }, -- a "position" component
|
||||
sprite = newSprite("awesomeguy.png") -- a "sprite" component
|
||||
}
|
||||
|
||||
-- example "sprite" system
|
||||
local sprite = {
|
||||
name = "sprite",
|
||||
filter = "sprite", -- process entities that have a "sprite" component
|
||||
-- systems callbacks that are called per-entity often give you the system component as an argument
|
||||
-- the system component is the component with the same name as the system, thus here the sprite component
|
||||
render = function(self, component, entity)
|
||||
-- component == entity.sprite
|
||||
component:draw()
|
||||
end
|
||||
}
|
||||
]]--
|
||||
|
||||
--- Recursively remove subsystems from a system.
|
||||
let recDestroySystems = (system)
|
||||
|
|
@ -84,7 +143,7 @@ let copy = (a, b, cache={})
|
|||
copy(v, b[k], cache)
|
||||
setmetatable(b[k], getmetatable(v))
|
||||
end
|
||||
elseif b[k] == "table" then
|
||||
elseif type(b[k]) == "table" then
|
||||
copy(v, b[k], cache)
|
||||
end
|
||||
elseif b[k] == nil then
|
||||
|
|
@ -93,23 +152,77 @@ let copy = (a, b, cache={})
|
|||
end
|
||||
end
|
||||
|
||||
--- System fields and methods.
|
||||
--
|
||||
-- When they are added to a world, a new, per-world self table is created and used for every method call (which we call "instancied system").
|
||||
-- Instancied systems can be retrieved in `system.s` or `system.systems`.
|
||||
--
|
||||
-- The "world" is just the top-level system, behaving in exactly the same way as other systems.
|
||||
--
|
||||
-- Every field defined below is optional and can be accessed or redefined at any time, unless written otherwise. Though you would typically set them
|
||||
-- when creating your system.
|
||||
-- @type System
|
||||
--[[-- Systems and Worlds.
|
||||
Systems are what do the processing on your entities. A system contains a list of entities; the entities in this list are selected
|
||||
using a `filter`, and the system will only operate on those filtered entities.
|
||||
|
||||
A system can also be created that do not accept any entity (`filter = false`, this is the default): such a system can still be
|
||||
used to do processing that don't need to be done per-entity but still behave like other systems (e.g. to do some static calculation each update).
|
||||
|
||||
The system also contains `callbacks`, these define the actual processing done on the system and its entities and you will want to redefine
|
||||
at least one of them to make your system actually do something.
|
||||
|
||||
Then you can call `System:update`, `System:draw`, `System:emit` or `System:callback` at appropriate times and the system will call the
|
||||
associated callbacks on itself and its entities, and then pass it to its subsystems. In practise you would likely only call these on
|
||||
the world system, so the callbacks are correctly propagated to every single system in the world.
|
||||
|
||||
Systems are defined as regular tables with all the fields and methods you need in it. However, when a system is added to
|
||||
a world, the table you defined is not used directly, but we use what we call an "instancied system": think of it of an instance of your system
|
||||
like if it were a class.
|
||||
The instancied system will have a metatable set that gives it some methods and fields defined by the library on top of what you defined.
|
||||
Modifying the instancied system will only modify this instance and not the original system you defined, so several instances of your system
|
||||
can exist in different worlds (note that the original system is not copied on instancing; if you reference a table in the original system it will use the
|
||||
original table directly).
|
||||
|
||||
Systems can have subsystems; that is a system that behave as an extension of their parent system. They only operates on the entities already
|
||||
present in their parent subsystem, only update when their parent system updates, etc. You can thus organize your systems in a hierarchy to
|
||||
avoid repeating your filters or allow controlling several system from a single parent system.
|
||||
|
||||
The top-level system is called the "world"; it behaves in exactly the same way as other systems, and accept every entity by default.
|
||||
|
||||
@type System
|
||||
@usage
|
||||
local sprite = {
|
||||
filter = { "sprite", "position" }, -- only operate on entities with "sprite" and "position" components
|
||||
systems = { animated }, -- subsystems: they only operate on entities already filtered by this system (on top of their own filtering)
|
||||
|
||||
-- Called when an entity is added to this system.
|
||||
onAdd = function(self, component, entity)
|
||||
print("Added an entity, entity count in the system:", self.entityCount) -- self refer to the instancied system
|
||||
end,
|
||||
|
||||
-- Called when the system is updated, for every entity the system
|
||||
process = function(self, component, entity, dt)
|
||||
-- processing...
|
||||
end
|
||||
}
|
||||
|
||||
local world = ecs.world(system) -- instanciate a world with the sprite system (and all its subsystems)
|
||||
|
||||
-- Add an entity: doesn't pass the filtering, so nothing happens
|
||||
world:add {
|
||||
name = "John"
|
||||
}
|
||||
|
||||
-- Added to the sprite system! Call sprite:onAdd, and also try to add it to its subsystems
|
||||
world:add {
|
||||
sprite = newSprite("example.png"),
|
||||
position = { x=5, y=0 }
|
||||
}
|
||||
|
||||
-- Trigger sprite:onUpdate and sprite:process callbacks
|
||||
world:update(dt)
|
||||
--]]
|
||||
let system_mt = {
|
||||
--- Modifiable fields
|
||||
--- Modifiable fields.
|
||||
--
|
||||
-- Every field defined below is optional and can be accessed or redefined at any time, unless written otherwise. Though you would typically set them
|
||||
-- before instanciating your systems.
|
||||
-- @doc modifiable
|
||||
|
||||
--- Name of the system.
|
||||
-- Used to create a field with the system's name in `world.s` and into each entity (the "entity's system table") that's in this system.
|
||||
-- If not set, the entity will not have a system table.
|
||||
-- Used to create a field with the system's name in `world.s` and determine the associated system component.
|
||||
-- If not set, the system will not appear in `world.s` and gives `nil` instead of the system component in callbacks.
|
||||
--
|
||||
-- Do not change after system instanciation.
|
||||
-- @ftype string
|
||||
|
|
@ -124,37 +237,30 @@ let system_mt = {
|
|||
-- @ftype nil if no subsystem
|
||||
systems = nil,
|
||||
|
||||
--- If not false, the system will only update every interval seconds.
|
||||
--- If not `false`, the system will only update every interval seconds.
|
||||
-- `false` by default.
|
||||
-- @ftype number interval of time between each update
|
||||
-- @ftype false to disable
|
||||
interval = false,
|
||||
--- The system and its susbsystems will only update if this is true.
|
||||
--- The system and its susbsystems will only update if this is `true`.
|
||||
-- `true` by default.
|
||||
-- @ftype boolean
|
||||
active = true,
|
||||
--- The system and its subsystems will only draw if this is true.
|
||||
--- The system and its subsystems will only draw if this is `true`.
|
||||
-- `true` by default.
|
||||
-- @ftype boolean
|
||||
visible = true,
|
||||
|
||||
--- Defaults value to put into the entities's system table when they are added. Will recursively fill missing values.
|
||||
--- Defaults value to put into the entities's system component when they are added.
|
||||
--
|
||||
-- If this is table, will recursively fill missing values.
|
||||
-- Metatables will be preserved during the copy but not copied themselves.
|
||||
--
|
||||
-- When an entity is added to a system, a `.entity` field is always set in the system table, referring to the full entity table.
|
||||
--
|
||||
-- Changing this will not affect entities already in the system.
|
||||
-- @ftype table
|
||||
-- Doesn't have any effect if the system doesn't have a name.
|
||||
-- @ftype any
|
||||
-- @ftype nil if no default
|
||||
default = nil,
|
||||
--- Defaults methods to assign to the entities's system table when they are added.
|
||||
--
|
||||
-- When calling the methods with `entity.systemName:method(...)`, the method will actually receive the
|
||||
-- arguments method(system, `system table, ...)`. Methamethods are accepted. New methods can be
|
||||
-- created anytime.
|
||||
-- @ftype table
|
||||
-- @ftype nil if no methods
|
||||
methods = nil,
|
||||
|
||||
--- Callbacks.
|
||||
--
|
||||
|
|
@ -163,40 +269,42 @@ let system_mt = {
|
|||
-- @doc callbacks
|
||||
|
||||
--- Called when checking if an entity should be added to this system.
|
||||
-- Returns true if the entity should be added to this system (and therefore its subsystems).
|
||||
-- Returns `true` if the entity should be added to this system (and therefore its subsystems).
|
||||
--
|
||||
-- If this is a string or a table, it will be converted to a filter function on instanciation using ecs.any.
|
||||
--
|
||||
-- If this true, will accept every entity; if false, reject every entity.
|
||||
-- If this `true`, will accept every entity; if `false`, reject every entity.
|
||||
--
|
||||
-- Will only test entities when they are added; changing this after system creation will not affect entities already in the system.
|
||||
--
|
||||
-- By default, rejects everything.
|
||||
-- @callback
|
||||
-- @tparam table e entity table to check
|
||||
-- @treturn boolean true if entity should be added
|
||||
-- @treturn boolean `true` if entity should be added
|
||||
filter = :(e) return false end,
|
||||
--- Called when adding an entity to this system determining its order.
|
||||
-- Returns true if e1 <= e2. e1 and e2 are two entities.
|
||||
-- Returns `true` if `e1 <=` e2 (i.e., if `e1` should be processed before `e2` in this system). e1 and e2 are two entities.
|
||||
--
|
||||
-- Used to place the entity in the sorted entity list when it is added; changing this after system creation
|
||||
-- will not change the order of entities already in the system.
|
||||
--
|
||||
-- By default, entities are in the same order they were inserted.
|
||||
-- By default, new entities are added at the start of the list.
|
||||
-- @callback
|
||||
-- @tparam table e1 entity table to check for inferiority
|
||||
-- @tparam table e2 entity table to check for superiority
|
||||
-- @treturn boolean true if e1 <= e2
|
||||
-- @tparam Entity e1 entity table to check for inferiority
|
||||
-- @tparam Entity e2 entity table to check for superiority
|
||||
-- @treturn boolean `true` if e1 <= e2
|
||||
compare = :(e1, e2) return true end,
|
||||
|
||||
--- Called when adding an entity to the system.
|
||||
-- @callback
|
||||
-- @tparam table s the entity's system table
|
||||
onAdd = :(s) end,
|
||||
-- @tparam Component c the entity's system component
|
||||
-- @tparam Entity e the entity table
|
||||
onAdd = :(c, e) end,
|
||||
--- Called when removing an entity from the system.
|
||||
-- @callback
|
||||
-- @tparam table s the entity's system table
|
||||
onRemove = :(s) end,
|
||||
-- @tparam Component c the entity's system component
|
||||
-- @tparam Entity e the entity table
|
||||
onRemove = :(c, e) end,
|
||||
--- Called when the system is instancied, before any call to `System:onAddToWorld` (including other systems in the world).
|
||||
-- @callback
|
||||
onInstance = :() end,
|
||||
|
|
@ -220,21 +328,29 @@ let system_mt = {
|
|||
onDraw = :() end,
|
||||
--- Called when updating the system, for every entity the system contains. Called after `System:onUpdate` was called on the system.
|
||||
-- @callback
|
||||
-- @tparam table s the entity's system table
|
||||
-- @tparam Component c the entity's system component
|
||||
-- @tparam Entity e the entity table
|
||||
-- @number dt delta-time since last update
|
||||
process = :(s, dt) end,
|
||||
process = :(c, e, dt) end,
|
||||
--- Called when drawing the system, for every entity the system contains. Called after `System:onDraw` was called on the system.
|
||||
-- @callback
|
||||
-- @tparam table s the entity's system table
|
||||
render = :(s) end,
|
||||
-- @tparam Component c the entity's system component
|
||||
-- @tparam Entity e the entity table
|
||||
render = :(c, e) end,
|
||||
|
||||
--- Read-only fields
|
||||
--- Read-only fields.
|
||||
--
|
||||
-- Fields available on instancied systems. Don't modify them unless you like broken things.
|
||||
-- @doc ro
|
||||
|
||||
--- The world the system belongs to.
|
||||
-- @ftype System world
|
||||
-- @ro
|
||||
world = nil,
|
||||
--- Shortcut to `System.world`.
|
||||
-- @ftype System world
|
||||
-- @ro
|
||||
w = nil,
|
||||
--- Number of entities in the system.
|
||||
-- @ftype integer
|
||||
-- @ro
|
||||
|
|
@ -249,22 +365,17 @@ let system_mt = {
|
|||
--- First element of the linked list of entities: { entity, next_element }.
|
||||
-- @local
|
||||
_first = nil,
|
||||
--- Associative map of entities in the system and their previous linked list element (or true if first element).
|
||||
--- Associative map of entities in the system and their previous linked list element (or `true` if first element).
|
||||
-- This make the list effectively a doubly linked list, but with easy access to the previous element using this map (and therefore O(1) deletion).
|
||||
-- @local
|
||||
_previous = nil,
|
||||
--- Amount of time waited since last update (if interval is set).
|
||||
-- @local
|
||||
_waited = 0,
|
||||
--- Metatable of entities' system table.
|
||||
-- Contains the methods defined in the methods field, wrapped to be called with the correct arguments, as well as a
|
||||
-- __index field (if not redefined in methods).
|
||||
-- @local
|
||||
_methods_mt = nil,
|
||||
|
||||
--- Methods.
|
||||
--
|
||||
-- Methods available on instancied system table.
|
||||
-- Methods available on instancied systems.
|
||||
-- @doc smethods
|
||||
|
||||
--- Add entities to the system and its subsystems.
|
||||
|
|
@ -274,22 +385,18 @@ let system_mt = {
|
|||
-- Entities are added to subsystems after they were succesfully added to their parent system.
|
||||
--
|
||||
-- If this is called on a subsystem instead of the world, be warned that this will bypass all the parent's systems filters.
|
||||
--
|
||||
-- Since `System:remove` will not search for entities in systems where they should have been filtered out, the added entities will not be removed
|
||||
-- when calling :remove on a parent system or the world. The entity can be removed by calling `System:remove` on the system `System:add` was called on.
|
||||
-- If you do that, since `System:remove` will not search for entities in systems where they should have been filtered out, the added entities will not be removed
|
||||
-- when calling `System:remove` on a parent system or the world. The entity can be removed by calling `System:remove` on the system `System:add` was called on.
|
||||
--
|
||||
-- Complexity: O(1) per unordered system, O(entityCount) per ordered system.
|
||||
-- @tparam table e entity to add
|
||||
-- @tparam table... ... other entities to add
|
||||
-- @treturn e,... the function arguments
|
||||
-- @tparam Entity e entity to add
|
||||
-- @tparam Entity... ... other entities to add
|
||||
-- @treturn Entity,... `e,...` the function arguments
|
||||
add = :(e, ...)
|
||||
if e ~= nil and not @_previous[e] and @filter(e) then
|
||||
-- setup entity
|
||||
if @name then
|
||||
if not e[@name] e[@name] = {}
|
||||
if @default copy(@default, e[@name])
|
||||
if @methods setmetatable(e[@name], @_methods_mt)
|
||||
e[@name].entity = e
|
||||
-- copy default system component
|
||||
if @name and @default then
|
||||
copy({ [@name] = @default }, e)
|
||||
end
|
||||
-- add to linked list
|
||||
if @_first == nil then
|
||||
|
|
@ -319,10 +426,12 @@ let system_mt = {
|
|||
end
|
||||
-- notify addition
|
||||
@entityCount += 1
|
||||
@onAdd(e[@name])
|
||||
-- add to subsystems
|
||||
for _, s in ipairs(@systems) do
|
||||
s:add(e)
|
||||
@onAdd(e[@name], e)
|
||||
-- add to subsystems (if it wasn't immediately removed in onAdd)
|
||||
if @_previous[e] then
|
||||
for _, s in ipairs(@systems) do
|
||||
s:add(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
if ... then
|
||||
|
|
@ -331,36 +440,7 @@ let system_mt = {
|
|||
return e
|
||||
end
|
||||
end,
|
||||
--- Refresh an entity's systems.
|
||||
--
|
||||
-- Behave similarly to `System:add`, but if the entity is already in the system, instead of skipping it, it
|
||||
-- will check for new and removed components and add and remove from (sub)systems accordingly.
|
||||
--
|
||||
-- Complexity: O(1) per system + add/remove complexity.
|
||||
-- @tparam table e entity to refresh
|
||||
-- @tparam table... ... other entities to refresh
|
||||
-- @treturn e,... the function arguments
|
||||
refresh = :(e, ...)
|
||||
if e ~= nil then
|
||||
if not @_previous[e] then
|
||||
@add(e)
|
||||
elseif @_previous[e] then
|
||||
if not @filter(e) then
|
||||
@remove(e)
|
||||
else
|
||||
for _, s in ipairs(@systems) do
|
||||
s:refresh(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if ... then
|
||||
return e, @refresh(...)
|
||||
else
|
||||
return e
|
||||
end
|
||||
end,
|
||||
--- Remove entities to the system and its subsystems.
|
||||
--- Remove entities from the system and its subsystems.
|
||||
--
|
||||
-- Will skip entities that are not in the system.
|
||||
--
|
||||
|
|
@ -368,12 +448,10 @@ let system_mt = {
|
|||
--
|
||||
-- If you intend to call this on a subsystem instead of the world, please read the warning in `System:add`.
|
||||
--
|
||||
-- Returns all removed entities.
|
||||
--
|
||||
-- Complexity: O(1) per system.
|
||||
-- @tparam table e entity to remove
|
||||
-- @tparam table... ... other entities to remove
|
||||
-- @treturn e,... the function arguments
|
||||
-- @tparam Entity e entity to remove
|
||||
-- @tparam Entity... ... other entities to remove
|
||||
-- @treturn Entity,... `e,...` the function arguments
|
||||
remove = :(e, ...)
|
||||
if e ~= nil then
|
||||
if @_previous[e] then
|
||||
|
|
@ -399,7 +477,7 @@ let system_mt = {
|
|||
-- notify removal
|
||||
@_previous[e] = nil
|
||||
@entityCount -= 1
|
||||
@onRemove(e[@name])
|
||||
@onRemove(e[@name], e)
|
||||
end
|
||||
end
|
||||
if ... then
|
||||
|
|
@ -408,12 +486,96 @@ let system_mt = {
|
|||
return e
|
||||
end
|
||||
end,
|
||||
--- Returns true if every entity is in the system.
|
||||
--- Refresh an entity's systems.
|
||||
--
|
||||
-- Behave similarly to `System:add`, but if the entity is already in the system, instead of skipping it, it
|
||||
-- will check for new and removed components and add and remove from (sub)systems accordingly.
|
||||
--
|
||||
-- Complexity: O(1) per system + add/remove complexity.
|
||||
-- @tparam Entity e entity to refresh
|
||||
-- @tparam Entity... ... other entities to refresh
|
||||
-- @treturn Entity,... `e,...` the function arguments
|
||||
refresh = :(e, ...)
|
||||
if e ~= nil then
|
||||
if not @_previous[e] then
|
||||
@add(e)
|
||||
elseif @_previous[e] then
|
||||
if not @filter(e) then
|
||||
@remove(e)
|
||||
else
|
||||
for _, s in ipairs(@systems) do
|
||||
s:refresh(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if ... then
|
||||
return e, @refresh(...)
|
||||
else
|
||||
return e
|
||||
end
|
||||
end,
|
||||
--- Reorder an entity.
|
||||
--
|
||||
-- Will recalculate the entity position in the entity list for this system and its subsystems.
|
||||
-- Will skip entities that are not in the system.
|
||||
--
|
||||
-- Complexity: O(entityCount) per system.
|
||||
-- @tparam Entity e entity to reorder
|
||||
-- @tparam Entity... ... other entities to reorder
|
||||
-- @treturn Entity,... `e,...` the function arguments
|
||||
reorder = :(e, ...)
|
||||
if e ~= nil then
|
||||
if @_previous[e] then
|
||||
let prev = @_previous[e] -- { prev, { e, next } }
|
||||
let next = prev == true and @_first[2] or prev[2][2]
|
||||
-- remove e from linked list
|
||||
if prev == true then
|
||||
@_first = @_first[2]
|
||||
else
|
||||
prev[2] = next
|
||||
end
|
||||
if next then
|
||||
@_previous[next[1]] = prev
|
||||
end
|
||||
-- find position so that prev < e <= next
|
||||
while prev ~= true and @compare(e, prev[1]) do -- ensure prev < e
|
||||
next = prev
|
||||
prev = @_previous[prev[1]]
|
||||
end
|
||||
while next ~= nil and not @compare(e, next[1]) do -- ensure e <= next
|
||||
prev = next
|
||||
next = next[2]
|
||||
end
|
||||
-- reinsert e in linked list
|
||||
let new = { e, next }
|
||||
@_previous[e] = prev
|
||||
if next then
|
||||
@_previous[next[1]] = new
|
||||
end
|
||||
if prev == true then
|
||||
@_first = new
|
||||
else
|
||||
prev[2] = new
|
||||
end
|
||||
-- Reorder in subsystems
|
||||
for _, s in ipairs(@systems) do
|
||||
s:reorder(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
if ... then
|
||||
return e, @reorder(...)
|
||||
else
|
||||
return e
|
||||
end
|
||||
end,
|
||||
--- Returns `true` if all these entities are in the system.
|
||||
--
|
||||
-- Complexity: O(1).
|
||||
-- @tparam table e entity that may be in the system
|
||||
-- @tparam table... ... other entities that may be in the system
|
||||
-- @treturn boolean true if every entity is in the system
|
||||
-- @tparam Entity e entity that may be in the system
|
||||
-- @tparam Entity... ... other entities that may be in the system
|
||||
-- @treturn boolean `true` if every entity is in the system
|
||||
has = :(e, ...)
|
||||
let has = e == nil or not not @_previous[e]
|
||||
if ... then
|
||||
|
|
@ -422,8 +584,8 @@ let system_mt = {
|
|||
return has
|
||||
end
|
||||
end,
|
||||
--- Returns an iterator that iterate through the entties in this system.
|
||||
-- @treturn iterator iterator over the entities in this system, in order
|
||||
--- Returns an iterator that iterate through the entties in this system, in order.
|
||||
-- @treturn iterator iterator over the entities in this system
|
||||
iter = :()
|
||||
return nextEntity, { @_first }
|
||||
end,
|
||||
|
|
@ -451,7 +613,7 @@ let system_mt = {
|
|||
@onUpdate(dt)
|
||||
if @process ~= system_mt.process then
|
||||
for e in @iter() do
|
||||
@process(e[@name], dt)
|
||||
@process(e[@name], e, dt)
|
||||
end
|
||||
end
|
||||
for _, s in ipairs(@systems) do
|
||||
|
|
@ -470,7 +632,7 @@ let system_mt = {
|
|||
@onDraw()
|
||||
if @render ~= system_mt.render then
|
||||
for e in @iter() do
|
||||
@render(e[@name])
|
||||
@render(e[@name], e)
|
||||
end
|
||||
end
|
||||
for _, s in ipairs(@systems) do
|
||||
|
|
@ -478,16 +640,74 @@ let system_mt = {
|
|||
end
|
||||
end
|
||||
end,
|
||||
--- Trigger a custom callback on a single entity.
|
||||
--
|
||||
-- This will call the `System:name(c, e, ...)` method in this system and its subsystems,
|
||||
-- if the method exists and the entity is in the system. `c` is the system [component](#Entity.Component)
|
||||
-- associated with the current system, and `e` is the `Entity`.
|
||||
--
|
||||
-- Think of it as a way to perform custom callbacks issued from an entity event, similar to `System:onAdd`.
|
||||
-- @tparam string name name of the callback
|
||||
-- @tparam Entity e the entity to perform the callback on
|
||||
-- @param ... other arguments to pass to the callback
|
||||
callback = :(name, e, ...)
|
||||
-- call callback
|
||||
if @_previous[e] and @[name] then
|
||||
@[name](@, e[@name], e, ...)
|
||||
end
|
||||
-- callback on subsystems (if it wasn't removed during the callback)
|
||||
if @_previous[e] then
|
||||
for _, ss in ipairs(@systems) do
|
||||
ss:callback(name, e, ...)
|
||||
end
|
||||
end
|
||||
end,
|
||||
--- Emit an event on the system.
|
||||
--
|
||||
-- This will call the `System:name(...)` method in this system and its subsystems,
|
||||
-- if the method exists.
|
||||
--
|
||||
-- Think of it as a way to perform custom callbacks issued from a general event, similar to `System:onUpdate`.
|
||||
--
|
||||
-- The called methods may return a string value to affect the event propagation behaviour:
|
||||
--
|
||||
-- * if a callback returns `"stop"`, the event will not be propagated to the subsystems.
|
||||
-- * if a callback returns `"capture"`, the event will not be propagated to the subsystems _and_
|
||||
-- its sibling systems (i.e. completely stop the propagation of the event).
|
||||
--
|
||||
-- `"stop"` would be for example used to disable some behaviour in the system and its subsystems (like `active = false` can
|
||||
-- disable `System:onUpdate` behaviour on the system and its subsystems).
|
||||
--
|
||||
-- `"capture"` would be for example used to prevent other systems from handling the event (for example to make sure an
|
||||
-- input event is handled only once by a single system).
|
||||
--
|
||||
-- @tparam string name name of the callback
|
||||
-- @param ... other arguments to pass to the callback
|
||||
emit = :(name, ...)
|
||||
-- call event
|
||||
let status
|
||||
if @[name] then
|
||||
status = @[name](@, ...)
|
||||
end
|
||||
-- call event on subsystems (if it wasn't stopped or captured)
|
||||
if status ~= "stop" and status ~= "capture" then
|
||||
for _, s in ipairs(@systems) do
|
||||
status = s:emit(name, ...)
|
||||
if status == "capture" then break end
|
||||
end
|
||||
end
|
||||
return status
|
||||
end,
|
||||
--- Remove all the entities and subsystems in this system.
|
||||
destroy = :()
|
||||
recCallOnRemoveFromWorld(@world, { @ })
|
||||
recDestroySystems({ systems = { @ } })
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
--- Self descriptive
|
||||
let alwaysTrue = () return true end
|
||||
let alwaysFalse = () return true end
|
||||
let alwaysFalse = () return false end
|
||||
|
||||
--- Recursively instanciate a list of systems for a world:
|
||||
-- * create their self table with instance fields set
|
||||
|
|
@ -496,31 +716,13 @@ let recInstanciateSystems = (world, systems)
|
|||
let t = {}
|
||||
for _, s in ipairs(systems) do
|
||||
let system
|
||||
-- setup method table
|
||||
let methods_mt = {}
|
||||
if s.methods then
|
||||
methods_mt.__index = methods_mt
|
||||
for k, v in pairs(s.methods) do
|
||||
methods_mt[k] = :(...)
|
||||
return v(system, @, ...)
|
||||
end
|
||||
end
|
||||
setmetatable(s.methods, {
|
||||
__newindex = :(k, v)
|
||||
rawset(@, k, v)
|
||||
methods_mt[k] = :(...)
|
||||
return v(system, @, ...)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
-- instanciate system
|
||||
system = setmetatable({
|
||||
systems = recInstanciateSystems(world, s.systems or {}),
|
||||
world = world,
|
||||
w = world,
|
||||
s = world.s,
|
||||
_previous = {},
|
||||
_methods_mt = methods_mt
|
||||
}, {
|
||||
__index = :(k)
|
||||
if s[k] ~= nil then
|
||||
|
|
@ -563,7 +765,7 @@ end
|
|||
ecs = {
|
||||
--- Create and returns a world system based on a list of systems.
|
||||
-- The systems will be instancied for this world.
|
||||
-- @tparam table,... ... list of (uninstancied) system tables
|
||||
-- @tparam table,... ... list of (uninstancied) systems
|
||||
-- @treturn System the world system
|
||||
world = (...)
|
||||
let world = setmetatable({
|
||||
|
|
@ -572,14 +774,15 @@ ecs = {
|
|||
_previous = {}
|
||||
}, { __index = system_mt })
|
||||
world.world = world
|
||||
world.w = world
|
||||
world.systems = recInstanciateSystems(world, {...})
|
||||
recCallOnAddToWorld(world, world.systems)
|
||||
return world
|
||||
end,
|
||||
|
||||
--- Returns a filter that returns true if, for every argument, a field with the same name exists in the entity.
|
||||
--- Returns a filter that returns `true` if, for every argument, a field with the same name exists in the entity.
|
||||
-- @tparam string,... ... list of field names that must be in entity
|
||||
-- @treturn function(e) that returns true if e has all the fields
|
||||
-- @treturn function(e) that returns `true` if e has all the fields
|
||||
all = (...)
|
||||
if ... then
|
||||
let l = {...}
|
||||
|
|
@ -596,9 +799,9 @@ ecs = {
|
|||
end
|
||||
end,
|
||||
|
||||
--- Returns a filter that returns true if one of the arguments if the name of a field in the entity.
|
||||
--- Returns a filter that returns `true` if one of the arguments if the name of a field in the entity.
|
||||
-- @tparam string,... ... list of field names that may be in entity
|
||||
-- @treturn function(e) that returns true if e has at leats one of the fields
|
||||
-- @treturn function(e) that returns `true` if e has at leats one of the fields
|
||||
any = (...)
|
||||
if ... then
|
||||
let l = {...}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue