Compare commits

..

No commits in common. "main" and "v2.0" have entirely different histories.
main ... v2.0

40 changed files with 1527 additions and 1228 deletions

View file

@ -1,38 +0,0 @@
name: Build Docs
on:
push:
branches:
- master
jobs:
build:
name: Build docs
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Setup Lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: 5.4
- name: Install Luarocks
uses: leafo/gh-actions-luarocks@v4
- name: Install LDoc
run: luarocks install ldoc
- name: Show
run: luarocks show ldoc
- name: Build docs
run: ldoc .
- name: Deploy
uses: JamesIves/github-pages-deploy-action@4.1.5
with:
branch: gh-pages
folder: docs

5
.gitignore vendored
View file

@ -37,7 +37,4 @@ luac.out
*.app *.app
*.i*86 *.i*86
*.x86_64 *.x86_64
*.hex *.hex
# VSCode
.vscode/

View file

@ -1,2 +1 @@
---@diagnostic disable: lowercase-global
std="love+luajit" std="love+luajit"

248
README.md
View file

@ -7,7 +7,7 @@ With Concord it is possibile to easily write fast and clean code.
This readme will explain how to use Concord. This readme will explain how to use Concord.
Additionally all of Concord is documented using the LDoc format. Additionally all of Concord is documented using the LDoc format.
Auto generated docs for Concord can be found in `docs` folder, or on the [GitHub page](https://keyslam-group.github.io/Concord/). Auto generated docs for Concord can be found in `docs` folder, or on the [Github page](https://tjakka5.github.io/Concord/).
--- ---
@ -19,16 +19,17 @@ Auto generated docs for Concord can be found in `docs` folder, or on the [GitHub
- [Entities](#entities) - [Entities](#entities)
- [Systems](#systems) - [Systems](#systems)
- [Worlds](#worlds) - [Worlds](#worlds)
- [Assemblages](#assemblages) - [Assemblages](#assemblages)
[Quick Example](#quick-example) [Quick Example](#quick-example)
[Contributors](#contributors) [Contributors](#contributors)
[License](#licence) [License](#licence)
--- ---
## Installation ## Installation
Download the repository and copy the 'concord' folder into your project. Then require it in your project like so: Download the repository and copy the 'src' folder in your project. Rename it to something that makes sense (Probably 'concord'), then require it in your project like so:
```lua ```lua
local Concord = require("path.to.concord") local Concord = require("path.to.concord")
``` ```
@ -41,9 +42,13 @@ local Entity = Concord.entity
local Component = Concord.component local Component = Concord.component
local System = Concord.system local System = Concord.system
local World = Concord.world local World = Concord.world
local Assemblage = Concord.assemblage
-- Containers -- Containers
local Components = Concord.components local Components = Concord.components
local Systems = Concord.systems
local Worlds = Concord.worlds
local Assemblages = Concord.assemblages
``` ```
--- ---
@ -51,7 +56,7 @@ local Components = Concord.components
## ECS ## ECS
Concord is an Entity Component System (ECS for short) library. Concord is an Entity Component System (ECS for short) library.
This is a coding paradigm where _composition_ is used over _inheritance_. This is a coding paradigm where _composition_ is used over _inheritance_.
Because of this it is easier to write more modular code. It often allows you to combine any form of behaviour for the objects in your game (Entities). Because of this it is easier to write more modular code. It often allowes you to combine any form of behaviour for the objects in your game (Entities).
As the name might suggest, ECS consists of 3 core things: Entities, Components, and Systems. A proper understanding of these is required to use Concord effectively. As the name might suggest, ECS consists of 3 core things: Entities, Components, and Systems. A proper understanding of these is required to use Concord effectively.
We'll start with the simplest one. We'll start with the simplest one.
@ -66,7 +71,7 @@ What is most important is that Components are data and nothing more. They have 0
Entities are the actual objects in your game. Like a player, an enemy, a crate, or a bullet. Entities are the actual objects in your game. Like a player, an enemy, a crate, or a bullet.
Every Entity has it's own set of Components, with their own values. Every Entity has it's own set of Components, with their own values.
A crate might have the following components (Note: Not actual Concord syntax): A crate might have the following components:
```lua ```lua
{ {
position = { x = 100, y = 200 }, position = { x = 100, y = 200 },
@ -95,12 +100,12 @@ For this they will only act on Entities that have the Components needed for this
In Concord this is done something alike this: In Concord this is done something alike this:
```lua ```lua
drawSystem = System({pool = {position, texture}}) -- Define a System that takes all Entities with a position and texture Component drawSystem = System({position, texture}) -- Define a System that takes all Entities with a position and texture Component
function drawSystem:draw() -- Give it a draw function function drawSystem:draw() -- Give it a draw function
for _, entity in ipairs(self.pool) do -- Iterate over all Entities that this System acts on for _, entity in ipairs(self.pool) do -- Iterate over all Entities that this System acts on
local position = entity.position -- Get the position Component of this Entity local position = entity[position] -- Get the position Component of this Entity
local texture = entity.texture -- Get the texture Component of this Entity local texture = entity[texture] -- Get the texture Component of this Entity
-- Draw the Entity -- Draw the Entity
love.graphics.draw(texture.image, position.x, position.y) love.graphics.draw(texture.image, position.x, position.y)
@ -128,40 +133,58 @@ And all that without writing a single extra line of code. Just reusing code that
Concord does a few things that might not be immediately clear. This segment should help understanding. Concord does a few things that might not be immediately clear. This segment should help understanding.
#### Requiring files #### Classes
Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in. When you define a Component or System you are actually defining a `ComponentClass` and `SystemClass` respectively. From these instances of them can be created. They also act as identifiers for Concord.
For example. If you want to get a specific Component from an Entity, you'd do `Component = Entity:get(ComponentClass)`.
When ComponentClasses or SystemClasses are required it will be written clearly in the Documentation.
#### Containers
Since you'll be defining or creating lots of Components, Systems, Worlds and Assemblages, Concord adds container tables for each of them so that they are easily accessible.
These containers can be accessed through
```lua ```lua
-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary. local Components = require("path.to.concord").components
Concord.utils.loadNamespace("path/to/components") local Systems = require("path.to.concord").systems
local Worlds = require("path.to.concord").worlds
print(Concord.components.componentName) local Assemblages = require("path.to.concord").aorlds
-- Loads all files in the directory, and puts the return value in the table Systems. The key is their filename without any extension
local Systems = {}
Concord.utils.loadNamespace("path/to/systems", Systems)
myWorld:addSystems(
Systems.healthSystem
Systems.damageSystem,
Systems.moveSystem,
-- etc
)
``` ```
Concord has helper functions to fill these containers. There are the following options depending on your needs / preference:
```lua
-- Loads each file. They are put in the container according to it's filename ( 'src.components.component_1.lua' becomes 'component_1' )
Concord.loadComponents({"path.to.component_1", "path.to.component_2", "etc"})
-- Loads all files in the directory. They are put in the container according to it's filename ( 'src.components.component_1.lua' becomes 'component_1' )
Concord.loadComponents("path.to.directory.containing.components")
-- Put the ComponentClass into the container directly. Useful if you want more manual control. Note that you need to require the file in this case
Components.register("componentName", ComponentClass)
```
Things can then be accessed through their names:
```lua
local component_1_class = Components.component_1
local componentName_class = Components.componentName
```
All the above applies the same to all the other containers.
#### Method chaining #### Method chaining
```lua ```lua
-- Most (if not all) methods will return self -- All functions that do something ( eg. Don't return anything ) will return self
-- This allowes you to chain methods -- This allowes you to chain methods
myEntity entity
:give("position", 100, 50) :give(position, 100, 50)
:give("velocity", 200, 0) :give(velocity, 200, 0)
:remove("position") :remove(position)
:destroy() :destroy()
myWorld --
world
:addEntity(fooEntity) :addEntity(fooEntity)
:addEntity(barEntity) :addEntity(barEntity)
:clear() :clear()
@ -169,13 +192,13 @@ myWorld
``` ```
### Components ### Components
When defining a ComponentClass you need to pass in a name and usually a `populate` function. This will fill the Component with values. When defining a ComponentClass you usually pass in a `populate` function. This will fill the Component with values.
```lua ```lua
-- Create the position class with a populate function -- Create the ComponentClass with a populate function
-- The component variable is the actual Component given to an Entity -- The component variable is the actual Component given to an Entity
-- The x and y variables are values we pass in when we create the Component -- The x and y variables are values we pass in when we create the Component
Concord.component("position", function(component, x, y) local positionComponentClass = Concord.component(function(component, x, y)
component.x = x or 0 component.x = x or 0
component.y = y or 0 component.y = y or 0
end) end)
@ -183,11 +206,19 @@ end)
-- Create a ComponentClass without a populate function -- Create a ComponentClass without a populate function
-- Components of this type won't have any fields. -- Components of this type won't have any fields.
-- This can be useful to indiciate state. -- This can be useful to indiciate state.
local pushableComponentClass = Concord.component("position") local pushableComponentClass = Concord.component()
```
```lua
-- Manually register the ComponentClass to the container if we want
Concord.components.register("positionComponent", positionComponentClass)
-- Otherwise return the ComponentClass so it can be required
return positionComponentClass
``` ```
### Entities ### Entities
Entities can be freely made and be given Components. You pass the name of the ComponentClass and the values you want to pass. It will then create the Component for you. Entities can be freely made and be given Components. You pass the ComponentClass and the values you want to pass. It will then create the Component for you.
Entities can only have a maximum of one of each Component. Entities can only have a maximum of one of each Component.
Entities can not share Components. Entities can not share Components.
@ -202,35 +233,43 @@ local myEntity = Entity(myWorld) -- To add it to a world immediately ( See World
```lua ```lua
-- Give the entity the position Component defined above -- Give the entity the position Component defined above
-- x will become 100. y will become 50 -- x will become 100. y will become 50
myEntity:give("position", 100, 50) myEntity:give(positionComponentClass, 100, 50)
``` ```
```lua ```lua
-- Retrieve a Component -- Retrieve a Component
local position = myEntity.position local positionComponent = myEntity[positionComponentClass]
-- or
local positionComponent = myEntity:get(positionComponentClass)
print(position.x, position.y) -- 100, 50 print(positionComponent.x, positionComponent.y) -- 100, 50
``` ```
```lua ```lua
-- Remove a Component -- Remove a Component
myEntity:remove("position") myEntity:remove(positionComponentClass)
```
```lua
-- Check if the Entity has a Component
local hasPositionComponent = myEntity:has(positionComponentClass)
print(hasPositionComponent) -- false
``` ```
```lua ```lua
-- Entity:give will override a Component if the Entity already has it -- Entity:give will override a Component if the Entity already has it
-- Entity:ensure will only put the Component if the Entity does not already have it -- Entity:ensure will only put the Component if the Entity does not already have it
Entity:ensure("position", 0, 0) -- Will give Entity:ensure(positionComponentClass, 0, 0) -- Will give
-- Position is {x = 0, y = 0} -- Position is {x = 0, y = 0}
Entity:give("position", 50, 50) -- Will override Entity:give(positionComponentClass, 50, 50) -- Will override
-- Position is {x = 50, y = 50} -- Position is {x = 50, y = 50}
Entity:give("position", 100, 100) -- Will override Entity:give(positionComponentClass, 100, 100) -- Will override
-- Position is {x = 100, y = 100} -- Position is {x = 100, y = 100}
Entity:ensure("position", 0, 0) -- Wont do anything Entity:ensure(positionComponentClass, 0, 0) -- Wont do anything
-- Position is {x = 100, y = 100} -- Position is {x = 100, y = 100}
``` ```
@ -246,7 +285,7 @@ end
```lua ```lua
-- Assemble the Entity ( See Assemblages ) -- Assemble the Entity ( See Assemblages )
myEntity:assemble(assemblageFunction, 100, true, "foo") myEntity:assemble(myAssemblage, 100, true, "foo")
``` ```
```lua ```lua
@ -271,19 +310,28 @@ Systems can have multiple pools.
```lua ```lua
-- Create a System -- Create a System
local mySystemClass = Concord.system({pool = {"position"}}) -- Pool named 'pool' will contain all Entities with a position Component local mySystemClass = Concord.system({positionComponentClass}) -- Pool will contain all Entities with a position Component
-- Create a System with multiple pools -- Create a System with multiple pools
local mySystemClass = Concord.system({ local mySystemClass = Concord.system(
pool = { -- This pool will be named 'pool' { -- This Pool's name will default to 'pool'
"position", positionCompomponentClass,
"velocity", velocityComponentClass,
}, },
secondPool = { -- This pool's name will be 'secondPool' { -- This Pool's name will be 'secondPool'
"health", healthComponentClass,
"damageable", damageableComponentClass,
"secondPool",
} }
}) )
```
```lua
-- Manually register the SystemClass to the container if we want
Concord.system.register("mySystem", mySystemClass)
-- Otherwise return the SystemClass so it can be required
return mySystemClass
``` ```
```lua ```lua
@ -299,14 +347,19 @@ end
-- Defining a function -- Defining a function
function mySystemClass:update(dt) function mySystemClass:update(dt)
-- Iterate over all entities in the Pool -- Iterate over all entities in the Pool
for _, e in ipairs(self.pool) do for _, e in ipairs(self.pool)
-- Get the Entity's Components
local positionComponent = e[positionComponentClass]
local velocityComponent = e[velocityComponentClass]
-- Do something with the Components -- Do something with the Components
e.position.x = e.position.x + e.velocity.x * dt positionComponent.x = positionComponent.x + velocityComponent.x * dt
e.position.y = e.position.y + e.velocity.y * dt positionComponent.y = positionComponent.y + velocityComponent.y * dt
end end
-- Iterate over all entities in the second Pool -- Iterate over all entities in the second Pool
for _, e in ipairs(self.secondPool) do for _, e in ipairs(self.secondPool)
-- Do something -- Do something
end end
end end
@ -314,20 +367,24 @@ end
```lua ```lua
-- Systems can be enabled and disabled -- Systems can be enabled and disabled
-- When systems are disabled their callbacks won't be executed.
-- Note that pools will still be updated
-- This is mainly useful for systems that display debug information
-- Systems are enabled by default -- Systems are enabled by default
-- Enable a System -- Enable a System
mySystem:enable()
-- or
mySystem:setEnable(true) mySystem:setEnable(true)
-- Disable a System -- Disable a System
mySystem:disable()
-- or
mySystem:setEnable(false) mySystem:setEnable(false)
-- Toggle the enable state
mySystem:toggleEnabled()
-- Get enabled state -- Get enabled state
local isEnabled = mySystem:isEnabled() local isEnabled = mySystem:isEnabled()
print(isEnabled) -- false print(isEnabled) -- true
``` ```
```lua ```lua
@ -348,6 +405,14 @@ Worlds can have any number of Entities.
local myWorld = Concord.world() local myWorld = Concord.world()
``` ```
```lua
-- Manually register the World to the container if we want
Concord.worlds.register("myWorld", myWorld)
-- Otherwise return the World so it can be required
return myWorld
```
```lua ```lua
-- Add an Entity to the World -- Add an Entity to the World
myWorld:addEntity(myEntity) myWorld:addEntity(myEntity)
@ -406,31 +471,33 @@ end
### Assemblages ### Assemblages
Assemblages are functions to 'make' Entities something. Assemblages are helpers to 'make' Entities something.
An important distinction is that they _append_ Components. An important distinction is that they _append_ Components.
```lua ```lua
-- Make an Assemblage function -- Make an Assemblage
-- e is the Entity being assembled. -- e is the Entity being assembled.
-- cuteness and legs are variables passed in -- cuteness and legs are variables passed in
function animal(e, cuteness, legs) local animalAssemblage(function(e, cuteness, legs)
e e
:give(cutenessComponentClass, cuteness) :give(cutenessComponentClass, cuteness)
:give(limbs, legs, 0) -- Variable amount of legs. 0 arm. :give(limbs, legs, 0) -- Variable amount of legs. 0 arm.
end) end)
-- Make an Assemblage that uses animal -- Make an Assemblage that used animalAssemblage
-- cuteness is a variables passed in -- cuteness is a variables passed in
function cat(e, cuteness) local catAssemblage(function(e, cuteness)
e e
:assemble(animal, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs. :assemble(animalAssemblage, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs.
:give(soundComponent, "meow.mp3") :give(soundComponent, "meow.mp3")
end) end)
``` ```
```lua ```lua
-- Use an Assemblage -- Use an Assemblage
myEntity:assemble(cat, 100) -- 100 cuteness myEntity:assemble(catAssemblage, 100) -- 100 cuteness
-- or
catAssemblage:assemble(myEntity, 100) -- 100 cuteness
``` ```
--- ---
@ -439,40 +506,43 @@ myEntity:assemble(cat, 100) -- 100 cuteness
```lua ```lua
local Concord = require("concord") local Concord = require("concord")
-- Defining components -- Defining ComponentClasses
Concord.component("position", function(c, x, y) -- I use UpperCamelCase to indicate its a class
local Position = Concord.component(function(c, x, y)
c.x = x or 0 c.x = x or 0
c.y = y or 0 c.y = y or 0
end) end)
Concord.component("velocity", function(c, x, y) local Velocity = Concord.component(function(c, x, y)
c.x = x or 0 c.x = x or 0
c.y = y or 0 c.y = y or 0
end) end)
local Drawable = Concord.component("drawable") local Drawable = Concord.component()
-- Defining Systems -- Defining Systems
local MoveSystem = Concord.system({ local MoveSystem = Concord.system({Position, Velocity})
pool = {"position", "velocity"}
})
function MoveSystem:update(dt) function MoveSystem:update(dt)
for _, e in ipairs(self.pool) do for _, e in ipairs(self.pool) do
e.position.x = e.position.x + e.velocity.x * dt -- I use lowerCamelCase to indicate its an instance
e.position.y = e.position.y + e.velocity.y * dt local position = e[Position]
local velocity = e[Velocity]
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
end end
end end
local DrawSystem = Concord.system({ local DrawSystem = Concord.system({Position, Drawable})
pool = {"position", "drawable"}
})
function DrawSystem:draw() function DrawSystem:draw()
for _, e in ipairs(self.pool) do for _, e in ipairs(self.pool) do
love.graphics.circle("fill", e.position.x, e.position.y, 5) local position = e[Position]
love.graphics.circle("fill", position.x, position.y, 5)
end end
end end
@ -485,18 +555,18 @@ world:addSystems(MoveSystem, DrawSystem)
-- This Entity will be rendered on the screen, and move to the right at 100 pixels a second -- This Entity will be rendered on the screen, and move to the right at 100 pixels a second
local entity_1 = Concord.entity(world) local entity_1 = Concord.entity(world)
:give("position", 100, 100) :give(Position, 100, 100)
:give("velocity", 100, 0) :give(Velocity, 100, 0)
:give("drawable") :give(Drawable)
-- This Entity will be rendered on the screen, and stay at 50, 50 -- This Entity will be rendered on the screen, and stay at 50, 50
local entity_2 = Concord.entity(world) local entity_2 = Concord.entity(world)
:give("position", 50, 50) :give(Position, 50, 50)
:give("drawable") :give(Drawable)
-- This Entity does exist in the World, but since it doesn't match any System's filters it won't do anything -- This Entity does exist in the World, but since it doesn't match any System's filters it won't do anything
local entity_3 = Concord.entity(world) local entity_3 = Concord.entity(world)
:give("position", 200, 200) :give(Position, 200, 200)
-- Emit the events -- Emit the events
@ -521,5 +591,5 @@ end
--- ---
## License ## Licence
MIT Licensed - Copyright Justin van der Leij (Tjakka5) MIT Licensed - Copyright Justin van der Leij (Tjakka5)

49
concord/assemblage.lua Normal file
View file

@ -0,0 +1,49 @@
--- Gives an entity a set of components.
-- @classmod Assemblage
local Assemblage = {}
Assemblage.__mt = {
__index = Assemblage,
}
--- Creates a new Assemblage.
-- @tparam function assemble Function that assembles an Entity
-- @treturn Assemblage A new assemblage
function Assemblage.new(assemble)
local assemblage = setmetatable({
__assemble = assemble,
__name = nil,
__isAssemblage = true,
}, Assemblage.__mt)
return assemblage
end
--- Assembles an Entity.
-- @tparam Entity e Entity to assemble
-- @param ... additional arguments to pass to the assemble function
-- @treturn Assemblage self
function Assemblage:assemble(e, ...)
self.__assemble(e, ...)
return self
end
--- Returns true if the Assemblage has a name.
-- @treturn boolean
function Assemblage:hasName()
return self.__name and true or false
end
--- Returns the name of the Assemblage.
-- @treturn string
function Assemblage:getName()
return self.__name
end
return setmetatable(Assemblage, {
__call = function(_, ...)
return Assemblage.new(...)
end,
})

49
concord/assemblages.lua Normal file
View file

@ -0,0 +1,49 @@
--- A container for registered @{Assemblage}s
-- @module Assemblages
local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type")
local Assemblages = {}
--- Registers an Assemblage.
-- @string name Name to register under
-- @tparam Assemblage assemblage Assemblage to register
function Assemblages.register(name, assemblage)
if (type(name) ~= "string") then
error("bad argument #1 to 'Assemblages.register' (string expected, got "..type(name)..")", 3)
end
if (not Type.isAssemblage(assemblage)) then
error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(assemblage)..")", 3)
end
if (rawget(Assemblages, name)) then
error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' was already registerd)", 3)
end
Assemblages[name] = assemblage
assemblage.__name = name
end
--- Returns true if the containter has an Assemblage with the specified name
-- @string name Name of the Assemblage to check
-- @treturn boolean
function Assemblages.has(name)
return Assemblages[name] and true or false
end
--- Returns the Assemblage with the specified name
-- @string name Name of the Assemblage to get
-- @treturn Assemblage
function Assemblages.get(name)
return Assemblages[name]
end
return setmetatable(Assemblages, {
__index = function(_, name)
error("Attempt to index assemblage '"..tostring(name).."' that does not exist / was not registered", 2)
end
})

View file

@ -1,6 +0,0 @@
local PATH = (...):gsub("(%.init)$", "")
return {
serializable = require(PATH..".serializable"),
key = require(PATH..".key"),
}

View file

@ -1,41 +0,0 @@
local PATH = (...):gsub('%.builtins%.[^%.]+$', '')
local Component = require(PATH..".component")
local getKey = function (self, key)
local entity = self.__entity
if not entity:inWorld() then
error("entity needs to belong to a world")
end
local world = entity:getWorld()
return world:__assignKey(entity, key)
end
local Key = Component("key", function (self, key)
self.value = getKey(self, key)
end)
function Key:deserialize (data)
self.value = getKey(self, data)
end
function Key.__mt:__call()
return self.value
end
function Key:removed (replaced)
if not replaced then
local entity = self.__entity
if entity:inWorld() then
local world = entity:getWorld()
return world:__clearKey(entity)
end
end
end
return Key

View file

@ -1,12 +0,0 @@
local PATH = (...):gsub('%.builtins%.[^%.]+$', '')
local Component = require(PATH..".component")
local Serializable = Component("serializable")
function Serializable:serialize ()
-- Don't serialize this Component
return nil
end
return Serializable

View file

@ -1,11 +1,6 @@
--- A pure data container that is contained by a single entity. --- A pure data container that is contained by a single entity.
-- @classmod Component -- @classmod Component
local PATH = (...):gsub('%.[^%.]+$', '')
local Components = require(PATH..".components")
local Utils = require(PATH..".utils")
local Component = {} local Component = {}
Component.__mt = { Component.__mt = {
__index = Component, __index = Component,
@ -14,27 +9,15 @@ Component.__mt = {
--- Creates a new ComponentClass. --- Creates a new ComponentClass.
-- @tparam function populate Function that populates a Component with values -- @tparam function populate Function that populates a Component with values
-- @treturn Component A new ComponentClass -- @treturn Component A new ComponentClass
function Component.new(name, populate) function Component.new(populate)
if (type(name) ~= "string") then
Utils.error(2, "bad argument #1 to 'Component.new' (string expected, got %s)", type(name))
end
if (string.match(name, Components.__REJECT_MATCH) ~= "") then
Utils.error(2, "bad argument #1 to 'Component.new' (Component names can't start with '%s', got %s)", Components.__REJECT_PREFIX, name)
end
if (rawget(Components, name)) then
Utils.error(2, "bad argument #1 to 'Component.new' (ComponentClass with name '%s' was already registerd)", name) -- luacheck: ignore
end
if (type(populate) ~= "function" and type(populate) ~= "nil") then if (type(populate) ~= "function" and type(populate) ~= "nil") then
Utils.error(2, "bad argument #1 to 'Component.new' (function/nil expected, got %s)", type(populate)) error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
end end
local componentClass = setmetatable({ local componentClass = setmetatable({
__populate = populate, __populate = populate,
__name = name, __name = nil,
__isComponentClass = true, __isComponentClass = true,
}, Component.__mt) }, Component.__mt)
@ -42,45 +25,25 @@ function Component.new(name, populate)
__index = componentClass __index = componentClass
} }
Components[name] = componentClass
return componentClass return componentClass
end end
-- Internal: Populates a Component with values. -- Internal: Populates a Component with values
function Component:__populate() -- luacheck: ignore function Component:__populate() -- luacheck: ignore
end end
-- Callback: When the Component gets removed or replaced in an Entity. function Component:serialize() -- luacheck: ignore
function Component:removed() -- luacheck: ignore
end end
-- Callback: When the Component gets serialized as part of an Entity. function Component:deserialize(data) -- luacheck: ignore
function Component:serialize()
local data = Utils.shallowCopy(self, {})
--This values shouldn't be copied over
data.__componentClass = nil
data.__entity = nil
data.__isComponent = nil
data.__isComponentClass = nil
return data
end
-- Callback: When the Component gets deserialized from serialized data.
function Component:deserialize(data)
Utils.shallowCopy(data, self)
end end
-- Internal: Creates a new Component. -- Internal: Creates a new Component.
-- @param entity The Entity that will receive this Component.
-- @return A new Component -- @return A new Component
function Component:__new(entity) function Component:__new()
local component = setmetatable({ local component = setmetatable({
__componentClass = self, __componentClass = self,
__entity = entity,
__isComponent = true, __isComponent = true,
__isComponentClass = false, __isComponentClass = false,
}, self.__mt) }, self.__mt)
@ -89,13 +52,11 @@ function Component:__new(entity)
end end
-- Internal: Creates and populates a new Component. -- Internal: Creates and populates a new Component.
-- @param entity The Entity that will receive this Component.
-- @param ... Varargs passed to the populate function -- @param ... Varargs passed to the populate function
-- @return A new populated Component -- @return A new populated Component
function Component:__initialize(entity, ...) function Component:__initialize(...)
local component = self:__new(entity) local component = self:__new()
---@diagnostic disable-next-line: redundant-parameter
self.__populate(component, ...) self.__populate(component, ...)
return component return component

View file

@ -1,73 +1,48 @@
--- Container for registered ComponentClasses --- Container for registered ComponentClasses
-- @module Components -- @module Components
local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type")
local Components = {} local Components = {}
Components.__REJECT_PREFIX = "!" --- Registers a ComponentClass.
Components.__REJECT_MATCH = "^(%"..Components.__REJECT_PREFIX.."?)(.+)" -- @string name Name to register under
-- @tparam Component componentClass ComponentClass to register
function Components.register(name, componentClass)
if (type(name) ~= "string") then
error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3)
end
if (not Type.isComponentClass(componentClass)) then
error("bad argument #2 to 'Components.register' (ComponentClass expected, got "..type(componentClass)..")", 3)
end
if (rawget(Components, name)) then
error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3) -- luacheck: ignore
end
Components[name] = componentClass
componentClass.__name = name
end
--- Returns true if the containter has the ComponentClass with the specified name --- Returns true if the containter has the ComponentClass with the specified name
-- @string name Name of the ComponentClass to check -- @string name Name of the ComponentClass to check
-- @treturn boolean -- @treturn boolean
function Components.has(name) function Components.has(name)
return rawget(Components, name) and true or false return Components[name] and true or false
end
--- Prefix a component's name with the currently set Reject Prefix
-- @string name Name of the ComponentClass to reject
-- @treturn string
function Components.reject(name)
local ok, err = Components.try(name)
if not ok then error(err, 2) end
return Components.__REJECT_PREFIX..name
end
--- Returns true and the ComponentClass if one was registered with the specified name
-- or false and an error otherwise
-- @string name Name of the ComponentClass to check
-- @boolean acceptRejected Whether to accept names prefixed with the Reject Prefix.
-- @treturn boolean
-- @treturn Component or error string
-- @treturn true if acceptRejected was true and the name had the Reject Prefix, false otherwise.
function Components.try(name, acceptRejected)
if type(name) ~= "string" then
return false, "ComponentsClass name is expected to be a string, got "..type(name)..")"
end
local rejected = false
if acceptRejected then
local prefix
prefix, name = string.match(name, Components.__REJECT_MATCH)
rejected = prefix ~= "" and name
end
local value = rawget(Components, name)
if not value then
return false, "ComponentClass '"..name.."' does not exist / was not registered"
end
return true, value, rejected
end end
--- Returns the ComponentClass with the specified name --- Returns the ComponentClass with the specified name
-- @string name Name of the ComponentClass to get -- @string name Name of the ComponentClass to get
-- @treturn Component -- @treturn Component
function Components.get(name) function Components.get(name)
local ok, value = Components.try(name) return Components[name]
if not ok then error(value, 2) end
return value
end end
return setmetatable(Components, { return setmetatable(Components, {
__index = function(_, name) __index = function(_, name)
local ok, value = Components.try(name) error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2)
end
if not ok then error(value, 2) end
return value end
}) })

View file

@ -6,16 +6,8 @@ local PATH = (...):gsub('%.[^%.]+$', '')
local Components = require(PATH..".components") local Components = require(PATH..".components")
local Type = require(PATH..".type") local Type = require(PATH..".type")
local Utils = require(PATH..".utils")
-- Initialize built-in Components (as soon as possible)
local Builtins = require(PATH..".builtins.init") --luacheck: ignore
-- Builtins is unused but the require already registers the Components
local Entity = {
SERIALIZE_BY_DEFAULT = true,
}
local Entity = {}
Entity.__mt = { Entity.__mt = {
__index = Entity, __index = Entity,
} }
@ -25,11 +17,12 @@ Entity.__mt = {
-- @treturn Entity A new Entity -- @treturn Entity A new Entity
function Entity.new(world) function Entity.new(world)
if (world ~= nil and not Type.isWorld(world)) then if (world ~= nil and not Type.isWorld(world)) then
Utils.error(2, "bad argument #1 to 'Entity.new' (world/nil expected, got %s)", type(world)) error("bad argument #1 to 'Entity.new' (world/nil expected, got "..type(world)..")", 2)
end end
local e = setmetatable({ local e = setmetatable({
__world = nil, __world = nil,
__components = {},
__isEntity = true, __isEntity = true,
}, Entity.__mt) }, Entity.__mt)
@ -38,86 +31,23 @@ function Entity.new(world)
world:addEntity(e) world:addEntity(e)
end end
if Entity.SERIALIZE_BY_DEFAULT then
e:give("serializable")
end
return e return e
end end
local function createComponent(e, name, componentClass, ...) local function give(e, componentClass, ...)
local component = componentClass:__initialize(e, ...) local component = componentClass:__initialize(...)
local hadComponent = not not e[name]
if hadComponent then e[componentClass] = component
e[name]:removed(true) e.__components[componentClass] = component
end
e[name] = component e:__dirty()
if not hadComponent then
e:__dirty()
end
end end
local function deserializeComponent(e, name, componentData) local function remove(e, componentClass)
local componentClass = Components[name] e[componentClass] = nil
local hadComponent = not not e[name] e.__components[componentClass] = nil
if hadComponent then e:__dirty()
e[name]:removed(true)
end
local component = componentClass:__new(e)
component:deserialize(componentData)
e[name] = component
if not hadComponent then
e:__dirty()
end
end
local function giveComponent(e, ensure, name, ...)
local component
if Type.isComponent(name) then
component = name
name = component:getName()
end
if ensure and e[name] then
return e
end
local ok, componentClass = Components.try(name)
if not ok then
Utils.error(3, "bad argument #1 to 'Entity:%s' (%s)", ensure and 'ensure' or 'give', componentClass)
end
if component then
local data = component:deserialize()
if data == nil then
Utils.error(3, "bad argument #1 to 'Entity:$s' (Component '%s' couldn't be deserialized)", ensure and 'ensure' or 'give', name)
end
deserializeComponent(e, name, data)
else
createComponent(e, name, componentClass, ...)
end
return e
end
local function removeComponent(e, name)
if e[name] then
e[name]:removed(false)
e[name] = nil
e:__dirty()
end
end end
--- Gives an Entity a Component. --- Gives an Entity a Component.
@ -125,8 +55,14 @@ end
-- @tparam Component componentClass ComponentClass to add an instance of -- @tparam Component componentClass ComponentClass to add an instance of
-- @param ... additional arguments to pass to the Component's populate function -- @param ... additional arguments to pass to the Component's populate function
-- @treturn Entity self -- @treturn Entity self
function Entity:give(name, ...) function Entity:give(componentClass, ...)
return giveComponent(self, false, name, ...) if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2)
end
give(self, componentClass, ...)
return self
end end
--- Ensures an Entity to have a Component. --- Ensures an Entity to have a Component.
@ -134,35 +70,43 @@ end
-- @tparam Component componentClass ComponentClass to add an instance of -- @tparam Component componentClass ComponentClass to add an instance of
-- @param ... additional arguments to pass to the Component's populate function -- @param ... additional arguments to pass to the Component's populate function
-- @treturn Entity self -- @treturn Entity self
function Entity:ensure(name, ...) function Entity:ensure(componentClass, ...)
return giveComponent(self, true, name, ...) if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2)
end
if self[componentClass] then
return self
end
give(self, componentClass, ...)
return self
end end
--- Removes a Component from an Entity. --- Removes a Component from an Entity.
-- @tparam Component componentClass ComponentClass of the Component to remove -- @tparam Component componentClass ComponentClass of the Component to remove
-- @treturn Entity self -- @treturn Entity self
function Entity:remove(name) function Entity:remove(componentClass)
local ok, componentClass = Components.try(name) if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:remove' (ComponentClass expected, got "..type(componentClass)..")")
if not ok then
Utils.error(2, "bad argument #1 to 'Entity:remove' (%s)", componentClass)
end end
removeComponent(self, name) remove(self, componentClass)
return self return self
end end
--- Assembles an Entity. --- Assembles an Entity.
-- @tparam function assemblage Function that will assemble an entity -- @tparam Assemblage assemblage Assemblage to assemble with
-- @param ... additional arguments to pass to the assemblage function. -- @param ... additional arguments to pass to the Assemblage's assemble function.
-- @treturn Entity self -- @treturn Entity self
function Entity:assemble(assemblage, ...) function Entity:assemble(assemblage, ...)
if type(assemblage) ~= "function" then if not Type.isAssemblage(assemblage) then
Utils.error(2, "bad argument #1 to 'Entity:assemble' (function expected, got %s)", type(assemblage)) error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")")
end end
assemblage(self, ...) assemblage:assemble(self, ...)
return self return self
end end
@ -191,40 +135,31 @@ end
--- Returns true if the Entity has a Component. --- Returns true if the Entity has a Component.
-- @tparam Component componentClass ComponentClass of the Component to check -- @tparam Component componentClass ComponentClass of the Component to check
-- @treturn boolean -- @treturn boolean
function Entity:has(name) function Entity:has(componentClass)
local ok, componentClass = Components.try(name) if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:has' (ComponentClass expected, got "..type(componentClass)..")")
if not ok then
Utils.error(2, "bad argument #1 to 'Entity:has' (%s)", componentClass)
end end
return self[name] and true or false return self[componentClass] ~= nil
end end
--- Gets a Component from the Entity. --- Gets a Component from the Entity.
-- @tparam Component componentClass ComponentClass of the Component to get -- @tparam Component componentClass ComponentClass of the Component to get
-- @treturn table -- @treturn table
function Entity:get(name) function Entity:get(componentClass)
local ok, componentClass = Components.try(name) if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")")
if not ok then
Utils.error(2, "bad argument #1 to 'Entity:get' (%s)", componentClass)
end end
return self[name] return self[componentClass]
end end
--- Returns a table of all Components the Entity has. --- Returns a table of all Components the Entity has.
-- Warning: Do not modify this table. -- Warning: Do not modify this table.
-- Use Entity:give/ensure/remove instead -- Use Entity:give/ensure/remove instead
-- @treturn table Table of all Components the Entity has -- @treturn table Table of all Components the Entity has
function Entity:getComponents(output) function Entity:getComponents()
output = output or {} return self.__components
local components = Utils.shallowCopy(self, output)
components.__world = nil
components.__isEntity = nil
return components
end end
--- Returns true if the Entity is in a World. --- Returns true if the Entity is in a World.
@ -239,23 +174,15 @@ function Entity:getWorld()
return self.__world return self.__world
end end
function Entity:serialize(ignoreKey) function Entity:serialize()
local data = {} local data = {}
for name, component in pairs(self) do for _, component in pairs(self.__components) do
-- The key component needs to be treated separately. if component.__name then
if name == "key" and component.__name == "key" then
if not ignoreKey then
data.key = component.value
end
--We only care about components that were properly given to the entity
elseif Type.isComponent(component) and (component.__name == name) then
local componentData = component:serialize() local componentData = component:serialize()
componentData.__name = component.__name
if componentData ~= nil then data[#data + 1] = componentData
componentData.__name = component.__name
data[#data + 1] = componentData
end
end end
end end
@ -266,11 +193,19 @@ function Entity:deserialize(data)
for i = 1, #data do for i = 1, #data do
local componentData = data[i] local componentData = data[i]
if (not Components.has(componentData.__name)) then if (not Components[componentData.__name]) then
Utils.error(2, "bad argument #1 to 'Entity:deserialize' (ComponentClass '%s' wasn't yet loaded)", tostring(componentData.__name)) -- luacheck: ignore error("bad argument #1 to 'Entity:deserialize' (ComponentClass "..type(componentData.__name).." wasn't yet loaded)") -- luacheck: ignore
end end
deserializeComponent(self, componentData.__name, componentData) local componentClass = Components[componentData.__name]
local component = componentClass:__new()
component:deserialize(componentData)
self[componentClass] = component
self.__components[componentClass] = component
self:__dirty()
end end
end end

View file

@ -1,199 +0,0 @@
--- Used to filter Entities with specific Components
-- A Filter has an associated Pool that can contain any amount of Entities.
-- @classmod Filter
local PATH = (...):gsub('%.[^%.]+$', '')
local List = require(PATH..".list")
local Type = require(PATH..".type")
local Utils = require(PATH..".utils")
local Components = require(PATH..".components")
local Filter = {}
Filter.__mt = {
__index = Filter,
}
--- Validates a Filter Definition to make sure every component is valid.
-- @string name Name for the Filter.
-- @tparam table definition Table containing the Filter Definition
-- @tparam onComponent Optional function, called when a component is valid.
function Filter.validate (errorLevel, name, def, onComponent)
local filter = "World:query filter"
if name then
filter = ("filter '%s'"):format(name)
end
if type(def) ~= 'table' then
Utils.error(3 + errorLevel, "invalid component list for %s (table expected, got %s)", filter, type(def))
end
if not onComponent and def.constructor and not Type.isCallable(def.constructor) then
Utils.error(3 + errorLevel, "invalid pool constructor for %s (callable expected, got %s)", filter, type(def.constructor))
end
for n, component in ipairs(def) do
local ok, err, reject = Components.try(component, true)
if not ok then
Utils.error(3 + errorLevel, "invalid component for %s at position #%d (%s)", filter, n, err)
end
if onComponent then
onComponent(component, reject)
end
end
end
--- Parses the Filter Defintion into two tables
-- required: An array of all the required component names.
-- rejected: An array of all the components that will be rejected.
-- @string name Name for the Filter.
-- @tparam table definition Table containing the Filter Definition
-- @treturn table required
-- @treturn table rejected
function Filter.parse (name, def)
local filter = {}
Filter.validate(1, name, def, function (component, reject)
if reject then
table.insert(filter, reject)
table.insert(filter, false)
else
table.insert(filter, component)
table.insert(filter, true)
end
end)
return filter
end
function Filter.match (e, filter)
for i=#filter, 2, -2 do
local match = filter[i - 0]
local name = filter[i - 1]
if (not e[name]) == match then return false end
end
return true
end
local REQUIRED_METHODS = {"add", "remove", "has", "clear"}
local VALID_POOL_TYPES = {table=true, userdata=true, lightuserdata=true, cdata=true}
function Filter.isValidPool (name, pool)
local poolType = type(pool)
--Check that pool is not nil
if not VALID_POOL_TYPES[poolType] then
Utils.error(3, "invalid value returned by pool '%s' constructor (table expected, got %s).", name, type(pool))
end
--Check if methods are callables
for _, method in ipairs(REQUIRED_METHODS) do
if not Type.isCallable(pool[method]) then
Utils.error(3, "invalid :%s method on pool '%s' (callable expected, got %s).", method, name, type(pool[method]))
end
end
end
--- Creates a new Filter
-- @string name Name for the Filter.
-- @tparam table definition Table containing the Filter Definition
-- @treturn Filter The new Filter
-- @treturn Pool The associated Pool
function Filter.new (name, def)
local pool
if def.constructor then
pool = def.constructor(def)
Filter.isValidPool(name, pool)
else
pool = List()
end
local filter = Filter.parse(name, def)
local filter = setmetatable({
pool = pool,
__filter = filter,
__name = name,
__isFilter = true,
}, Filter.__mt)
return filter, pool
end
--- Checks if an Entity fulfills the Filter requirements.
-- @tparam Entity e Entity to check
-- @treturn boolean
function Filter:eligible(e)
return Filter.match(e, self.__filter)
end
function Filter:evaluate (e)
local has = self.pool:has(e)
local eligible = self:eligible(e)
if not has and eligible then
self.pool:add(e)
elseif has and not eligible then
self.pool:remove(e)
end
return self
end
-- Adds an Entity to the Pool, if it passes the Filter.
-- @param e Entity to add
-- @param bypass Whether to bypass the Filter or not.
-- @treturn Filter self
-- @treturn boolean Whether the entity was added or not.
function Filter:add (e, bypass)
if not bypass and not self:eligible(e) then
return self, false
end
self.pool:add(e)
return self, true
end
-- Remove an Entity from the Pool associated to this Filter.
-- @param e Entity to remove
-- @treturn Filter self
function Filter:remove (e)
self.pool:remove(e)
return self
end
-- Clear the Pool associated to this Filter.
-- @param e Entity to remove
-- @treturn Filter self
function Filter:clear (e)
self.pool:clear(e)
return self
end
-- Check if the Pool bound to this System contains the passed Entity
-- @param e Entity to check
-- @treturn boolean Whether the Entity exists.
function Filter:has (e)
return self.pool:has(e)
end
--- Gets the name of the Filter
-- @treturn string
function Filter:getName()
return self.__name
end
return setmetatable(Filter, {
__call = function(_, ...)
return Filter.new(...)
end,
})

View file

@ -4,12 +4,12 @@
local PATH = (...):gsub('%.init$', '') local PATH = (...):gsub('%.init$', '')
local Concord = { local Concord = {
_VERSION = "3.0", _VERSION = "2.0 Beta",
_DESCRIPTION = "A feature-complete ECS library", _DESCRIPTION = "A feature-complete ECS library",
_LICENCE = [[ _LICENCE = [[
MIT LICENSE MIT LICENSE
Copyright (c) 2020 Justin van der Leij / Tjakka5 Copyright (c) 2018 Justin van der Leij / Tjakka5
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the copy of this software and associated documentation files (the
@ -33,10 +33,82 @@ local Concord = {
} }
Concord.entity = require(PATH..".entity") Concord.entity = require(PATH..".entity")
Concord.component = require(PATH..".component") Concord.component = require(PATH..".component")
Concord.components = require(PATH..".components") Concord.components = require(PATH..".components")
Concord.system = require(PATH..".system") Concord.system = require(PATH..".system")
Concord.systems = require(PATH..".systems")
Concord.world = require(PATH..".world") Concord.world = require(PATH..".world")
Concord.utils = require(PATH..".utils") Concord.worlds = require(PATH..".worlds")
Concord.assemblage = require(PATH..".assemblage")
Concord.assemblages = require(PATH..".assemblages")
local function load(pathOrFiles, namespace)
if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then
error("bad argument #1 to 'load' (string/table of strings expected, got "..type(pathOrFiles)..")", 3)
end
if (type(pathOrFiles) == "string") then
local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore
if (info == nil or info.type ~= "directory") then
error("bad argument #1 to 'load' (path '"..pathOrFiles.."' not found)", 3)
end
local files = love.filesystem.getDirectoryItems(pathOrFiles)
for _, file in ipairs(files) do
local name = file:sub(1, #file - 4)
local path = pathOrFiles.."."..name
namespace.register(name, require(path))
end
elseif (type(pathOrFiles == "table")) then
for _, path in ipairs(pathOrFiles) do
if (type(path) ~= "string") then
error("bad argument #2 to 'load' (string/table of strings expected, got table containing "..type(path)..")", 3) -- luacheck: ignore
end
local name = path
local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/")
if (dotIndex or slashIndex) then
name = path:sub((dotIndex or slashIndex) + 1)
end
namespace.register(name, require(path))
end
end
end
--- Loads ComponentClasses and puts them in the Components container.
-- Accepts a table of paths to files: {"component_1", "component_2", "etc"}
-- Accepts a path to a directory with ComponentClasses: "components"
function Concord.loadComponents(pathOrFiles)
load(pathOrFiles, Concord.components)
end
--- Loads SystemClasses and puts them in the Systems container.
-- Accepts a table of paths to files: {"system_1", "system_2", "etc"}
-- Accepts a path to a directory with SystemClasses: "systems"
function Concord.loadSystems(pathOrFiles)
load(pathOrFiles, Concord.systems)
end
--- Loads Worlds and puts them in the Worlds container.
-- Accepts a table of paths to files: {"world_1", "world_2", "etc"}
-- Accepts a path to a directory with Worlds: "worlds"
function Concord.loadWorlds(pathOrFiles)
load(pathOrFiles, Concord.worlds)
end
--- Loads Assemblages and puts them in the Assemblages container.
-- Accepts a table of paths to files: {"assemblage_1", "assemblage_2", "etc"}
-- Accepts a path to a directory with Assemblages: "assemblages"
function Concord.loadAssemblages(pathOrFiles)
load(pathOrFiles, Concord.assemblages)
end
return Concord return Concord

View file

@ -16,24 +16,23 @@ end
--- Adds an object to the List. --- Adds an object to the List.
-- Object must be of reference type -- Object must be of reference type
-- Object may not be the string 'size', 'onAdded' or 'onRemoved' -- Object may not be the string 'size'
-- @param obj Object to add -- @param obj Object to add
-- @treturn List self -- @treturn List self
function List:add(obj) function List:__add(obj)
local size = self.size + 1 local size = self.size + 1
self[size] = obj self[size] = obj
self[obj] = size self[obj] = size
self.size = size self.size = size
if self.onAdded then self:onAdded(obj) end
return self return self
end end
--- Removes an object from the List. --- Removes an object from the List.
-- @param obj Object to remove -- @param obj Object to remove
-- @treturn List self -- @treturn List self
function List:remove(obj) function List:__remove(obj)
local index = self[obj] local index = self[obj]
if not index then return end if not index then return end
local size = self.size local size = self.size
@ -52,13 +51,12 @@ function List:remove(obj)
self[obj] = nil self[obj] = nil
self.size = size - 1 self.size = size - 1
if self.onRemoved then self:onRemoved(obj) end
return self return self
end end
--- Clears the List completely. --- Clears the List completely.
-- @treturn List self -- @treturn List self
function List:clear() function List:__clear()
for i = 1, self.size do for i = 1, self.size do
local o = self[i] local o = self[i]
@ -96,30 +94,6 @@ function List:indexOf(obj)
return self[obj] return self[obj]
end end
--- Sorts the List in place, using the order function.
-- The order function is passed to table.sort internally so documentation on table.sort can be used as reference.
-- @param order Function that takes two Entities (a and b) and returns true if a should go before than b.
-- @treturn List self
function List:sort(order)
table.sort(self, order)
for key, obj in ipairs(self) do
self[obj] = key
end
return self
end
--- Callback for when an item is added to the List.
-- @param obj Object that was added
function List:onAdded (obj) --luacheck: ignore
end
--- Callback for when an item is removed to the List.
-- @param obj Object that was removed
function List:onRemoved (obj) --luacheck: ignore
end
return setmetatable(List, { return setmetatable(List, {
__call = function() __call = function()
return List.new() return List.new()

90
concord/pool.lua Normal file
View file

@ -0,0 +1,90 @@
--- Used to iterate over Entities with a specific Components
-- A Pool contain a any amount of Entities.
-- @classmod Pool
local PATH = (...):gsub('%.[^%.]+$', '')
local List = require(PATH..".list")
local Pool = {}
Pool.__mt = {
__index = Pool,
}
--- Creates a new Pool
-- @string name Name for the Pool.
-- @tparam table filter Table containing the required BaseComponents
-- @treturn Pool The new Pool
function Pool.new(name, filter)
local pool = setmetatable(List(), Pool.__mt)
pool.__name = name
pool.__filter = filter
pool.__isPool = true
return pool
end
--- Checks if an Entity is eligible for the Pool.
-- @tparam Entity e Entity to check
-- @treturn boolean
function Pool:__eligible(e)
for _, component in ipairs(self.__filter) do
if not e[component] then
return false
end
end
return true
end
-- Internal: Adds an Entity to the Pool.
-- @param e Entity to add
-- @treturn Pool self
function Pool:__add(e)
List.__add(self, e)
self:onEntityAdded(e)
return self
end
-- Internal: Removed an Entity from the Pool.
-- @param e Entity to remove
-- @treturn Pool self
function Pool:__remove(e)
List.__remove(self, e)
self:onEntityRemoved(e)
return self
end
--- Gets the name of the Pool
-- @treturn string
function Pool:getName()
return self.__name
end
--- Gets the filter of the Pool.
-- Warning: Do not modify this filter.
-- @return Filter of the Pool.
function Pool:getFilter()
return self.__filter
end
--- Callback for when an Entity is added to the Pool.
-- @tparam Entity e Entity that was added.
function Pool:onEntityAdded(e) -- luacheck: ignore
end
-- Callback for when an Entity is removed from the Pool.
-- @tparam Entity e Entity that was removed.
function Pool:onEntityRemoved(e) -- luacheck: ignore
end
return setmetatable(Pool, {
__index = List,
__call = function(_, ...)
return Pool.new(...)
end,
})

View file

@ -5,8 +5,8 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Filter = require(PATH..".filter") local Pool = require(PATH..".pool")
local Utils = require(PATH..".utils") local Utils = require(PATH..".utils")
local System = { local System = {
ENABLE_OPTIMIZATION = true, ENABLE_OPTIMIZATION = true,
@ -18,25 +18,28 @@ System.mt = {
local system = setmetatable({ local system = setmetatable({
__enabled = true, __enabled = true,
__filters = {}, __pools = {},
__world = world, __world = world,
__isSystem = true, __isSystem = true,
__isSystemClass = false, -- Overwrite value from systemClass __isSystemClass = false, -- Overwrite value from systemClass
}, systemClass) }, systemClass)
-- Optimization: We deep copy the System class into our instance of a system. -- Optimization: We deep copy the World class into our instance of a world.
-- This grants slightly faster access times at the cost of memory. -- This grants slightly faster access times at the cost of memory.
-- Since there (generally) won't be many instances of worlds this is a worthwhile tradeoff -- Since there (generally) won't be many instances of worlds this is a worthwhile tradeoff
if (System.ENABLE_OPTIMIZATION) then if (System.ENABLE_OPTIMIZATION) then
Utils.shallowCopy(systemClass, system) Utils.shallowCopy(systemClass, system)
end end
for name, def in pairs(systemClass.__definition) do for _, filter in pairs(systemClass.__filter) do
local filter, pool = Filter(name, Utils.shallowCopy(def, {})) local pool = system.__buildPool(filter)
if not system[pool.__name] then
system[name] = pool system[pool.__name] = pool
table.insert(system.__filters, filter) system.__pools[#system.__pools + 1] = pool
else
error("Pool with name '"..pool.name.."' already exists.")
end
end end
system:init(world) system:init(world)
@ -44,23 +47,15 @@ System.mt = {
return system return system
end, end,
} }
--- Creates a new SystemClass. --- Creates a new SystemClass.
-- @param table filters A table containing filters (name = {components...}) -- @param ... Variable amounts of filters
-- @treturn System A new SystemClass -- @treturn System A new SystemClass
function System.new(definition) function System.new(...)
definition = definition or {}
for name, def in pairs(definition) do
if type(name) ~= 'string' then
Utils.error(2, "invalid name for filter (string key expected, got %s)", type(name))
end
Filter.validate(0, name, def)
end
local systemClass = setmetatable({ local systemClass = setmetatable({
__definition = definition, __filter = {...},
__name = nil,
__isSystemClass = true, __isSystemClass = true,
}, System.mt) }, System.mt)
systemClass.__index = systemClass systemClass.__index = systemClass
@ -75,12 +70,37 @@ function System.new(definition)
return systemClass return systemClass
end end
-- Internal: Builds a Pool for the System.
-- @param baseFilter The 'raw' Filter
-- @return A new Pool
function System.__buildPool(baseFilter)
local name = "pool"
local filter = {}
for _, value in ipairs(baseFilter) do
if type(value) == "table" then
filter[#filter + 1] = value
elseif type(value) == "string" then
name = value
end
end
return Pool(name, filter)
end
-- Internal: Evaluates an Entity for all the System's Pools. -- Internal: Evaluates an Entity for all the System's Pools.
-- @param e The Entity to check -- @param e The Entity to check
-- @treturn System self -- @treturn System self
function System:__evaluate(e) function System:__evaluate(e)
for _, filter in ipairs(self.__filters) do for _, pool in ipairs(self.__pools) do
filter:evaluate(e) local has = pool:has(e)
local eligible = pool:__eligible(e)
if not has and eligible then
pool:__add(e)
elseif has and not eligible then
pool:__remove(e)
end
end end
return self return self
@ -90,9 +110,9 @@ end
-- @param e The Entity to remove -- @param e The Entity to remove
-- @treturn System self -- @treturn System self
function System:__remove(e) function System:__remove(e)
for _, filter in ipairs(self.__filters) do for _, pool in ipairs(self.__pools) do
if filter:has(e) then if pool:has(e) then
filter:remove(e) pool:__remove(e)
end end
end end
@ -102,13 +122,37 @@ end
-- Internal: Clears all Entities from the System. -- Internal: Clears all Entities from the System.
-- @treturn System self -- @treturn System self
function System:__clear() function System:__clear()
for _, filter in ipairs(self.__filters) do for i = 1, #self.__pools do
filter:clear() self.__pools[i]:__clear()
end end
return self return self
end end
--- Enables the System.
-- @treturn System self
function System:enable()
self:setEnabled(true)
return self
end
--- Disables the System.
-- @treturn System self
function System:disable()
self:setEnabled(false)
return self
end
--- Toggles if the System is enabled.
-- @treturn System self
function System:toggleEnabled()
self:setEnabled(not self.__enabled)
return self
end
--- Sets if the System is enabled --- Sets if the System is enabled
-- @tparam boolean enable -- @tparam boolean enable
-- @treturn System self -- @treturn System self
@ -136,6 +180,18 @@ function System:getWorld()
return self.__world return self.__world
end end
--- Returns true if the System has a name.
-- @treturn boolean
function System:hasName()
return self.__name and true or false
end
--- Returns the name of the System.
-- @treturn string
function System:getName()
return self.__name
end
--- Callbacks --- Callbacks
-- @section Callbacks -- @section Callbacks

48
concord/systems.lua Normal file
View file

@ -0,0 +1,48 @@
--- Container for registered SystemClasses
-- @module Systems
local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type")
local Systems = {}
--- Registers a SystemClass.
-- @tparam string name Name to register under
-- @tparam System systemClass SystemClass to register
function Systems.register(name, systemClass)
if (type(name) ~= "string") then
error("bad argument #1 to 'Systems.register' (string expected, got "..type(name)..")", 3)
end
if (not Type.isSystemClass(systemClass)) then
error("bad argument #2 to 'Systems.register' (systemClass expected, got "..type(systemClass)..")", 3)
end
if (rawget(Systems, name)) then
error("bad argument #2 to 'Systems.register' (System with name '"..name.."' is already registerd)", 3)
end
Systems[name] = systemClass
systemClass.__name = name
end
--- Returns true if the containter has the SystemClass with the name
-- @tparam string name Name of the SystemClass to check
-- @treturn boolean
function Systems.has(name)
return Systems[name] and true or false
end
--- Returns the SystemClass with the name
-- @tparam string name Name of the SystemClass to get
-- @return SystemClass with the name
function Systems.get(name)
return Systems[name]
end
return setmetatable(Systems, {
__index = function(_, name)
error("Attempt to index system '"..tostring(name).."' that does not exist / was not registered", 2)
end
})

View file

@ -3,15 +3,6 @@
local Type = {} local Type = {}
function Type.isCallable(t)
if type(t) == "function" then return true end
local meta = getmetatable(t)
if meta and type(meta.__call) == "function" then return true end
return false
end
--- Returns if object is an Entity. --- Returns if object is an Entity.
-- @param t Object to check -- @param t Object to check
-- @treturn boolean -- @treturn boolean
@ -54,11 +45,11 @@ function Type.isWorld(t)
return type(t) == "table" and t.__isWorld or false return type(t) == "table" and t.__isWorld or false
end end
--- Returns if object is a Filter. --- Returns if object is an Assemblage.
-- @param t Object to check -- @param t Object to check
-- @treturn boolean -- @treturn boolean
function Type.isFilter(t) function Type.isAssemblage(t)
return type(t) == "table" and t.__isFilter or false return type(t) == "table" and t.__isAssemblage or false
end end
return Type return Type

View file

@ -3,70 +3,13 @@
local Utils = {} local Utils = {}
function Utils.error(level, str, ...)
error(string.format(str, ...), level + 1)
end
--- Does a shallow copy of a table and appends it to a target table. --- Does a shallow copy of a table and appends it to a target table.
-- @param orig Table to copy -- @param orig Table to copy
-- @param target Table to append to -- @param target Table to append to
function Utils.shallowCopy(orig, target) function Utils.shallowCopy(orig, target)
for key, value in pairs(orig) do for key, value in pairs(orig) do
target[key] = value target[key] = value
end end
return target
end end
--- Requires files and puts them in a table. return Utils
-- Accepts a table of paths to Lua files: {"path/to/file_1", "path/to/another/file_2", "etc"}
-- Accepts a path to a directory with Lua files: "my_files/here"
-- @param pathOrFiles The table of paths or a path to a directory.
-- @param namespace A table that will hold the required files
-- @treturn table The namespace table
function Utils.loadNamespace(pathOrFiles, namespace)
if type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table" then
Utils.error(2, "bad argument #1 to 'loadNamespace' (string/table of strings expected, got %s)", type(pathOrFiles))
end
if type(pathOrFiles) == "string" then
local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore
if info == nil or info.type ~= "directory" then
Utils.error(2, "bad argument #1 to 'loadNamespace' (path '%s' not found)", pathOrFiles)
end
local files = love.filesystem.getDirectoryItems(pathOrFiles)
for _, file in ipairs(files) do
local isFile = love.filesystem.getInfo(pathOrFiles .. "/" .. file).type == "file"
if isFile and string.match(file, '%.lua$') ~= nil then
local name = file:sub(1, #file - 4)
local path = pathOrFiles.."."..name
local value = require(path:gsub("%/", "."))
if namespace then namespace[name] = value end
end
end
elseif type(pathOrFiles) == "table" then
for _, path in ipairs(pathOrFiles) do
if type(path) ~= "string" then
Utils.error(2, "bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing %s)", type(path)) -- luacheck: ignore
end
local name = path
local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/")
if dotIndex or slashIndex then
name = path:sub((dotIndex or slashIndex) + 1)
end
local value = require(path:gsub("%/", "."))
if namespace then namespace[name] = value end
end
end
return namespace
end
return Utils

View file

@ -6,12 +6,10 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Filter = require(PATH..".filter") local Entity = require(PATH..".entity")
local Entity = require(PATH..".entity") local Type = require(PATH..".type")
local Components = require(PATH..".components") local List = require(PATH..".list")
local Type = require(PATH..".type") local Utils = require(PATH..".utils")
local List = require(PATH..".list")
local Utils = require(PATH..".utils")
local World = { local World = {
ENABLE_OPTIMIZATION = true, ENABLE_OPTIMIZATION = true,
@ -20,12 +18,6 @@ World.__mt = {
__index = World, __index = World,
} }
local defaultGenerator = function (state)
local current = state
state = state +1
return string.format("%d", current), state
end
--- Creates a new World. --- Creates a new World.
-- @treturn World The new World -- @treturn World The new World
function World.new() function World.new()
@ -36,24 +28,14 @@ function World.new()
__events = {}, __events = {},
__emitSDepth = 0, __emitSDepth = 0,
__resources = {},
__hash = {
state = -2^53,
generator = defaultGenerator,
keys = {},
entities = {}
},
__added = List(), __backAdded = List(), __added = List(), __backAdded = List(),
__removed = List(), __backRemoved = List(), __removed = List(), __backRemoved = List(),
__dirty = List(), __backDirty = List(), __dirty = List(), __backDirty = List(),
__systemLookup = {}, __systemLookup = {},
__name = nil,
__isWorld = true, __isWorld = true,
__ignoreEmits = false
}, World.__mt) }, World.__mt)
-- Optimization: We deep copy the World class into our instance of a world. -- Optimization: We deep copy the World class into our instance of a world.
@ -71,7 +53,7 @@ end
-- @treturn World self -- @treturn World self
function World:addEntity(e) function World:addEntity(e)
if not Type.isEntity(e) then if not Type.isEntity(e) then
Utils.error(2, "bad argument #1 to 'World:addEntity' (Entity expected, got %s)", type(e)) error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2)
end end
if e.__world then if e.__world then
@ -79,55 +61,20 @@ function World:addEntity(e)
end end
e.__world = self e.__world = self
self.__added:add(e) self.__added:__add(e)
return self return self
end end
--- Creates a new Entity and adds it to the World.
-- @treturn Entity e the new Entity
function World:newEntity()
return Entity(self)
end
function World:query(def, onMatch)
local filter = Filter.parse(nil, def)
local list = nil
if not Type.isCallable(onMatch) then
list = type(onMatch) == "table" and onMatch or {}
end
for _, e in ipairs(self.__entities) do
if Filter.match(e, filter) then
if list then
table.insert(list, e)
else
onMatch(e)
end
end
end
return list
end
--- Removes an Entity from the World. --- Removes an Entity from the World.
-- @tparam Entity e Entity to remove -- @tparam Entity e Entity to remove
-- @treturn World self -- @treturn World self
function World:removeEntity(e) function World:removeEntity(e)
if not Type.isEntity(e) then if not Type.isEntity(e) then
Utils.error(2, "bad argument #1 to 'World:removeEntity' (Entity expected, got %s)", type(e)) error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
end end
if e.__world ~= self then self.__removed:__add(e)
error("trying to remove an Entity from a World it doesn't belong to", 2)
end
if e:has("key") then
e:remove("key")
end
self.__removed:add(e)
return self return self
end end
@ -136,7 +83,7 @@ end
-- @param e Entity to mark as dirty -- @param e Entity to mark as dirty
function World:__dirtyEntity(e) function World:__dirtyEntity(e)
if not self.__dirty:has(e) then if not self.__dirty:has(e) then
self.__dirty:add(e) self.__dirty:__add(e)
end end
end end
@ -160,46 +107,40 @@ function World:__flush()
for i = 1, self.__backAdded.size do for i = 1, self.__backAdded.size do
e = self.__backAdded[i] e = self.__backAdded[i]
if e.__world == self then self.__entities:__add(e)
self.__entities:add(e)
for j = 1, self.__systems.size do for j = 1, self.__systems.size do
self.__systems[j]:__evaluate(e) self.__systems[j]:__evaluate(e)
end
self:onEntityAdded(e)
end end
self:onEntityAdded(e)
end end
self.__backAdded:clear() self.__backAdded:__clear()
-- Process removed entities -- Process removed entities
for i = 1, self.__backRemoved.size do for i = 1, self.__backRemoved.size do
e = self.__backRemoved[i] e = self.__backRemoved[i]
if e.__world == self then e.__world = nil
e.__world = nil self.__entities:__remove(e)
self.__entities:remove(e)
for j = 1, self.__systems.size do for j = 1, self.__systems.size do
self.__systems[j]:__remove(e) self.__systems[j]:__remove(e)
end
self:onEntityRemoved(e)
end end
self:onEntityRemoved(e)
end end
self.__backRemoved:clear() self.__backRemoved:__clear()
-- Process dirty entities -- Process dirty entities
for i = 1, self.__backDirty.size do for i = 1, self.__backDirty.size do
e = self.__backDirty[i] e = self.__backDirty[i]
if e.__world == self then for j = 1, self.__systems.size do
for j = 1, self.__systems.size do self.__systems[j]:__evaluate(e)
self.__systems[j]:__evaluate(e)
end
end end
end end
self.__backDirty:clear() self.__backDirty:__clear()
return self return self
end end
@ -211,31 +152,37 @@ local blacklistedSystemFunctions = {
"onDisabled", "onDisabled",
} }
local tryAddSystem = function (world, systemClass) --- Adds a System to the World.
-- Callbacks are registered automatically
-- Entities added before are added to the System retroactively
-- @see World:emit
-- @tparam System systemClass SystemClass of System to add
-- @treturn World self
function World:addSystem(systemClass)
if (not Type.isSystemClass(systemClass)) then if (not Type.isSystemClass(systemClass)) then
return false, "SystemClass expected, got "..type(systemClass) error("bad argument #1 to 'World:addSystems' (SystemClass expected, got "..type(systemClass)..")", 2)
end end
if (world.__systemLookup[systemClass]) then if (self.__systemLookup[systemClass]) then
return false, "SystemClass was already added to World" error("bad argument #1 to 'World:addSystems' (SystemClass was already added to World)", 2)
end end
-- Create instance of system -- Create instance of system
local system = systemClass(world) local system = systemClass(self)
world.__systemLookup[systemClass] = system self.__systemLookup[systemClass] = system
world.__systems:add(system) self.__systems:__add(system)
for callbackName, callback in pairs(systemClass) do for callbackName, callback in pairs(systemClass) do
-- Skip callback if its blacklisted -- Skip callback if its blacklisted
if (not blacklistedSystemFunctions[callbackName]) then if (not blacklistedSystemFunctions[callbackName]) then
-- Make container for all listeners of the callback if it does not exist yet -- Make container for all listeners of the callback if it does not exist yet
if (not world.__events[callbackName]) then if (not self.__events[callbackName]) then
world.__events[callbackName] = {} self.__events[callbackName] = {}
end end
-- Add callback to listeners -- Add callback to listeners
local listeners = world.__events[callbackName] local listeners = self.__events[callbackName]
listeners[#listeners + 1] = { listeners[#listeners + 1] = {
system = system, system = system,
callback = callback, callback = callback,
@ -244,24 +191,8 @@ local tryAddSystem = function (world, systemClass)
end end
-- Evaluate all existing entities -- Evaluate all existing entities
for j = 1, world.__entities.size do for j = 1, self.__entities.size do
system:__evaluate(world.__entities[j]) system:__evaluate(self.__entities[j])
end
return true
end
--- Adds a System to the World.
-- Callbacks are registered automatically
-- Entities added before are added to the System retroactively
-- @see World:emit
-- @tparam System systemClass SystemClass of System to add
-- @treturn World self
function World:addSystem(systemClass)
local ok, err = tryAddSystem(self, systemClass)
if not ok then
Utils.error(2, "bad argument #1 to 'World:addSystem' (%s)", err)
end end
return self return self
@ -277,10 +208,7 @@ function World:addSystems(...)
for i = 1, select("#", ...) do for i = 1, select("#", ...) do
local systemClass = select(i, ...) local systemClass = select(i, ...)
local ok, err = tryAddSystem(self, systemClass) self:addSystem(systemClass)
if not ok then
Utils.error(2, "bad argument #%d to 'World:addSystems' (%s)", i, err)
end
end end
return self return self
@ -291,7 +219,7 @@ end
-- @treturn boolean -- @treturn boolean
function World:hasSystem(systemClass) function World:hasSystem(systemClass)
if not Type.isSystemClass(systemClass) then if not Type.isSystemClass(systemClass) then
Utils.error(2, "bad argument #1 to 'World:hasSystem' (SystemClass expected, got %s)", type(systemClass)) error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
end end
return self.__systemLookup[systemClass] and true or false return self.__systemLookup[systemClass] and true or false
@ -302,7 +230,7 @@ end
-- @treturn System System to get -- @treturn System System to get
function World:getSystem(systemClass) function World:getSystem(systemClass)
if not Type.isSystemClass(systemClass) then if not Type.isSystemClass(systemClass) then
Utils.error(2, "bad argument #1 to 'World:getSystem' (SystemClass expected, got %s)", type(systemClass)) error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
end end
return self.__systemLookup[systemClass] return self.__systemLookup[systemClass]
@ -315,21 +243,14 @@ end
-- @treturn World self -- @treturn World self
function World:emit(functionName, ...) function World:emit(functionName, ...)
if not functionName or type(functionName) ~= "string" then if not functionName or type(functionName) ~= "string" then
Utils.error(2, "bad argument #1 to 'World:emit' (String expected, got %s)", type(functionName)) error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")")
end end
local shouldFlush = self.__emitSDepth == 0 local shouldFlush = self.__emitSDepth == 0
self.__emitSDepth = self.__emitSDepth + 1 self.__emitSDepth = self.__emitSDepth + 1
local listeners = self.__events[functionName] local listeners = self.__events[functionName]
if not self.__ignoreEmits and Type.isCallable(self.beforeEmit) then
self.__ignoreEmits = true
local preventDefaults = self:beforeEmit(functionName, listeners, ...)
self.__ignoreEmits = false
if preventDefaults then return end
end
if listeners then if listeners then
for i = 1, #listeners do for i = 1, #listeners do
@ -345,12 +266,6 @@ function World:emit(functionName, ...)
end end
end end
if not self.__ignoreEmits and Type.isCallable(self.afterEmit) then
self.__ignoreEmits = true
self:afterEmit(functionName, listeners, ...)
self.__ignoreEmits = false
end
self.__emitSDepth = self.__emitSDepth - 1 self.__emitSDepth = self.__emitSDepth - 1
return self return self
@ -363,13 +278,9 @@ function World:clear()
self:removeEntity(self.__entities[i]) self:removeEntity(self.__entities[i])
end end
for i = 1, self.__added.size do for i = 1, self.__systems.size do
local e = self.__added[i] self.__systems[i]:__clear()
e.__world = nil
end end
self.__added:clear()
self:__flush()
return self return self
end end
@ -382,103 +293,49 @@ function World:getSystems()
return self.__systems return self.__systems
end end
function World:serialize(ignoreKeys) function World:serialize()
self:__flush() self:__flush()
local data = { generator = self.__hash.state } local data = {}
for i = 1, self.__entities.size do for i = 1, self.__entities.size do
local entity = self.__entities[i] local entity = self.__entities[i]
if entity.serializable then local entityData = entity:serialize()
local entityData = entity:serialize(ignoreKeys)
table.insert(data, entityData) data[i] = entityData
end
end end
return data return data
end end
function World:deserialize(data, startClean, ignoreGenerator) function World:deserialize(data, append)
if startClean then if (not append) then
self:clear() self:clear()
end end
if (not ignoreGenerator) then
self.__hash.state = data.generator
end
local entities = {}
for i = 1, #data do for i = 1, #data do
local entity = Entity(self) local entityData = data[i]
if data[i].key then local entity = Entity()
local component = Components.key:__new(entity) entity:deserialize(entityData)
component:deserialize(data[i].key)
entity.key = component
entity:__dirty() self:addEntity(entity)
end
entities[i] = entity
end
for i = 1, #data do
entities[i]:deserialize(data[i])
end end
self:__flush() self:__flush()
return self
end end
function World:setKeyGenerator(generator, initialState) --- Returns true if the World has a name.
if not Type.isCallable(generator) then -- @treturn boolean
Utils.error(2, "bad argument #1 to 'World:setKeyGenerator' (function expected, got %s)", type(generator)) function World:hasName()
end return self.__name and true or false
self.__hash.generator = generator
self.__hash.state = initialState
return self
end end
function World:__clearKey(e) --- Returns the name of the World.
local key = self.__hash.keys[e] -- @treturn string
function World:getName()
if key then return self.__name
self.__hash.keys[e] = nil
self.__hash.entities[key] = nil
end
return self
end
function World:__assignKey(e, key)
local hash = self.__hash
if not key then
key = hash.keys[e]
if key then return key end
key, hash.state = hash.generator(hash.state)
end
if hash.entities[key] and hash.entities[key] ~= e then
Utils.error(4, "Trying to assign a key that is already taken (key: '%s').", key)
elseif hash.keys[e] and hash.keys[e] ~= key then
Utils.error(4, "Trying to assign more than one key to an Entity. (old: '%s', new: '%s')", hash.keys[e], key)
end
hash.keys[e] = key
hash.entities[key] = e
return key
end
function World:getEntityByKey(key)
return self.__hash.entities[key]
end end
--- Callback for when an Entity is added to the World. --- Callback for when an Entity is added to the World.
@ -491,25 +348,8 @@ end
function World:onEntityRemoved(e) -- luacheck: ignore function World:onEntityRemoved(e) -- luacheck: ignore
end end
--- Sets a named resource in the world
-- @string name Name of the resource
-- @tparam Any resource Resource to set
-- @treturn World self
function World:setResource(name, resource)
self.__resources[name] = resource
return self
end
--- Gets a named resource from the world
-- @string name Name of the resource
-- @treturn Any resource
function World:getResource(name)
return self.__resources[name]
end
return setmetatable(World, { return setmetatable(World, {
__call = function(_, ...) __call = function(_, ...)
---@diagnostic disable-next-line: redundant-parameter
return World.new(...) return World.new(...)
end, end,
}) })

48
concord/worlds.lua Normal file
View file

@ -0,0 +1,48 @@
--- Worlds
-- Container for registered Worlds
local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type")
local Worlds = {}
--- Registers a World.
-- @tparam string name Name to register under
-- @param world World to register
function Worlds.register(name, world)
if (type(name) ~= "string") then
error("bad argument #1 to 'Worlds.register' (string expected, got "..type(name)..")", 3)
end
if (not Type.isWorld(world)) then
error("bad argument #2 to 'Worlds.register' (world expected, got "..type(world)..")", 3)
end
if (rawget(Worlds, name)) then
error("bad argument #2 to 'Worlds.register' (World with name '"..name.."' is already registerd)", 3)
end
Worlds[name] = world
world.__name = name
end
--- Returns true if the containter has the World with the name
-- @tparam string name Name of the World to check
-- @treturn boolean
function Worlds.has(name)
return Worlds[name] and true or false
end
--- Returns the World with the name
-- @tparam string name Name of the World to get
-- @return World with the name
function Worlds.get(name)
return Worlds[name]
end
return setmetatable(Worlds, {
__index = function(_, name)
error("Attempt to index world '"..tostring(name).."' that does not exist / was not registered", 2)
end
})

View file

@ -38,6 +38,7 @@
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><strong>Component</strong></li> <li><strong>Component</strong></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -47,10 +48,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -159,7 +163,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -38,6 +38,7 @@
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><strong>Entity</strong></li> <li><strong>Entity</strong></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -47,10 +48,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -60,7 +64,8 @@
<h1>Class <code>Entity</code></h1> <h1>Class <code>Entity</code></h1>
<p>An object that exists in a world.</p> <p>An object that exists in a world.</p>
<p> An entity <p> An entity
contains components which are processed by systems.</p> contains components which are processed by systems.
</p>
<h2><a href="#Methods">Methods</a></h2> <h2><a href="#Methods">Methods</a></h2>
@ -246,11 +251,11 @@
<h3>Parameters:</h3> <h3>Parameters:</h3>
<ul> <ul>
<li><span class="parameter">assemblage</span> <li><span class="parameter">assemblage</span>
<span class="types"><span class="type">function</span></span> <span class="types"><a class="type" href="../classes/Assemblage.html#">Assemblage</a></span>
Function that will assemble an entity Assemblage to assemble with
</li> </li>
<li><span class="parameter">...</span> <li><span class="parameter">...</span>
additional arguments to pass to the assemblage function. additional arguments to pass to the Assemblage's assemble function.
</li> </li>
</ul> </ul>
@ -408,7 +413,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -33,11 +33,13 @@
<h2>Contents</h2> <h2>Contents</h2>
<ul> <ul>
<li><a href="#Methods">Methods</a></li> <li><a href="#Methods">Methods</a></li>
<li><a href="#Metamethods">Metamethods</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><strong>List</strong></li> <li><strong>List</strong></li>
@ -47,10 +49,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -69,18 +74,6 @@
<td class="summary">Creates a new List.</td> <td class="summary">Creates a new List.</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#List:add">List:add (obj)</a></td>
<td class="summary">Adds an object to the List.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#List:remove">List:remove (obj)</a></td>
<td class="summary">Removes an object from the List.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#List:clear">List:clear ()</a></td>
<td class="summary">Clears the List completely.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#List:has">List:has (obj)</a></td> <td class="name" nowrap><a href="#List:has">List:has (obj)</a></td>
<td class="summary">Returns true if the List has the object.</td> <td class="summary">Returns true if the List has the object.</td>
</tr> </tr>
@ -93,6 +86,21 @@
<td class="summary">Returns the index of an object in the List.</td> <td class="summary">Returns the index of an object in the List.</td>
</tr> </tr>
</table> </table>
<h2><a href="#Metamethods">Metamethods</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#List:__add">List:__add (obj)</a></td>
<td class="summary">Adds an object to the List.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#List:__remove">List:__remove (obj)</a></td>
<td class="summary">Removes an object from the List.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#List:__clear">List:__clear ()</a></td>
<td class="summary">Clears the List completely.</td>
</tr>
</table>
<br/> <br/>
<br/> <br/>
@ -120,80 +128,6 @@
</dd>
<dt>
<a name = "List:add"></a>
<strong>List:add (obj)</strong>
</dt>
<dd>
Adds an object to the List.
Object must be of reference type
Object may not be the string 'size'
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">obj</span>
Object to add
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd>
<dt>
<a name = "List:remove"></a>
<strong>List:remove (obj)</strong>
</dt>
<dd>
Removes an object from the List.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">obj</span>
Object to remove
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd>
<dt>
<a name = "List:clear"></a>
<strong>List:clear ()</strong>
</dt>
<dd>
Clears the List completely.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd> </dd>
<dt> <dt>
<a name = "List:has"></a> <a name = "List:has"></a>
@ -272,6 +206,84 @@
</dd>
</dl>
<h2 class="section-header "><a name="Metamethods"></a>Metamethods</h2>
<dl class="function">
<dt>
<a name = "List:__add"></a>
<strong>List:__add (obj)</strong>
</dt>
<dd>
Adds an object to the List.
Object must be of reference type
Object may not be the string 'size'
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">obj</span>
Object to add
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd>
<dt>
<a name = "List:__remove"></a>
<strong>List:__remove (obj)</strong>
</dt>
<dd>
Removes an object from the List.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">obj</span>
Object to remove
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd>
<dt>
<a name = "List:__clear"></a>
<strong>List:__clear ()</strong>
</dt>
<dd>
Clears the List completely.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/List.html#">List</a></span>
self
</ol>
</dd> </dd>
</dl> </dl>
@ -280,7 +292,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -33,11 +33,13 @@
<h2>Contents</h2> <h2>Contents</h2>
<ul> <ul>
<li><a href="#Methods">Methods</a></li> <li><a href="#Methods">Methods</a></li>
<li><a href="#Metamethods">Metamethods</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -47,10 +49,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -70,14 +75,6 @@
<td class="summary">Creates a new Pool</td> <td class="summary">Creates a new Pool</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#Pool:eligible">Pool:eligible (e)</a></td>
<td class="summary">Checks if an Entity is eligible for the Pool.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Pool:evaluate">Pool:evaluate (e)</a></td>
<td class="summary">Evaluate whether an Entity should be added or removed from the Pool.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Pool:getName">Pool:getName ()</a></td> <td class="name" nowrap><a href="#Pool:getName">Pool:getName ()</a></td>
<td class="summary">Gets the name of the Pool</td> <td class="summary">Gets the name of the Pool</td>
</tr> </tr>
@ -90,6 +87,13 @@
<td class="summary">Callback for when an Entity is added to the Pool.</td> <td class="summary">Callback for when an Entity is added to the Pool.</td>
</tr> </tr>
</table> </table>
<h2><a href="#Metamethods">Metamethods</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#Pool:__eligible">Pool:__eligible (e)</a></td>
<td class="summary">Checks if an Entity is eligible for the Pool.</td>
</tr>
</table>
<br/> <br/>
<br/> <br/>
@ -128,59 +132,6 @@
</dd>
<dt>
<a name = "Pool:eligible"></a>
<strong>Pool:eligible (e)</strong>
</dt>
<dd>
Checks if an Entity is eligible for the Pool.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">e</span>
<span class="types"><a class="type" href="../classes/Entity.html#">Entity</a></span>
Entity to check
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">boolean</span></span>
</ol>
</dd>
<dt>
<a name = "Pool:evaluate"></a>
<strong>Pool:evaluate (e)</strong>
</dt>
<dd>
Evaluate whether an Entity should be added or removed from the Pool.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">e</span>
Entity to add or remove
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/Pool.html#">Pool</a></span>
self
</ol>
</dd> </dd>
<dt> <dt>
<a name = "Pool:getName"></a> <a name = "Pool:getName"></a>
@ -242,6 +193,37 @@
</dd>
</dl>
<h2 class="section-header "><a name="Metamethods"></a>Metamethods</h2>
<dl class="function">
<dt>
<a name = "Pool:__eligible"></a>
<strong>Pool:__eligible (e)</strong>
</dt>
<dd>
Checks if an Entity is eligible for the Pool.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">e</span>
<span class="types"><a class="type" href="../classes/Entity.html#">Entity</a></span>
Entity to check
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">boolean</span></span>
</ol>
</dd> </dd>
</dl> </dl>
@ -250,7 +232,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -39,6 +39,7 @@
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -48,10 +49,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -62,16 +66,29 @@
<p>Iterates over Entities.</p> <p>Iterates over Entities.</p>
<p> From these Entities its get Components and modify them. <p> From these Entities its get Components and modify them.
A System contains 1 or more Pools. A System contains 1 or more Pools.
A System is contained by 1 World.</p> A System is contained by 1 World.
</p>
<h2><a href="#Methods">Methods</a></h2> <h2><a href="#Methods">Methods</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" nowrap><a href="#System:new">System:new (table)</a></td> <td class="name" nowrap><a href="#System:new">System:new (...)</a></td>
<td class="summary">Creates a new SystemClass.</td> <td class="summary">Creates a new SystemClass.</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#System:enable">System:enable ()</a></td>
<td class="summary">Enables the System.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#System:disable">System:disable ()</a></td>
<td class="summary">Disables the System.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#System:toggleEnabled">System:toggleEnabled ()</a></td>
<td class="summary">Toggles if the System is enabled.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#System:setEnabled">System:setEnabled (enable)</a></td> <td class="name" nowrap><a href="#System:setEnabled">System:setEnabled (enable)</a></td>
<td class="summary">Sets if the System is enabled</td> <td class="summary">Sets if the System is enabled</td>
</tr> </tr>
@ -117,7 +134,7 @@
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "System:new"></a> <a name = "System:new"></a>
<strong>System:new (table)</strong> <strong>System:new (...)</strong>
</dt> </dt>
<dd> <dd>
Creates a new SystemClass. Creates a new SystemClass.
@ -125,8 +142,8 @@
<h3>Parameters:</h3> <h3>Parameters:</h3>
<ul> <ul>
<li><span class="parameter">table</span> <li><span class="parameter">...</span>
filters A table containing filters (name = {components...}) Variable amounts of filters
</li> </li>
</ul> </ul>
@ -140,6 +157,66 @@
</dd>
<dt>
<a name = "System:enable"></a>
<strong>System:enable ()</strong>
</dt>
<dd>
Enables the System.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/System.html#">System</a></span>
self
</ol>
</dd>
<dt>
<a name = "System:disable"></a>
<strong>System:disable ()</strong>
</dt>
<dd>
Disables the System.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/System.html#">System</a></span>
self
</ol>
</dd>
<dt>
<a name = "System:toggleEnabled"></a>
<strong>System:toggleEnabled ()</strong>
</dt>
<dd>
Toggles if the System is enabled.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../classes/System.html#">System</a></span>
self
</ol>
</dd> </dd>
<dt> <dt>
<a name = "System:setEnabled"></a> <a name = "System:setEnabled"></a>
@ -308,7 +385,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -38,6 +38,7 @@
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -47,10 +48,13 @@
</ul> </ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
</div> </div>
@ -62,7 +66,8 @@
<p> <p>
A world emits to let Systems iterate. A world emits to let Systems iterate.
A World contains any amount of Systems. A World contains any amount of Systems.
A World contains any amount of Entities.</p> A World contains any amount of Entities.
</p>
<h2><a href="#Methods">Methods</a></h2> <h2><a href="#Methods">Methods</a></h2>
@ -461,7 +466,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -31,13 +31,17 @@
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="modules/Assemblages.html">Assemblages</a></li>
<li><a href="modules/Components.html">Components</a></li> <li><a href="modules/Components.html">Components</a></li>
<li><a href="modules/Concord.html">Concord</a></li> <li><a href="modules/Concord.html">Concord</a></li>
<li><a href="modules/Systems.html">Systems</a></li>
<li><a href="modules/type.html">type</a></li> <li><a href="modules/type.html">type</a></li>
<li><a href="modules/utils.html">utils</a></li> <li><a href="modules/utils.html">utils</a></li>
<li><a href="modules/worlds.html">worlds</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="classes/Assemblage.html">Assemblage</a></li>
<li><a href="classes/Component.html">Component</a></li> <li><a href="classes/Component.html">Component</a></li>
<li><a href="classes/Entity.html">Entity</a></li> <li><a href="classes/Entity.html">Entity</a></li>
<li><a href="classes/List.html">List</a></li> <li><a href="classes/List.html">List</a></li>
@ -55,6 +59,10 @@
<h2>Modules</h2> <h2>Modules</h2>
<table class="module_list"> <table class="module_list">
<tr>
<td class="name" nowrap><a href="modules/Assemblages.html">Assemblages</a></td>
<td class="summary">A container for registered <a href="classes/Assemblage.html#">Assemblage</a>s</td>
</tr>
<tr> <tr>
<td class="name" nowrap><a href="modules/Components.html">Components</a></td> <td class="name" nowrap><a href="modules/Components.html">Components</a></td>
<td class="summary">Container for registered ComponentClasses</td> <td class="summary">Container for registered ComponentClasses</td>
@ -63,6 +71,10 @@
<td class="name" nowrap><a href="modules/Concord.html">Concord</a></td> <td class="name" nowrap><a href="modules/Concord.html">Concord</a></td>
<td class="summary"></td> <td class="summary"></td>
</tr> </tr>
<tr>
<td class="name" nowrap><a href="modules/Systems.html">Systems</a></td>
<td class="summary">Container for registered SystemClasses</td>
</tr>
<tr> <tr>
<td class="name" nowrap><a href="modules/type.html">type</a></td> <td class="name" nowrap><a href="modules/type.html">type</a></td>
<td class="summary">Type <td class="summary">Type
@ -73,9 +85,18 @@
<td class="summary">Utils <td class="summary">Utils
Helper module for misc operations</td> Helper module for misc operations</td>
</tr> </tr>
<tr>
<td class="name" nowrap><a href="modules/worlds.html">worlds</a></td>
<td class="summary">Worlds
Container for registered Worlds</td>
</tr>
</table> </table>
<h2>Classes</h2> <h2>Classes</h2>
<table class="module_list"> <table class="module_list">
<tr>
<td class="name" nowrap><a href="classes/Assemblage.html">Assemblage</a></td>
<td class="summary">Gives an entity a set of components.</td>
</tr>
<tr> <tr>
<td class="name" nowrap><a href="classes/Component.html">Component</a></td> <td class="name" nowrap><a href="classes/Component.html">Component</a></td>
<td class="summary">A pure data container that is contained by a single entity.</td> <td class="summary">A pure data container that is contained by a single entity.</td>
@ -107,7 +128,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -30,17 +30,25 @@
<li><a href="../index.html">Index</a></li> <li><a href="../index.html">Index</a></li>
</ul> </ul>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
</ul>
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><strong>Concord</strong></li> <li><strong>Concord</strong></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -58,18 +66,129 @@
<p></p> <p></p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#loadComponents">loadComponents (pathOrFiles)</a></td>
<td class="summary">Loads ComponentClasses and puts them in the Components container.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#loadSystems">loadSystems (pathOrFiles)</a></td>
<td class="summary">Loads SystemClasses and puts them in the Systems container.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#loadWorlds">loadWorlds (pathOrFiles)</a></td>
<td class="summary">Loads Worlds and puts them in the Worlds container.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#loadAssemblages">loadAssemblages (pathOrFiles)</a></td>
<td class="summary">Loads Assemblages and puts them in the Assemblages container.</td>
</tr>
</table>
<br/> <br/>
<br/> <br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "loadComponents"></a>
<strong>loadComponents (pathOrFiles)</strong>
</dt>
<dd>
Loads ComponentClasses and puts them in the Components container.
Accepts a table of paths to files: {"component_1", "component_2", "etc"}
Accepts a path to a directory with ComponentClasses: "components"
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">pathOrFiles</span>
</li>
</ul>
</dd>
<dt>
<a name = "loadSystems"></a>
<strong>loadSystems (pathOrFiles)</strong>
</dt>
<dd>
Loads SystemClasses and puts them in the Systems container.
Accepts a table of paths to files: {"system_1", "system_2", "etc"}
Accepts a path to a directory with SystemClasses: "systems"
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">pathOrFiles</span>
</li>
</ul>
</dd>
<dt>
<a name = "loadWorlds"></a>
<strong>loadWorlds (pathOrFiles)</strong>
</dt>
<dd>
Loads Worlds and puts them in the Worlds container.
Accepts a table of paths to files: {"world_1", "world_2", "etc"}
Accepts a path to a directory with Worlds: "worlds"
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">pathOrFiles</span>
</li>
</ul>
</dd>
<dt>
<a name = "loadAssemblages"></a>
<strong>loadAssemblages (pathOrFiles)</strong>
</dt>
<dd>
Loads Assemblages and puts them in the Assemblages container.
Accepts a table of paths to files: {"assemblage_1", "assemblage_2", "etc"}
Accepts a path to a directory with Assemblages: "assemblages"
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">pathOrFiles</span>
</li>
</ul>
</dd>
</dl>
</div> <!-- id="content" --> </div> <!-- id="content" -->
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -38,13 +38,17 @@
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><strong>Components</strong></li> <li><strong>Components</strong></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -65,13 +69,12 @@
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" nowrap><a href="#has">has (name)</a></td> <td class="name" nowrap><a href="#register">register (name, componentClass)</a></td>
<td class="summary">Returns true if the containter has the ComponentClass with the specified name</td> <td class="summary">Registers a ComponentClass.</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#try">try (name)</a></td> <td class="name" nowrap><a href="#has">has (name)</a></td>
<td class="summary">Returns true and the ComponentClass if one was registered with the specified name <td class="summary">Returns true if the containter has the ComponentClass with the specified name</td>
or false and an error otherwise</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#get">get (name)</a></td> <td class="name" nowrap><a href="#get">get (name)</a></td>
@ -86,6 +89,31 @@
<h2 class="section-header "><a name="Functions"></a>Functions</h2> <h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function"> <dl class="function">
<dt>
<a name = "register"></a>
<strong>register (name, componentClass)</strong>
</dt>
<dd>
Registers a ComponentClass.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">name</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
Name to register under
</li>
<li><span class="parameter">componentClass</span>
<span class="types"><a class="type" href="../classes/Component.html#">Component</a></span>
ComponentClass to register
</li>
</ul>
</dd>
<dt> <dt>
<a name = "has"></a> <a name = "has"></a>
<strong>has (name)</strong> <strong>has (name)</strong>
@ -112,37 +140,6 @@
</dd>
<dt>
<a name = "try"></a>
<strong>try (name)</strong>
</dt>
<dd>
Returns true and the ComponentClass if one was registered with the specified name
or false and an error otherwise
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">name</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
Name of the ComponentClass to check
</li>
</ul>
<h3>Returns:</h3>
<ol>
<li>
<span class="types"><span class="type">boolean</span></span>
</li>
<li>
<span class="types"><a class="type" href="../classes/Component.html#">Component</a></span>
or error string</li>
</ol>
</dd> </dd>
<dt> <dt>
<a name = "get"></a> <a name = "get"></a>
@ -178,7 +175,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -38,13 +38,17 @@
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><strong>type</strong></li> <li><strong>type</strong></li>
<li><a href="../modules/utils.html">utils</a></li> <li><a href="../modules/utils.html">utils</a></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -89,6 +93,10 @@
<td class="name" nowrap><a href="#Type.isWorld">Type.isWorld (t)</a></td> <td class="name" nowrap><a href="#Type.isWorld">Type.isWorld (t)</a></td>
<td class="summary">Returns if object is a World.</td> <td class="summary">Returns if object is a World.</td>
</tr> </tr>
<tr>
<td class="name" nowrap><a href="#Type.isAssemblage">Type.isAssemblage (t)</a></td>
<td class="summary">Returns if object is an Assemblage.</td>
</tr>
</table> </table>
<br/> <br/>
@ -253,6 +261,32 @@
</dd>
<dt>
<a name = "Type.isAssemblage"></a>
<strong>Type.isAssemblage (t)</strong>
</dt>
<dd>
Returns if object is an Assemblage.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">t</span>
Object to check
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">boolean</span></span>
</ol>
</dd> </dd>
</dl> </dl>
@ -261,7 +295,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -38,13 +38,17 @@
<h2>Modules</h2> <h2>Modules</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../modules/Assemblages.html">Assemblages</a></li>
<li><a href="../modules/Components.html">Components</a></li> <li><a href="../modules/Components.html">Components</a></li>
<li><a href="../modules/Concord.html">Concord</a></li> <li><a href="../modules/Concord.html">Concord</a></li>
<li><a href="../modules/Systems.html">Systems</a></li>
<li><a href="../modules/type.html">type</a></li> <li><a href="../modules/type.html">type</a></li>
<li><strong>utils</strong></li> <li><strong>utils</strong></li>
<li><a href="../modules/worlds.html">worlds</a></li>
</ul> </ul>
<h2>Classes</h2> <h2>Classes</h2>
<ul class="nowrap"> <ul class="nowrap">
<li><a href="../classes/Assemblage.html">Assemblage</a></li>
<li><a href="../classes/Component.html">Component</a></li> <li><a href="../classes/Component.html">Component</a></li>
<li><a href="../classes/Entity.html">Entity</a></li> <li><a href="../classes/Entity.html">Entity</a></li>
<li><a href="../classes/List.html">List</a></li> <li><a href="../classes/List.html">List</a></li>
@ -69,10 +73,6 @@
<td class="name" nowrap><a href="#Utils.shallowCopy">Utils.shallowCopy (orig, target)</a></td> <td class="name" nowrap><a href="#Utils.shallowCopy">Utils.shallowCopy (orig, target)</a></td>
<td class="summary">Does a shallow copy of a table and appends it to a target table.</td> <td class="summary">Does a shallow copy of a table and appends it to a target table.</td>
</tr> </tr>
<tr>
<td class="name" nowrap><a href="#Utils.loadNamespace">Utils.loadNamespace (pathOrFiles, namespace)</a></td>
<td class="summary">Requires files and puts them in a table.</td>
</tr>
</table> </table>
<br/> <br/>
@ -104,37 +104,6 @@
</dd>
<dt>
<a name = "Utils.loadNamespace"></a>
<strong>Utils.loadNamespace (pathOrFiles, namespace)</strong>
</dt>
<dd>
Requires files and puts them in a table.
Accepts a table of paths to Lua files: {"path/to/file_1", "path/to/another/file_2", "etc"}
Accepts a path to a directory with Lua files: "my_files/here"
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">pathOrFiles</span>
The table of paths or a path to a directory.
</li>
<li><span class="parameter">namespace</span>
A table that will hold the required files
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
The namespace table
</ol>
</dd> </dd>
</dl> </dl>
@ -143,7 +112,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2020-08-18 15:20:32 </i> <i style="float:right;">Last updated 2020-01-04 10:27:07 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -0,0 +1,65 @@
local Concord = require("src")
local Entity = Concord.entity
local Component = Concord.component
local System = Concord.system
local Assemblage = Concord.assemblage
local Game = Concord.context()
local Legs = Component(function(e, legCount)
e.legCount = legCount or 0
end)
local Instinct = Component(function(e) -- luacheck: ignore
end)
local Cool = Component(function(e, coolness)
e.coolness = coolness
end)
local Wings = Component(function(e)
e.wingCount = 2
end)
local Animal = Assemblage(function(e, legCount)
e
:give(Legs, legCount)
:give(Instinct)
print("Animal")
end)
local Lion = Assemblage(function(e, coolness)
e
:assemble(Animal, 4)
:give(Cool, coolness)
print("Lion")
end)
local Eagle = Assemblage(function(e)
e
:assemble(Animal, 2)
:give(Wings)
print("Eagle")
end)
local Griffin = Assemblage(function(e, coolness)
e
:assemble(Animal, 4)
:assemble(Lion, coolness * 2)
:assemble(Eagle)
end)
local myAnimal = Entity()
:assemble(Griffin, 5)
--:apply()
print(myAnimal:has(Legs))
print(myAnimal:has(Instinct))
print(myAnimal:has(Cool))
print(myAnimal:has(Wings))

View file

@ -0,0 +1,6 @@
local PATH = (...):gsub('%.[^%.]+$', '')
local Concord = require("src")
local C = require(PATH..".src.components")
local S = require(PATH..".src.systems")

View file

@ -1,3 +1,5 @@
local PATH = (...):gsub('%.init$', '') local PATH = (...):gsub('%.init$', '')
return require(PATH..".concord") return {
}

View file

@ -0,0 +1,5 @@
local PATH = (...):gsub('%.init$', '')
return {
}

View file

@ -0,0 +1,69 @@
local Concord = require("concord")
local function display(t)
print("Table: " ..tostring(t))
for key, value in pairs(t) do
if type(value) == "table" then
display(value)
else
print(key, value)
end
end
end
local test_component_1 = Concord.component(function(e, x, y)
e.x = x or 0
e.y = y or 0
end)
Concord.components.register("test_component_1", test_component_1)
function test_component_1:serialize()
return {
x = self.x,
y = self.y,
}
end
function test_component_1:deserialize(data)
self.x = data.x or 0
self.y = data.y or 0
end
local test_component_2 = Concord.component(function(e, foo)
e.foo = foo
end)
Concord.components.register("test_component_2", test_component_2)
function test_component_2:serialize()
return {
foo = self.foo
}
end
function test_component_2:deserialize(data)
self.foo = data.foo
end
-- Test worlds
local world_1 = Concord.world()
local world_2 = Concord.world()
-- Test Entity
Concord.entity(world_1)
:give(test_component_1, 100, 50)
:give(test_component_2, "Hello World!")
-- Serialize world
local data = world_1:serialize()
-- Deserialize world
world_2:deserialize(data)
-- Check result
local test_entity_copy = world_2:getEntities()[1]
local test_comp_1 = test_entity_copy[test_component_1]
local test_comp_2 = test_entity_copy[test_component_2]
print(test_comp_1.x, test_comp_1.y)
print(test_comp_2.foo)

View file

@ -0,0 +1,125 @@
local Concord = require("src")
local Entity = Concord.entity
local Component = Concord.component
local System = Concord.system
local Game = Concord.world()
local Position = Component(function(e, x, y)
e.x = x
e.y = y
end)
local Rectangle = Component(function(e, w, h)
e.w = w
e.h = h
end)
local Circle = Component(function(e, r)
e.r = r
end)
local Color = Component(function(e, r, g, b, a)
e.r = r
e.g = g
e.b = b
e.a = a
end)
local RectangleRenderer = System({Position, Rectangle})
function RectangleRenderer:draw()
for _, e in ipairs(self.pool) do
local position = e:get(Position)
local rectangle = e:get(Rectangle)
local color = e:get(Color)
love.graphics.setColor(255, 255, 255)
if color then
love.graphics.setColor(color.r, color.g, color.b, color.a)
end
love.graphics.rectangle("fill", position.x, position.y, rectangle.w, rectangle.h)
end
end
local CircleRenderer = System({Position, Circle})
function CircleRenderer:flush()
for _, e in ipairs(self.pool.removed) do
print(tostring(e).. " was removed from my pool D:")
end
end
function CircleRenderer:draw()
for _, e in ipairs(self.pool) do
local position = e:get(Position)
local circle = e:get(Circle)
local color = e:get(Color)
love.graphics.setColor(255, 255, 255)
if color then
love.graphics.setColor(color.r, color.g, color.b, color.a)
end
love.graphics.circle("fill", position.x, position.y, circle.r)
end
end
local RandomRemover = System({})
function RandomRemover:init()
self.time = 0
end
function RandomRemover:update(dt)
self.time = self.time + dt
if self.time >= 0.05 then
self.time = 0
if self.pool.size > 0 then
local i = love.math.random(1, self.pool.size)
self.pool:get(i):destroy()
end
end
love.window.setTitle(love.timer.getFPS())
end
Game:addSystem(RandomRemover(), "update")
Game:addSystem(RectangleRenderer(), "draw")
Game:addSystem(CircleRenderer(), "draw")
for _ = 1, 100 do
local e = Entity()
e:give(Position, love.math.random(0, 700), love.math.random(0, 700))
e:give(Rectangle, love.math.random(5, 20), love.math.random(5, 20))
if love.math.random(0, 1) == 0 then
e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1)
end
Game:addEntity(e)
end
for _ = 1, 100 do
local e = Entity()
e:give(Position, love.math.random(0, 700), love.math.random(0, 700))
e:give(Circle, love.math.random(5, 20))
if love.math.random(0, 1) == 0 then
e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1)
end
Game:addEntity(e)
end
function love.update(dt)
Game:emit("update", dt)
end
function love.draw()
Game:emit("draw")
end

1
main.lua Normal file
View file

@ -0,0 +1 @@
require("examples.serialization")