From a55efd042a6275749ad589a78ee2383865d0d645 Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Tue, 14 Feb 2023 18:14:25 -0300 Subject: [PATCH] Entity's Keys You can now give the 'key' component to Entities. A key will be generated automatically and stored in Entity.key.value. You can then use this key to fetch the Entity from the World with World:getEntityByKey(key) The keys are generated with a generator function that can be overriden. --- concord/builtins/init.lua | 1 + concord/builtins/key.lua | 37 ++++++++++++++ concord/component.lua | 2 + concord/entity.lua | 18 ++++--- concord/system.lua | 2 + concord/world.lua | 103 ++++++++++++++++++++++++++++++++++---- 6 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 concord/builtins/key.lua diff --git a/concord/builtins/init.lua b/concord/builtins/init.lua index d5b749d..e3ab37b 100644 --- a/concord/builtins/init.lua +++ b/concord/builtins/init.lua @@ -2,4 +2,5 @@ local PATH = (...):gsub("(%.init)$", "") return { serializable = require(PATH..".serializable"), + key = require(PATH..".key"), } diff --git a/concord/builtins/key.lua b/concord/builtins/key.lua new file mode 100644 index 0000000..f083bd4 --- /dev/null +++ b/concord/builtins/key.lua @@ -0,0 +1,37 @@ +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.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 \ No newline at end of file diff --git a/concord/component.lua b/concord/component.lua index 351d00c..2b43779 100644 --- a/concord/component.lua +++ b/concord/component.lua @@ -74,6 +74,7 @@ function Component:deserialize(data) end -- Internal: Creates a new Component. +-- @param entity The Entity that will receive this Component. -- @return A new Component function Component:__new(entity) local component = setmetatable({ @@ -88,6 +89,7 @@ function Component:__new(entity) end -- Internal: Creates and populates a new Component. +-- @param entity The Entity that will receive this Component. -- @param ... Varargs passed to the populate function -- @return A new populated Component function Component:__initialize(entity, ...) diff --git a/concord/entity.lua b/concord/entity.lua index ca972e9..120146f 100644 --- a/concord/entity.lua +++ b/concord/entity.lua @@ -9,7 +9,8 @@ local Type = require(PATH..".type") local Utils = require(PATH..".utils") -- Initialize built-in Components (as soon as possible) -local Builtins = require(PATH..".builtins") +local Builtins = require(PATH..".builtins.init") --luacheck: ignore +-- Builtins is unused but the require already registers the Components local Entity = { SERIALIZE_BY_DEFAULT = true, @@ -49,7 +50,7 @@ local function give(e, name, componentClass, ...) local hadComponent = not not e[name] if hadComponent then - e[name]:removed() + e[name]:removed(true) end e[name] = component @@ -61,7 +62,7 @@ end local function remove(e, name) if e[name] then - e[name]:removed() + e[name]:removed(false) e[name] = nil @@ -207,11 +208,16 @@ function Entity:getWorld() return self.__world end -function Entity:serialize() +function Entity:serialize(ignoreKey) local data = {} for name, component in pairs(self) do - if name ~= "__world" and name ~= "__isEntity" and component.__name == name then + -- The key component needs to be treated separately. + if name == "key" and component.__name == "key" then + if not ignoreKey then + data.key = component.value + end + elseif (name ~= "__world") and (name ~= "__isEntity") and (component.__name == name) then local componentData = component:serialize() if componentData ~= nil then @@ -234,7 +240,7 @@ function Entity:deserialize(data) local componentClass = Components[componentData.__name] - local component = componentClass:__new() + local component = componentClass:__new(self) component:deserialize(componentData) self[componentData.__name] = component diff --git a/concord/system.lua b/concord/system.lua index 53731ff..e9d45d6 100644 --- a/concord/system.lua +++ b/concord/system.lua @@ -48,6 +48,8 @@ System.mt = { -- @param table filters A table containing filters (name = {components...}) -- @treturn System A new SystemClass function System.new(definition) + 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)) diff --git a/concord/world.lua b/concord/world.lua index e4aa48d..85a19e8 100644 --- a/concord/world.lua +++ b/concord/world.lua @@ -19,6 +19,12 @@ World.__mt = { __index = World, } +local defaultGenerator = function (state) + local current = state + state = state +1 + return string.format("%d", current), state +end + --- Creates a new World. -- @treturn World The new World function World.new() @@ -29,6 +35,13 @@ function World.new() __events = {}, __emitSDepth = 0, + __hash = { + state = -2^53, + generator = defaultGenerator, + keys = {}, + entities = {} + }, + __added = List(), __backAdded = List(), __removed = List(), __backRemoved = List(), __dirty = List(), __backDirty = List(), @@ -101,6 +114,14 @@ function World:removeEntity(e) Utils.error(2, "bad argument #1 to 'World:removeEntity' (Entity expected, got %s)", type(e)) end + if e.__world ~= self then + 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 @@ -343,16 +364,16 @@ function World:getSystems() return self.__systems end -function World:serialize() +function World:serialize(ignoreKeys) self:__flush() - local data = {} + local data = { generator = self.__hash.state } for i = 1, self.__entities.size do local entity = self.__entities[i] if entity.serializable then - local entityData = entity:serialize() + local entityData = entity:serialize(ignoreKeys) table.insert(data, entityData) end end @@ -360,21 +381,85 @@ function World:serialize() return data end -function World:deserialize(data, append) - if (not append) then +function World:deserialize(data, startClean, ignoreGenerator) + if startClean then self:clear() end + if (not ignoreGenerator) then + self.__hash.state = data.generator + end + + local entities = {} + for i = 1, #data do - local entityData = data[i] + local entity = Entity(self) - local entity = Entity() - entity:deserialize(entityData) + if data[i].key then + local component = Components.key:__new() + entity.key = component:deserialize(data[i].key) - self:addEntity(entity) + entity:__dirty() + end + + entities[i] = entity + end + + for i = 1, #data do + entity[i]:deserialize(data[i]) end self:__flush() + + return self +end + +function World:setKeyGenerator(generator, initialState) + if not Type.isCallable(generator) then + Utils.error(2, "bad argument #1 to 'World:setKeyGenerator' (function expected, got %s)", type(generator)) + end + + self.__hash.generator = generator + self.__hash.state = initialState + + return self +end + +function World:__clearKey(e) + local key = self.__hash.keys[e] + + if key then + 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 --- Callback for when an Entity is added to the World.