local ecs = require "ecs.core" local REFERENCE_ID = 1 local function get_attrib(opt, inout) if opt == nil then return { exist = true } end local desc = {} if opt == "?" then desc.opt = true else assert(opt == ":") end if inout == "in" then desc.r = true elseif inout == "out" then desc.w = true elseif inout == "update" then desc.r = true desc.w = true elseif inout == "exist" then desc.exist = true assert(not desc.opt) elseif inout == "absent" then desc.absent = true assert(not desc.opt) else assert(inout == "temp") end return desc end local function cache_world(obj, k) local c = { typenames = {}, id = 0, select = {}, ref = {}, } local function gen_ref_pat(key) local typenames = c.typenames local desc = {} local tc = typenames[key] if tc == nil then error("Unknown type " .. key) end local a = { exist = true, name = tc.name, id = tc.id, type = tc.type, } local n = #tc for i=1,#tc do a[i] = tc[i] end desc[1] = a return desc end local function gen_all_pat() local desc = {} local i = 1 for name,t in pairs(c.typenames) do if t.tag ~= "ORDER" then local a = { name = t.name, id = t.id, type = t.type, opt = true, r = true, } table.move(t, 1, #t, 1, a) desc[i] = a i = i + 1 end end return desc end setmetatable(c, { __index = function(_, key) if key == "all" then local all = k:_groupiter(gen_all_pat()) c.all = all return all end end }) local function gen_select_pat(pat) local typenames = c.typenames local desc = {} local idx = 1 for token in pat:gmatch "[^ ]+" do local key, index, padding = token:match "^([_%w]+)%(?([_%w]*)%)?(.*)" assert(key, "Invalid pattern") local opt, inout if padding ~= "" then opt, inout = padding:match "^([:?])(%l+)$" assert(opt, "Invalid pattern") end local tc = typenames[key] if tc == nil then error("Unknown type " .. key) end if index ~= "" then local indexc = typenames[index] if indexc == nil then error("Unknown index type "..index) end local a = get_attrib(opt, inout == "temp" and "temp" or "in") a.name = index a.id = indexc.id a.type = indexc.type a.ref = true desc[idx] = a idx = idx + 1 end local a = get_attrib(opt, inout) a.name = tc.name a.id = tc.id a.type = tc.type local n = #tc for i=1,#tc do a[i] = tc[i] end desc[idx] = a idx = idx + 1 if tc.ref and index == "" then local dead = typenames[key .. "_dead"] local a = { absent = true, name = dead.name, id = dead.id, } desc[idx] = a idx = idx + 1 end end return desc end local function cache_select(cache, pat) local pat_desc = gen_select_pat(pat) cache[pat] = k:_groupiter(pat_desc) return cache[pat] end setmetatable(c.select, { __mode = "kv", __index = cache_select, }) local function cache_ref(cache, pat) local pat_desc = gen_ref_pat(pat) cache[pat] = k:_groupiter(pat_desc) return cache[pat] end setmetatable(c.ref, { __mode = "kv", __index = cache_ref, }) obj[k] = c return c end local context = setmetatable({}, { __index = cache_world }) local typeid = { int = assert(ecs._TYPEINT), float = assert(ecs._TYPEFLOAT), bool = assert(ecs._TYPEBOOL), int64 = assert(ecs._TYPEINT64), dword = assert(ecs._TYPEDWORD), word = assert(ecs._TYPEWORD), byte = assert(ecs._TYPEBYTE), double = assert(ecs._TYPEDOUBLE), userdata = assert(ecs._TYPEUSERDATA), } local typesize = { [typeid.int] = 4, [typeid.float] = 4, [typeid.bool] = 1, [typeid.int64] = 8, [typeid.dword] = 4, [typeid.word] = 2, [typeid.byte] = 1, [typeid.double] = 8, [typeid.userdata] = 8, } local M = ecs._METHODS do -- newtype local function parse(s) -- s is "name:typename" local name, typename = s:match "^([%w_]+):(%w+)$" local typeid = assert(typeid[typename]) return { typeid, name } end local function align(c, field) local t = field[1] local tsize = typesize[t] local offset = ((c.size + tsize - 1) & ~(tsize-1)) c.size = offset + tsize field[3] = offset return field end local function align_struct(c, t) if t then local s = typesize[t] - 1 c.size = ((c.size + s) & ~s) end end function M:register(typeclass) local name = assert(typeclass.name) local ctx = context[self] ctx.all = nil -- clear all pattern local typenames = ctx.typenames local id = ctx.id + 1 assert(typenames[name] == nil and id <= ecs._MAXTYPE) ctx.id = id local c = { id = id, name = name, size = 0, } for i, v in ipairs(typeclass) do c[i] = align(c, parse(v)) end local ttype = typeclass.type if ttype == "lua" then assert(c.size == 0) c.size = ecs._LUAOBJECT assert(c[1] == nil) elseif c.size > 0 then align_struct(c, typeclass[1][1]) else -- size == 0, one value if ttype then local t = assert(typeid[typeclass.type]) c.type = t c.size = typesize[t] c[1] = { t, "v", 0 } elseif typeclass.order then c.size = ecs._ORDERKEY c.tag = "ORDER" else c.tag = true end end typenames[name] = c self:_newtype(id, c.size) if typeclass.ref then c.ref = true self:register { name = name .. "_dead" } end end end local function dump(obj) for e,v in pairs(obj) do if type(v) == "table" then for k,v in pairs(v) do print(e,k,v) end else print(e,v) end end end function M:new(obj) -- dump(obj) local eid = self:_newentity() local typenames = context[self].typenames local reference = obj.reference if reference then obj.reference = nil end for k,v in pairs(obj) do local tc = typenames[k] if not tc then error ("Invalid key : ".. k) end local id = self:_addcomponent(eid, tc.id) if tc.tag ~= "ORDER" then self:object(k, id, v) end end if reference then local id = self:_addcomponent(eid, REFERENCE_ID) reference[1] = id reference[2] = REFERENCE_ID self:object("reference", id, reference) obj.reference = reference return reference end end function M:ref(name, refobj) local obj = assert(refobj[name]) local ctx = context[self] local typenames = ctx.typenames local tc = assert(typenames[name]) local refid = self:_reuse(tc.id) refobj[2] = tc.id if refid then local p = context[self].select[name .. ":out"] refobj[1] = refid self:_sync(p, refobj) else local eid = self:_newentity() refid = self:_addcomponent(eid, tc.id) refobj[1] = refid self:object(name, refid, obj) end for k,v in pairs(refobj) do if (v == true or v == false) and name ~= k then local p = context[self].select[k .. "?out"] self:_sync(p, refobj) end end return refid end function M:object_ref(name, refid) local typenames = context[self].typenames return { refid, typenames[name].id } end function M:release(name, refid) local id = assert(context[self].typenames[name].id) self:_release(id, refid) end function M:context(t) local typenames = context[self].typenames local id = {} for i, name in ipairs(t) do local tc = typenames[name] if not tc then error ("Invalid component name " .. name) end id[i] = tc.id end return self:_context(id) end function M:select(pat) return context[self].select[pat]() end function M:sync(pat, iter) local p = context[self].select[pat] self:_sync(p, iter) return iter end function M:readall(iter) local p = context[self].all self:_sync(p, iter) return iter end function M:clear(name) local id = assert(context[self].typenames[name].id) self:_clear(id) end function M:dumpid(name) local typenames = context[self].typenames return self:_dumpid(typenames[name].id) end function M:update() self:_update_reference(REFERENCE_ID) self:_update() end function M:remove_reference(ref) ref.reference = false self:sync("reference:out", ref) ref[1] = nil end do local _object = M._object function M:object(name, refid, v) local pat = context[self].ref[name] return _object(pat, v, refid) end function M:singleton(name, pattern, iter) local typenames = context[self].typenames if iter == nil then iter = { 1, typenames[name].id } if pattern then local p = context[self].select[pattern] self:_sync(p, iter) end return iter else iter[1] = 1 iter[2] = typenames[name].id local p = context[self].select[pattern] self:_sync(p, iter) end return iter end end function ecs.world() local w = ecs._world() context[w].typenames.REMOVED = { name = "REMOVED", id = ecs._REMOVED, size = 0, tag = true, } w:register { name = "reference", type = "lua", } assert(context[w].typenames.reference.id == REFERENCE_ID) return w end return ecs