Merge pull request #29 from Tjakka5/dev

Dev
This commit is contained in:
Justin van der Leij 2020-08-18 11:27:37 +02:00 committed by GitHub
commit 0897b1e4f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 384 additions and 893 deletions

229
README.md
View file

@ -19,17 +19,16 @@ Auto generated docs for Concord can be found in `docs` folder, or on the [Github
- [Entities](#entities)
- [Systems](#systems)
- [Worlds](#worlds)
- [Assemblages](#assemblages)
- [Assemblages](#assemblages)
[Quick Example](#quick-example)
[Contributors](#contributors)
[License](#licence)
---
## Installation
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:
Download the repository and copy the 'concord' folder into your project. Then require it in your project like so:
```lua
local Concord = require("path.to.concord")
```
@ -42,13 +41,9 @@ local Entity = Concord.entity
local Component = Concord.component
local System = Concord.system
local World = Concord.world
local Assemblage = Concord.assemblage
-- Containers
local Components = Concord.components
local Systems = Concord.systems
local Worlds = Concord.worlds
local Assemblages = Concord.assemblages
```
---
@ -71,7 +66,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.
Every Entity has it's own set of Components, with their own values.
A crate might have the following components:
A crate might have the following components (Note: Not actual Concord syntax):
```lua
{
position = { x = 100, y = 200 },
@ -100,12 +95,12 @@ For this they will only act on Entities that have the Components needed for this
In Concord this is done something alike this:
```lua
drawSystem = System({position, texture}) -- Define a System that takes all Entities with a position and texture Component
drawSystem = System({pool = {position, texture}}) -- Define a System that takes all Entities with a position and texture Component
function drawSystem:draw() -- Give it a draw function
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 texture = entity[texture] -- Get the texture 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
-- Draw the Entity
love.graphics.draw(texture.image, position.x, position.y)
@ -133,58 +128,35 @@ 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.
#### Classes
#### Requiring files
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.
Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in.
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
local Components = require("path.to.concord").components
local Systems = require("path.to.concord").systems
local Worlds = require("path.to.concord").worlds
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)
print(Systems.systemName)
-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary.
Concord.utils.loadNamespace("path/to/components")
print(Concord.components.componentName)
```
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
```lua
-- All functions that do something ( eg. Don't return anything ) will return self
-- Most (if not all) methods will return self
-- This allowes you to chain methods
entity
:give(position, 100, 50)
:give(velocity, 200, 0)
:remove(position)
myEntity
:give("position", 100, 50)
:give("velocity", 200, 0)
:remove("position")
:destroy()
--
world
myWorld
:addEntity(fooEntity)
:addEntity(barEntity)
:clear()
@ -192,13 +164,13 @@ world
```
### Components
When defining a ComponentClass you usually pass in a `populate` function. This will fill the Component with values.
When defining a ComponentClass you need to pass in a name and usually a `populate` function. This will fill the Component with values.
```lua
-- Create the ComponentClass with a populate function
-- Create the position class with a populate function
-- 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
local positionComponentClass = Concord.component(function(component, x, y)
Concord.component("position" function(component, x, y)
component.x = x or 0
component.y = y or 0
end)
@ -206,19 +178,11 @@ end)
-- Create a ComponentClass without a populate function
-- Components of this type won't have any fields.
-- This can be useful to indiciate state.
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
local pushableComponentClass = Concord.component("position")
```
### Entities
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 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 only have a maximum of one of each Component.
Entities can not share Components.
@ -233,43 +197,35 @@ local myEntity = Entity(myWorld) -- To add it to a world immediately ( See World
```lua
-- Give the entity the position Component defined above
-- x will become 100. y will become 50
myEntity:give(positionComponentClass, 100, 50)
myEntity:give("position", 100, 50)
```
```lua
-- Retrieve a Component
local positionComponent = myEntity[positionComponentClass]
-- or
local positionComponent = myEntity:get(positionComponentClass)
local position = myEntity.position
print(positionComponent.x, positionComponent.y) -- 100, 50
print(position.x, position.y) -- 100, 50
```
```lua
-- Remove a Component
myEntity:remove(positionComponentClass)
```
```lua
-- Check if the Entity has a Component
local hasPositionComponent = myEntity:has(positionComponentClass)
print(hasPositionComponent) -- false
myEntity:remove("position")
```
```lua
-- 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(positionComponentClass, 0, 0) -- Will give
Entity:ensure("position", 0, 0) -- Will give
-- Position is {x = 0, y = 0}
Entity:give(positionComponentClass, 50, 50) -- Will override
Entity:give("position", 50, 50) -- Will override
-- Position is {x = 50, y = 50}
Entity:give(positionComponentClass, 100, 100) -- Will override
Entity:give("position", 100, 100) -- Will override
-- Position is {x = 100, y = 100}
Entity:ensure(positionComponentClass, 0, 0) -- Wont do anything
Entity:ensure("position", 0, 0) -- Wont do anything
-- Position is {x = 100, y = 100}
```
@ -285,7 +241,7 @@ end
```lua
-- Assemble the Entity ( See Assemblages )
myEntity:assemble(myAssemblage, 100, true, "foo")
myEntity:assemble(assemblageFunction, 100, true, "foo")
```
```lua
@ -310,30 +266,21 @@ Systems can have multiple pools.
```lua
-- Create a System
local mySystemClass = Concord.system({positionComponentClass}) -- Pool will contain all Entities with a position Component
local mySystemClass = Concord.system(pool = {"position"}) -- Pool named 'pool' will contain all Entities with a position Component
-- Create a System with multiple pools
local mySystemClass = Concord.system(
{ -- This Pool's name will default to 'pool'
positionCompomponentClass,
velocityComponentClass,
pool = { -- This pool will be named 'pool'
"position",
"velocity",
},
{ -- This Pool's name will be 'secondPool'
healthComponentClass,
damageableComponentClass,
"secondPool",
secondPool = { -- This pool's name will be 'secondPool'
"health",
"damageable",
}
)
```
```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
-- If a System has a :init function it will be called on creation
@ -348,16 +295,11 @@ end
function mySystemClass:update(dt)
-- Iterate over all entities in the Pool
for _, e in ipairs(self.pool)
-- Get the Entity's Components
local positionComponent = e[positionComponentClass]
local velocityComponent = e[velocityComponentClass]
-- Do something with the Components
positionComponent.x = positionComponent.x + velocityComponent.x * dt
positionComponent.y = positionComponent.y + velocityComponent.y * dt
e.position.x = e.position.x + e.velocity.x * dt
e.position.y = e.position.y + e.velocity.y * dt
end
-- Iterate over all entities in the second Pool
for _, e in ipairs(self.secondPool)
-- Do something
@ -367,24 +309,20 @@ end
```lua
-- 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
-- Enable a System
mySystem:enable()
-- or
mySystem:setEnable(true)
-- Disable a System
mySystem:disable()
-- or
mySystem:setEnable(false)
-- Toggle the enable state
mySystem:toggleEnabled()
-- Get enabled state
local isEnabled = mySystem:isEnabled()
print(isEnabled) -- true
print(isEnabled) -- false
```
```lua
@ -405,14 +343,6 @@ Worlds can have any number of Entities.
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
-- Add an Entity to the World
myWorld:addEntity(myEntity)
@ -471,33 +401,31 @@ end
### Assemblages
Assemblages are helpers to 'make' Entities something.
Assemblages are functions to 'make' Entities something.
An important distinction is that they _append_ Components.
```lua
-- Make an Assemblage
-- Make an Assemblage function
-- e is the Entity being assembled.
-- cuteness and legs are variables passed in
local animalAssemblage(function(e, cuteness, legs)
function animal(e, cuteness, legs)
e
:give(cutenessComponentClass, cuteness)
:give(limbs, legs, 0) -- Variable amount of legs. 0 arm.
end)
-- Make an Assemblage that used animalAssemblage
-- Make an Assemblage that uses animal
-- cuteness is a variables passed in
local catAssemblage(function(e, cuteness)
function cat(e, cuteness)
e
:assemble(animalAssemblage, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs.
:assemble(animal, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs.
:give(soundComponent, "meow.mp3")
end)
```
```lua
-- Use an Assemblage
myEntity:assemble(catAssemblage, 100) -- 100 cuteness
-- or
catAssemblage:assemble(myEntity, 100) -- 100 cuteness
myEntity:assemble(cat, 100) -- 100 cuteness
```
---
@ -506,14 +434,13 @@ catAssemblage:assemble(myEntity, 100) -- 100 cuteness
```lua
local Concord = require("concord")
-- Defining ComponentClasses
-- I use UpperCamelCase to indicate its a class
local Position = Concord.component(function(c, x, y)
-- Defining components
Concord.component("position", function(c, x, y)
c.x = x or 0
c.y = y or 0
end)
local Velocity = Concord.component(function(c, x, y)
Concord.component("velocity", function(c, x, y)
c.x = x or 0
c.y = y or 0
end)
@ -522,27 +449,25 @@ local Drawable = Concord.component()
-- Defining Systems
local MoveSystem = Concord.system({Position, Velocity})
local MoveSystem = Concord.system({
pool = {"position", "velocity"}
})
function MoveSystem:update(dt)
for _, e in ipairs(self.pool) do
-- I use lowerCamelCase to indicate its an instance
local position = e[Position]
local velocity = e[Velocity]
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
e.position.x = e.position.x + e.velocity.x * dt
e.position.y = e.position.y + e.velocity.y * dt
end
end
local DrawSystem = Concord.system({Position, Drawable})
local DrawSystem = Concord.system({
pool = {"position", "drawable"}
})
function DrawSystem:draw()
for _, e in ipairs(self.pool) do
local position = e[Position]
love.graphics.circle("fill", position.x, position.y, 5)
love.graphics.circle("fill", e.position.x, e.position.y, 5)
end
end
@ -555,18 +480,18 @@ world:addSystems(MoveSystem, DrawSystem)
-- This Entity will be rendered on the screen, and move to the right at 100 pixels a second
local entity_1 = Concord.entity(world)
:give(Position, 100, 100)
:give(Velocity, 100, 0)
:give(Drawable)
:give("position", 100, 100)
:give("velocity", 100, 0)
:give("drawable")
-- This Entity will be rendered on the screen, and stay at 50, 50
local entity_2 = Concord.entity(world)
:give(Position, 50, 50)
:give(Drawable)
:give("position", 50, 50)
:give("drawable")
-- 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)
:give(Position, 200, 200)
:give("position", 200, 200)
-- Emit the events
@ -591,5 +516,5 @@ end
---
## Licence
## License
MIT Licensed - Copyright Justin van der Leij (Tjakka5)

View file

@ -1,49 +0,0 @@
--- 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,
})

View file

@ -1,49 +0,0 @@
--- 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 +1,11 @@
--- A pure data container that is contained by a single entity.
-- @classmod Component
local PATH = (...):gsub('%.[^%.]+$', '')
local Components = require(PATH..".components")
local Utils = require(PATH..".utils")
local Component = {}
Component.__mt = {
__index = Component,
@ -9,7 +14,15 @@ Component.__mt = {
--- Creates a new ComponentClass.
-- @tparam function populate Function that populates a Component with values
-- @treturn Component A new ComponentClass
function Component.new(populate)
function Component.new(name, populate)
if (type(name) ~= "string") then
error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2)
end
if (rawget(Components, name)) then
error("bad argument #1 to 'Component.new' (ComponentClass with name '"..name.."' was already registerd)", 2) -- luacheck: ignore
end
if (type(populate) ~= "function" and type(populate) ~= "nil") then
error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
end
@ -17,7 +30,7 @@ function Component.new(populate)
local componentClass = setmetatable({
__populate = populate,
__name = nil,
__name = name,
__isComponentClass = true,
}, Component.__mt)
@ -25,6 +38,8 @@ function Component.new(populate)
__index = componentClass
}
Components[name] = componentClass
return componentClass
end
@ -32,10 +47,19 @@ end
function Component:__populate() -- luacheck: ignore
end
function Component:serialize() -- luacheck: ignore
function Component:serialize()
local data = Utils.shallowCopy(self, {})
--This values shouldn't be copied over
data.__componentClass = nil
data.__isComponent = nil
data.__isComponentClass = nil
return data
end
function Component:deserialize(data) -- luacheck: ignore
function Component:deserialize(data)
Utils.shallowCopy(data, self)
end
-- Internal: Creates a new Component.

View file

@ -7,42 +7,47 @@ local Type = require(PATH..".type")
local Components = {}
--- Registers a ComponentClass.
-- @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
-- @string name Name of the ComponentClass to check
-- @treturn boolean
function Components.has(name)
return Components[name] and true or false
return rawget(Components, name) and true or false
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
-- @treturn boolean
-- @treturn Component or error string
function Components.try(name)
if type(name) ~= "string" then
return false, "ComponentsClass name is expected to be a string, got "..type(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
end
--- Returns the ComponentClass with the specified name
-- @string name Name of the ComponentClass to get
-- @treturn Component
function Components.get(name)
return Components[name]
local ok, value = Components.try(name)
if not ok then error(value, 2) end
return value
end
return setmetatable(Components, {
__index = function(_, name)
error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2)
end
__index = function(_, name)
local ok, value = Components.try(name)
if not ok then error(value, 2) end
return value end
})

View file

@ -34,18 +34,18 @@ function Entity.new(world)
return e
end
local function give(e, componentClass, ...)
local function give(e, name, componentClass, ...)
local component = componentClass:__initialize(...)
e[componentClass] = component
e.__components[componentClass] = component
e[name] = component
e.__components[name] = component
e:__dirty()
end
local function remove(e, componentClass)
e[componentClass] = nil
e.__components[componentClass] = nil
local function remove(e, name, componentClass)
e[name] = nil
e.__components[name] = nil
e:__dirty()
end
@ -55,12 +55,14 @@ end
-- @tparam Component componentClass ComponentClass to add an instance of
-- @param ... additional arguments to pass to the Component's populate function
-- @treturn Entity self
function Entity:give(componentClass, ...)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2)
function Entity:give(name, ...)
local ok, componentClass = Components.try(name)
if not ok then
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
end
give(self, componentClass, ...)
give(self, name, componentClass, ...)
return self
end
@ -70,16 +72,18 @@ end
-- @tparam Component componentClass ComponentClass to add an instance of
-- @param ... additional arguments to pass to the Component's populate function
-- @treturn Entity self
function Entity:ensure(componentClass, ...)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2)
function Entity:ensure(name, ...)
local ok, componentClass = Components.try(name)
if not ok then
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
end
if self[componentClass] then
if self[name] then
return self
end
give(self, componentClass, ...)
give(self, name, componentClass, ...)
return self
end
@ -87,26 +91,28 @@ end
--- Removes a Component from an Entity.
-- @tparam Component componentClass ComponentClass of the Component to remove
-- @treturn Entity self
function Entity:remove(componentClass)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:remove' (ComponentClass expected, got "..type(componentClass)..")")
function Entity:remove(name)
local ok, componentClass = Components.try(name)
if not ok then
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
end
remove(self, componentClass)
remove(self, name, componentClass)
return self
end
--- Assembles an Entity.
-- @tparam Assemblage assemblage Assemblage to assemble with
-- @param ... additional arguments to pass to the Assemblage's assemble function.
-- @tparam function assemblage Function that will assemble an entity
-- @param ... additional arguments to pass to the assemblage function.
-- @treturn Entity self
function Entity:assemble(assemblage, ...)
if not Type.isAssemblage(assemblage) then
error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")")
if type(assemblage) ~= "function" then
error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")")
end
assemblage:assemble(self, ...)
assemblage(self, ...)
return self
end
@ -135,23 +141,27 @@ end
--- Returns true if the Entity has a Component.
-- @tparam Component componentClass ComponentClass of the Component to check
-- @treturn boolean
function Entity:has(componentClass)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:has' (ComponentClass expected, got "..type(componentClass)..")")
function Entity:has(name)
local ok, componentClass = Components.try(name)
if not ok then
error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2)
end
return self[componentClass] ~= nil
return self[name] and true or false
end
--- Gets a Component from the Entity.
-- @tparam Component componentClass ComponentClass of the Component to get
-- @treturn table
function Entity:get(componentClass)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")")
function Entity:get(name)
local ok, componentClass = Components.try(name)
if not ok then
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
end
return self[componentClass]
return self[name]
end
--- Returns a table of all Components the Entity has.
@ -180,9 +190,11 @@ function Entity:serialize()
for _, component in pairs(self.__components) do
if component.__name then
local componentData = component:serialize()
componentData.__name = component.__name
data[#data + 1] = componentData
if componentData ~= nil then
componentData.__name = component.__name
data[#data + 1] = componentData
end
end
end
@ -193,8 +205,8 @@ function Entity:deserialize(data)
for i = 1, #data do
local componentData = data[i]
if (not Components[componentData.__name]) then
error("bad argument #1 to 'Entity:deserialize' (ComponentClass "..type(componentData.__name).." wasn't yet loaded)") -- luacheck: ignore
if (not Components.has(componentData.__name)) then
error("bad argument #1 to 'Entity:deserialize' (ComponentClass '"..tostring(componentData.__name).."' wasn't yet loaded)") -- luacheck: ignore
end
local componentClass = Components[componentData.__name]
@ -202,8 +214,8 @@ function Entity:deserialize(data)
local component = componentClass:__new()
component:deserialize(componentData)
self[componentClass] = component
self.__components[componentClass] = component
self[componentData.__name] = component
self.__components[componentData.__name] = component
self:__dirty()
end

View file

@ -9,7 +9,7 @@ local Concord = {
_LICENCE = [[
MIT LICENSE
Copyright (c) 2018 Justin van der Leij / Tjakka5
Copyright (c) 2020 Justin van der Leij / Tjakka5
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
@ -33,82 +33,10 @@ local Concord = {
}
Concord.entity = require(PATH..".entity")
Concord.component = require(PATH..".component")
Concord.components = require(PATH..".components")
Concord.system = require(PATH..".system")
Concord.systems = require(PATH..".systems")
Concord.world = require(PATH..".world")
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
Concord.utils = require(PATH..".utils")
return Concord

View file

@ -19,7 +19,7 @@ end
-- Object may not be the string 'size'
-- @param obj Object to add
-- @treturn List self
function List:__add(obj)
function List:add(obj)
local size = self.size + 1
self[size] = obj
@ -32,7 +32,7 @@ end
--- Removes an object from the List.
-- @param obj Object to remove
-- @treturn List self
function List:__remove(obj)
function List:remove(obj)
local index = self[obj]
if not index then return end
local size = self.size
@ -56,7 +56,7 @@ end
--- Clears the List completely.
-- @treturn List self
function List:__clear()
function List:clear()
for i = 1, self.size do
local o = self[i]

View file

@ -29,32 +29,53 @@ 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
function Pool:eligible(e)
for i=#self.__filter, 1, -1 do
local component = self.__filter[i].__name
if not e[component] then return false end
end
return true
end
-- Internal: Adds an Entity to the Pool.
-- Adds an Entity to the Pool, if it can be eligible.
-- @param e Entity to add
-- @treturn Pool self
function Pool:__add(e)
List.__add(self, e)
-- @treturn boolean Whether the entity was added or not
function Pool:add(e, bypass)
if not bypass and not self:eligible(e) then
return self, false
end
List.add(self, e)
self:onEntityAdded(e)
return self, true
end
-- Remove 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
-- Internal: Removed an Entity from the Pool.
-- @param e Entity to remove
--- Evaluate whether an Entity should be added or removed from the Pool.
-- @param e Entity to add or remove
-- @treturn Pool self
function Pool:__remove(e)
List.__remove(self, e)
self:onEntityRemoved(e)
function Pool:evaluate(e)
local has = self:has(e)
local eligible = self:eligible(e)
if not has and eligible then
self:add(e, true) --Bypass the check cause we already checked
elseif has and not eligible then
self:remove(e)
end
return self
end

View file

@ -5,8 +5,9 @@
local PATH = (...):gsub('%.[^%.]+$', '')
local Pool = require(PATH..".pool")
local Utils = require(PATH..".utils")
local Pool = require(PATH..".pool")
local Utils = require(PATH..".utils")
local Components = require(PATH..".components")
local System = {
ENABLE_OPTIMIZATION = true,
@ -25,21 +26,18 @@ System.mt = {
__isSystemClass = false, -- Overwrite value from systemClass
}, systemClass)
-- Optimization: We deep copy the World class into our instance of a world.
-- Optimization: We deep copy the System class into our instance of a system.
-- 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
if (System.ENABLE_OPTIMIZATION) then
Utils.shallowCopy(systemClass, system)
end
for _, filter in pairs(systemClass.__filter) do
local pool = system.__buildPool(filter)
if not system[pool.__name] then
system[pool.__name] = pool
system.__pools[#system.__pools + 1] = pool
else
error("Pool with name '"..pool.name.."' already exists.")
end
for name, filter in pairs(systemClass.__filter) do
local pool = Pool(name, filter)
system[name] = pool
system.__pools[#system.__pools + 1] = pool
end
system:init(world)
@ -48,12 +46,41 @@ System.mt = {
end,
}
local validateFilters = function (baseFilters)
local filters = {}
for name, componentsList in pairs(baseFilters) do
if type(name) ~= 'string' then
error("invalid name for filter (string key expected, got "..type(name)..")", 3)
end
if type(componentsList) ~= 'table' then
error("invalid component list for filter '"..name.."' (table expected, got "..type(componentsList)..")", 3)
end
local filter = {}
for n, component in ipairs(componentsList) do
local ok, componentClass = Components.try(component)
if not ok then
error("invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3)
end
filter[#filter + 1] = componentClass
end
filters[name] = filter
end
return filters
end
--- Creates a new SystemClass.
-- @param ... Variable amounts of filters
-- @param table filters A table containing filters (name = {components...})
-- @treturn System A new SystemClass
function System.new(...)
function System.new(filters)
local systemClass = setmetatable({
__filter = {...},
__filter = validateFilters(filters),
__name = nil,
__isSystemClass = true,
@ -70,37 +97,12 @@ function System.new(...)
return systemClass
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.
-- @param e The Entity to check
-- @treturn System self
function System:__evaluate(e)
for _, pool in ipairs(self.__pools) do
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
pool:evaluate(e)
end
return self
@ -112,7 +114,7 @@ end
function System:__remove(e)
for _, pool in ipairs(self.__pools) do
if pool:has(e) then
pool:__remove(e)
pool:remove(e)
end
end
@ -123,36 +125,12 @@ end
-- @treturn System self
function System:__clear()
for i = 1, #self.__pools do
self.__pools[i]:__clear()
self.__pools[i]:clear()
end
return self
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
-- @tparam boolean enable
-- @treturn System self

View file

@ -1,48 +0,0 @@
--- 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

@ -45,11 +45,4 @@ function Type.isWorld(t)
return type(t) == "table" and t.__isWorld or false
end
--- Returns if object is an Assemblage.
-- @param t Object to check
-- @treturn boolean
function Type.isAssemblage(t)
return type(t) == "table" and t.__isAssemblage or false
end
return Type

View file

@ -7,9 +7,58 @@ local Utils = {}
-- @param orig Table to copy
-- @param target Table to append to
function Utils.shallowCopy(orig, target)
for key, value in pairs(orig) do
target[key] = value
end
for key, value in pairs(orig) do
target[key] = value
end
return target
end
--- 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"
-- @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
error("bad argument #1 to 'loadNamespace' (string/table of strings expected, got "..type(pathOrFiles)..")", 2)
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 'loadNamespace' (path '"..pathOrFiles.."' not found)", 2)
end
local files = love.filesystem.getDirectoryItems(pathOrFiles)
for _, file in ipairs(files) do
local name = file:sub(1, #file - 4)
local path = pathOrFiles.."."..name
local value = require(path)
if namespace then namespace[name] = value end
end
elseif (type(pathOrFiles == "table")) then
for _, path in ipairs(pathOrFiles) do
if (type(path) ~= "string") then
error("bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing "..type(path)..")", 2) -- 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)
if namespace then namespace[name] = value end
end
end
return namespace
end
return Utils

View file

@ -61,7 +61,7 @@ function World:addEntity(e)
end
e.__world = self
self.__added:__add(e)
self.__added:add(e)
return self
end
@ -74,7 +74,7 @@ function World:removeEntity(e)
error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
end
self.__removed:__add(e)
self.__removed:add(e)
return self
end
@ -83,7 +83,7 @@ end
-- @param e Entity to mark as dirty
function World:__dirtyEntity(e)
if not self.__dirty:has(e) then
self.__dirty:__add(e)
self.__dirty:add(e)
end
end
@ -107,40 +107,46 @@ function World:__flush()
for i = 1, self.__backAdded.size do
e = self.__backAdded[i]
self.__entities:__add(e)
if e.__world == self then
self.__entities:add(e)
for j = 1, self.__systems.size do
self.__systems[j]:__evaluate(e)
for j = 1, self.__systems.size do
self.__systems[j]:__evaluate(e)
end
self:onEntityAdded(e)
end
self:onEntityAdded(e)
end
self.__backAdded:__clear()
self.__backAdded:clear()
-- Process removed entities
for i = 1, self.__backRemoved.size do
e = self.__backRemoved[i]
e.__world = nil
self.__entities:__remove(e)
if e.__world == self then
e.__world = nil
self.__entities:remove(e)
for j = 1, self.__systems.size do
self.__systems[j]:__remove(e)
for j = 1, self.__systems.size do
self.__systems[j]:__remove(e)
end
self:onEntityRemoved(e)
end
self:onEntityRemoved(e)
end
self.__backRemoved:__clear()
self.__backRemoved:clear()
-- Process dirty entities
for i = 1, self.__backDirty.size do
e = self.__backDirty[i]
for j = 1, self.__systems.size do
self.__systems[j]:__evaluate(e)
if e.__world == self then
for j = 1, self.__systems.size do
self.__systems[j]:__evaluate(e)
end
end
end
self.__backDirty:__clear()
self.__backDirty:clear()
return self
end
@ -152,37 +158,31 @@ local blacklistedSystemFunctions = {
"onDisabled",
}
--- 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 tryAddSystem = function (world, systemClass)
if (not Type.isSystemClass(systemClass)) then
error("bad argument #1 to 'World:addSystems' (SystemClass expected, got "..type(systemClass)..")", 2)
return false, "SystemClass expected, got "..type(systemClass)
end
if (self.__systemLookup[systemClass]) then
error("bad argument #1 to 'World:addSystems' (SystemClass was already added to World)", 2)
if (world.__systemLookup[systemClass]) then
return false, "SystemClass was already added to World"
end
-- Create instance of system
local system = systemClass(self)
local system = systemClass(world)
self.__systemLookup[systemClass] = system
self.__systems:__add(system)
world.__systemLookup[systemClass] = system
world.__systems:add(system)
for callbackName, callback in pairs(systemClass) do
-- Skip callback if its blacklisted
if (not blacklistedSystemFunctions[callbackName]) then
-- Make container for all listeners of the callback if it does not exist yet
if (not self.__events[callbackName]) then
self.__events[callbackName] = {}
if (not world.__events[callbackName]) then
world.__events[callbackName] = {}
end
-- Add callback to listeners
local listeners = self.__events[callbackName]
local listeners = world.__events[callbackName]
listeners[#listeners + 1] = {
system = system,
callback = callback,
@ -191,8 +191,24 @@ function World:addSystem(systemClass)
end
-- Evaluate all existing entities
for j = 1, self.__entities.size do
system:__evaluate(self.__entities[j])
for j = 1, world.__entities.size do
system:__evaluate(world.__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
error("bad argument #1 to 'World:addSystem' ("..err..")", 2)
end
return self
@ -208,7 +224,10 @@ function World:addSystems(...)
for i = 1, select("#", ...) do
local systemClass = select(i, ...)
self:addSystem(systemClass)
local ok, err = tryAddSystem(self, systemClass)
if not ok then
error("bad argument #"..i.." to 'World:addSystems' ("..err..")", 2)
end
end
return self
@ -278,9 +297,13 @@ function World:clear()
self:removeEntity(self.__entities[i])
end
for i = 1, self.__systems.size do
self.__systems[i]:__clear()
for i = 1, self.__added.size do
local e = self.__added[i]
e.__world = nil
end
self.__added:clear()
self:__flush()
return self
end

View file

@ -1,48 +0,0 @@
--- 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

@ -1,65 +0,0 @@
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

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

View file

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

View file

@ -1,69 +0,0 @@
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

@ -1,125 +0,0 @@
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

View file

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

View file

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