math.randomseed(os.time()) local module = {} setmetatable(module, {__index=_G}) setfenv(1, module) --===== Components =====-- -- Components provide a kind-of-type-safe way of storing values, since they are -- guaranteed to have a specific set of keys and no other values in them. They also serialize -- nicely for storage. Component = {} function Component.newFactory(name, params, serialize) -- create the metatable for this component type local metatable = {} metatable.__tostring = serialize or Component.serialize -- nice serialization metatable.__newindex = function(tbl, index, value) if params[index] ~= nil then tbl[index] = value else -- throw an error if we try to set a key that is not in the valid set error(string.format("%q is not a valid key for a component of type %q", index, name)) end end -- the factory function for creating components return function(tbl) local tbl = tbl or {} local self = { __type=name } -- ensure that all keys are present for k, v in pairs(params) do self[k] = v end setmetatable(self, metatable) -- set the keys from the factory call for k, v in pairs(tbl) do self[k] = v end return self end end function Component.serialize(self) local str = "{" for k, v in pairs(self) do if type(v) == "string" then str = str .. string.format("%s=%q,", k, v) else str = str .. string.format("%s=%s,", k, tostring(v)) end end str = string.sub(str, 1, -2) .. "}" return str end --===== EntityDb =====-- -- EntityDb is a database of entities and their associated components -- it should be quite efficient to query for all entities with a given component, and reasonably -- efficient to query for all components of a given entity EntityDb = {} EntityDb.__index = EntityDb function EntityDb.new(_) local self = { entities = {}, components = {}, } setmetatable(self, EntityDb) return self end setmetatable(EntityDb, {__call=EntityDb.new}) function EntityDb.checkIsValid(self, id) if not self.entities[id] then error(string.format("invalid entity id: %s", tostring(id))) end end local function uid() local template ='xxxx:xxxx:xxxx' return string.gsub(template, 'x', function (c) local v = math.random(0, 0xf) return string.format('%x', v) end) end function EntityDb.createEntity(self) local id = uid() self.entities[id] = true return id end function EntityDb.addComponent(self, id, name, value) self:checkIsValid(id) if not self.components[name] then self.components[name] = { count=0 } end local component = self.components[name] component[id] = value component.count = component.count + 1 end local function shallowCopy(tbl) local copy = {} for k, v in pairs(tbl) do copy[k] = v end return copy end function EntityDb.queryComponent(self, name) local query = shallowCopy(self.components[name]) query.count = nil return query end function EntityDb.queryEntity(self, id) self:checkIsValid(id) local query = {} for name, component in pairs(self.components) do query[name] = component[id] end return query end function EntityDb.removeComponent(self, id, name) self:checkIsValid(id) local component = self.components[name] if component[id] ~= nil then component[id] = nil component.count = component.count - 1 if component.count == 0 then self.components[name] = nil end end end function EntityDb.deleteEntity(self, id) self:checkIsValid(id) for name in pairs(self.components) do self:removeComponent(id, name) end self.entities[id] = nil end return module