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.
This commit is contained in:
Pablo Ariel Mayobre 2023-02-14 18:14:25 -03:00
parent 3d195c790f
commit a55efd042a
6 changed files with 148 additions and 15 deletions

View file

@ -2,4 +2,5 @@ local PATH = (...):gsub("(%.init)$", "")
return {
serializable = require(PATH..".serializable"),
key = require(PATH..".key"),
}

37
concord/builtins/key.lua Normal file
View file

@ -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

View file

@ -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, ...)

View file

@ -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

View file

@ -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))

View file

@ -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.