Add serialization and deserialization functions to component, entity, world

This commit is contained in:
Tjakka5 2020-01-04 13:26:26 +01:00
parent c217183cb9
commit 6cd66e6737
6 changed files with 214 additions and 33 deletions

View file

@ -18,6 +18,7 @@ function Component.new(populate)
local componentClass = setmetatable({ local componentClass = setmetatable({
__populate = populate, __populate = populate,
__name = nil,
__isComponentClass = true, __isComponentClass = true,
}, Component.__mt) }, Component.__mt)
@ -32,22 +33,44 @@ end
function Component:__populate() -- luacheck: ignore function Component:__populate() -- luacheck: ignore
end end
function Component:serialize() -- luacheck: ignore
end
function Component:deserialize(data) -- luacheck: ignore
end
--- Internal: Creates a new Component.
-- @return A new Component
function Component:__new()
local component = setmetatable({
__componentClass = self,
__isComponent = true,
__isComponentClass = false,
}, self.__mt)
return component
end
--- Internal: Creates and populates a new Component. --- Internal: Creates and populates a new 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(...) function Component:__initialize(...)
local component = setmetatable({ local component = self:__new()
__componentClass = self,
__isComponent = true,
__isComponentClass = false,
}, self)
self.__populate(component, ...) self.__populate(component, ...)
return component return component
end end
function Component:hasName()
return self.__name and true or false
end
function Component:getName()
return self.__name
end
return setmetatable(Component, { return setmetatable(Component, {
__call = function(_, ...) __call = function(_, ...)
return Component.new(...) return Component.new(...)

View file

@ -20,12 +20,22 @@ function Components.register(name, componentClass)
end end
if (rawget(Components, name)) then if (rawget(Components, name)) then
error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3) error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3) -- luacheck: ignore
end end
Components[name] = componentClass Components[name] = componentClass
componentClass.__name = name
end end
function Components.has(name)
return Components[name] and true or false
end
function Components.get(name)
return Components[name]
end
return setmetatable(Components, { return setmetatable(Components, {
__index = function(_, name) __index = function(_, name)
error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2) error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2)

View file

@ -5,7 +5,8 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type") local Components = require(PATH..".components")
local Type = require(PATH..".type")
local Entity = {} local Entity = {}
Entity.__mt = { Entity.__mt = {
@ -173,6 +174,41 @@ function Entity:getWorld()
return self.__world return self.__world
end end
function Entity:serialize()
local data = {}
for _, component in pairs(self.__components) do
if component.__name then
local componentData = component:serialize()
componentData.__name = component.__name
data[#data + 1] = componentData
end
end
return data
end
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
end
local componentClass = Components[componentData.__name]
local component = componentClass:__new()
component:deserialize(componentData)
self[componentClass] = component
self.__components[componentClass] = component
self:__dirty()
end
end
return setmetatable(Entity, { return setmetatable(Entity, {
__call = function(_, ...) __call = function(_, ...)
return Entity.new(...) return Entity.new(...)

View file

@ -6,9 +6,10 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type") local Entity = require(PATH..".entity")
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,
@ -21,10 +22,10 @@ World.__mt = {
-- @return The new World -- @return The new World
function World.new() function World.new()
local world = setmetatable({ local world = setmetatable({
entities = List(), __entities = List(),
systems = List(), __systems = List(),
events = {}, __events = {},
__added = List(), __backAdded = List(), __added = List(), __backAdded = List(),
__removed = List(), __backRemoved = List(), __removed = List(), __backRemoved = List(),
@ -104,10 +105,10 @@ 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]
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 end
self:onEntityAdded(e) self:onEntityAdded(e)
@ -119,10 +120,10 @@ function World:__flush()
e = self.__backRemoved[i] e = self.__backRemoved[i]
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 end
self:onEntityRemoved(e) self:onEntityRemoved(e)
@ -133,8 +134,8 @@ function World:__flush()
for i = 1, self.__backDirty.size do for i = 1, self.__backDirty.size do
e = self.__backDirty[i] e = self.__backDirty[i]
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
self.__backDirty:__clear() self.__backDirty:__clear()
@ -168,18 +169,18 @@ function World:addSystem(systemClass)
local system = systemClass(self) local system = systemClass(self)
self.__systemLookup[systemClass] = system self.__systemLookup[systemClass] = system
self.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 self.events[callbackName]) then if (not self.__events[callbackName]) then
self.events[callbackName] = {} self.__events[callbackName] = {}
end end
-- Add callback to listeners -- Add callback to listeners
local listeners = self.events[callbackName] local listeners = self.__events[callbackName]
listeners[#listeners + 1] = { listeners[#listeners + 1] = {
system = system, system = system,
callback = callback, callback = callback,
@ -188,8 +189,8 @@ function World:addSystem(systemClass)
end end
-- Evaluate all existing entities -- Evaluate all existing entities
for j = 1, self.entities.size do for j = 1, self.__entities.size do
system:__evaluate(self.entities[j]) system:__evaluate(self.__entities[j])
end end
return self return self
@ -243,7 +244,7 @@ function World:emit(functionName, ...)
error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")") error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")")
end end
local listeners = self.events[functionName] local listeners = self.__events[functionName]
if listeners then if listeners then
for i = 1, #listeners do for i = 1, #listeners do
@ -263,17 +264,58 @@ end
--- Removes all entities from the World --- Removes all entities from the World
-- @return self -- @return self
function World:clear() function World:clear()
for i = 1, self.entities.size do for i = 1, self.__entities.size do
self:removeEntity(self.entities[i]) self:removeEntity(self.__entities[i])
end end
for i = 1, self.systems.size do for i = 1, self.__systems.size do
self.systems[i]:__clear() self.__systems[i]:__clear()
end end
return self return self
end end
function World:getEntities()
return self.__entities
end
function World:getSystems()
return self.__systems
end
function World:serialize()
self:__flush()
local data = {}
for i = 1, self.__entities.size do
local entity = self.__entities[i]
local entityData = entity:serialize()
data[i] = entityData
end
return data
end
function World:deserialize(data, append)
if (not append) then
self:clear()
end
for i = 1, #data do
local entityData = data[i]
local entity = Entity()
entity:deserialize(entityData)
self:addEntity(entity)
end
self:__flush()
end
--- Callback for when an Entity is added to the World. --- Callback for when an Entity is added to the World.
-- @param e The Entity that was added -- @param e The Entity that was added
function World:onEntityAdded(e) -- luacheck: ignore function World:onEntityAdded(e) -- luacheck: ignore

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)

1
main.lua Normal file
View file

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