|
|
|
|
@ -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.
|
|
|
|
|
--[[-- 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.
|
|
|
|
|
--
|
|
|
|
|
-- 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
|
|
|
|
|
let system_mt = {
|
|
|
|
|
--- Modifiable fields
|
|
|
|
|
-- 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,48 +426,21 @@ let system_mt = {
|
|
|
|
|
end
|
|
|
|
|
-- notify addition
|
|
|
|
|
@entityCount += 1
|
|
|
|
|
@onAdd(e[@name])
|
|
|
|
|
-- add to subsystems
|
|
|
|
|
@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
|
|
|
|
|
return e, @add(...)
|
|
|
|
|
else
|
|
|
|
|
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 = {...}
|
|
|
|
|
|