mirror of
https://github.com/BlackMATov/evolved.lua.git
synced 2025-12-13 11:38:15 +07:00
first queries impl
This commit is contained in:
@@ -17,4 +17,12 @@ do
|
||||
local query = registry:query(
|
||||
fragments.position,
|
||||
fragments.velocity)
|
||||
|
||||
for chunk in query:chunks() do
|
||||
local ps = chunk.components[fragments.position]
|
||||
local vs = chunk.components[fragments.position]
|
||||
|
||||
for i = 1, #chunk.entities do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
78
develop/iterators.lua
Normal file
78
develop/iterators.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
local iterators = {}
|
||||
|
||||
---@generic K
|
||||
---@param t table<K, any>
|
||||
---@return fun(): K
|
||||
function iterators.keys(t)
|
||||
if #t > 0 then
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if i <= #t then
|
||||
return i
|
||||
end
|
||||
end
|
||||
else
|
||||
local k = nil
|
||||
return function()
|
||||
local v
|
||||
k, v = next(t, k)
|
||||
if k ~= nil then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@generic V
|
||||
---@param t table<any, V>
|
||||
---@return fun(): V?
|
||||
function iterators.values(t)
|
||||
if #t > 0 then
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if i <= #t then
|
||||
return t[i]
|
||||
end
|
||||
end
|
||||
else
|
||||
local k = nil
|
||||
return function()
|
||||
local v
|
||||
k, v = next(t, k)
|
||||
if k ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@generic V
|
||||
---@param iter fun(): V?
|
||||
---@return integer
|
||||
function iterators.count(iter)
|
||||
local count = 0
|
||||
for _ in iter do count = count + 1 end
|
||||
return count
|
||||
end
|
||||
|
||||
---@generic V
|
||||
---@param iter fun(): V?
|
||||
---@param func fun(v: V): boolean
|
||||
---@return fun(): V?
|
||||
function iterators.filter(iter, func)
|
||||
return function()
|
||||
while true do
|
||||
local v = iter()
|
||||
if v == nil then
|
||||
return
|
||||
end
|
||||
if func(v) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return iterators
|
||||
@@ -1,4 +1,6 @@
|
||||
local evolved = require 'evolved'
|
||||
|
||||
local iterators = require 'develop.iterators'
|
||||
local utilities = require 'develop.utilities'
|
||||
|
||||
---@param name string
|
||||
@@ -25,36 +27,8 @@ local function describe(name, func, ...)
|
||||
collectgarbage('restart')
|
||||
end
|
||||
|
||||
describe('random entity:insert', function()
|
||||
for _ = 1, 1000 do
|
||||
local registry = evolved.registry()
|
||||
|
||||
---@type evolved.entity[]
|
||||
local all_fragments = {}
|
||||
local all_fragment_count = math.random(1, 10)
|
||||
for i = 1, all_fragment_count do all_fragments[i] = registry:entity() end
|
||||
|
||||
for _ = 1, 100 do
|
||||
local e1, e2 = registry:entity(), registry:entity()
|
||||
|
||||
---@type evolved.entity[]
|
||||
local insert_fragments = {}
|
||||
local insert_fragment_count = math.random(1, 10)
|
||||
for i = 1, insert_fragment_count do insert_fragments[i] = all_fragments[math.random(1, all_fragment_count)] end
|
||||
|
||||
utilities.shuffle_array(insert_fragments)
|
||||
for _, fragment in ipairs(insert_fragments) do e1:insert(fragment) end
|
||||
|
||||
utilities.shuffle_array(insert_fragments)
|
||||
for _, fragment in ipairs(insert_fragments) do e2:insert(fragment) end
|
||||
|
||||
assert(e1.chunk == e2.chunk)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
describe('random entity:remove', function()
|
||||
for _ = 1, 1000 do
|
||||
describe('entity:chunk', function()
|
||||
for _ = 1, 500 do
|
||||
local registry = evolved.registry()
|
||||
|
||||
---@type evolved.entity[]
|
||||
@@ -93,3 +67,61 @@ describe('random entity:remove', function()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
describe('query:chunks', function()
|
||||
for _ = 1, 500 do
|
||||
local registry = evolved.registry()
|
||||
|
||||
---@type evolved.entity[]
|
||||
local all_fragments = {}
|
||||
local all_fragment_count = math.random(1, 10)
|
||||
for i = 1, all_fragment_count do all_fragments[i] = registry:entity() end
|
||||
|
||||
local function check_all_queries()
|
||||
for _, q in ipairs(registry.queries) do
|
||||
local query_chunk_count = iterators.count(q:chunks())
|
||||
local registry_chunk_count = iterators.count(iterators.filter(
|
||||
iterators.values(registry.chunks),
|
||||
function(chunk) return chunk:has_all_fragments(unpack(q.fragments)) end))
|
||||
assert(query_chunk_count == registry_chunk_count)
|
||||
end
|
||||
end
|
||||
|
||||
---@param n integer
|
||||
local function create_queries(n)
|
||||
for _ = 1, n do
|
||||
---@type evolved.entity[]
|
||||
local query_fragments = {}
|
||||
local query_fragment_count = math.random(1, 5)
|
||||
for i = 1, query_fragment_count do
|
||||
query_fragments[i] = all_fragments[math.random(1, all_fragment_count)]
|
||||
end
|
||||
|
||||
registry:query(unpack(query_fragments))
|
||||
end
|
||||
end
|
||||
|
||||
---@param n integer
|
||||
local function create_entities(n)
|
||||
for _ = 1, n do
|
||||
---@type evolved.entity[]
|
||||
local insert_fragments = {}
|
||||
local insert_fragment_count = math.random(1, 10)
|
||||
for i = 1, insert_fragment_count do
|
||||
insert_fragments[i] = all_fragments[math.random(1, all_fragment_count)]
|
||||
end
|
||||
|
||||
local e = registry:entity()
|
||||
for _, fragment in ipairs(insert_fragments) do e:insert(fragment) end
|
||||
end
|
||||
end
|
||||
|
||||
create_entities(50)
|
||||
create_queries(10)
|
||||
check_all_queries()
|
||||
|
||||
create_entities(50)
|
||||
create_queries(10)
|
||||
check_all_queries()
|
||||
end
|
||||
end)
|
||||
|
||||
282
evolved.lua
282
evolved.lua
@@ -11,6 +11,7 @@ local evolved = {}
|
||||
---@field owner evolved.registry
|
||||
---@field parent? evolved.chunk
|
||||
---@field fragment? evolved.entity
|
||||
---@field children evolved.chunk[]
|
||||
---@field entities evolved.entity[]
|
||||
---@field components table<evolved.entity, any[]>
|
||||
---@field with_cache table<evolved.entity, evolved.chunk>
|
||||
@@ -21,6 +22,8 @@ evolved_chunk_mt.__index = evolved_chunk_mt
|
||||
---@class evolved.query
|
||||
---@field owner evolved.registry
|
||||
---@field id integer
|
||||
---@field roots evolved.chunk[]
|
||||
---@field fragments evolved.entity[]
|
||||
local evolved_query_mt = {}
|
||||
evolved_query_mt.__index = evolved_query_mt
|
||||
|
||||
@@ -37,109 +40,11 @@ evolved_entity_mt.__index = evolved_entity_mt
|
||||
---@field chunks evolved.chunk[]
|
||||
---@field queries evolved.query[]
|
||||
---@field entities evolved.entity[]
|
||||
---@field chunks_by_fragment table<evolved.entity, evolved.chunk[]>
|
||||
---@field queries_by_fragment table<evolved.entity, evolved.query[]>
|
||||
local evolved_registry_mt = {}
|
||||
evolved_registry_mt.__index = evolved_registry_mt
|
||||
|
||||
---
|
||||
---
|
||||
---
|
||||
---
|
||||
---
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param chunk evolved.chunk
|
||||
local function on_new_chunk(owner, chunk)
|
||||
owner.chunks[#owner.chunks + 1] = chunk
|
||||
end
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param query evolved.query
|
||||
local function on_new_query(owner, query)
|
||||
owner.queries[#owner.queries + 1] = query
|
||||
end
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param entity evolved.entity
|
||||
local function on_new_entity(owner, entity)
|
||||
owner.entities[#owner.entities + 1] = entity
|
||||
end
|
||||
|
||||
---
|
||||
---
|
||||
---
|
||||
---
|
||||
---
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param parent? evolved.chunk
|
||||
---@param fragment? evolved.entity
|
||||
---@return evolved.chunk
|
||||
local function create_chunk(owner, parent, fragment)
|
||||
---@type evolved.chunk
|
||||
local chunk = {
|
||||
owner = owner,
|
||||
parent = parent,
|
||||
fragment = fragment,
|
||||
entities = {},
|
||||
components = {},
|
||||
with_cache = {},
|
||||
without_cache = {},
|
||||
}
|
||||
|
||||
do
|
||||
local iter = chunk
|
||||
while iter and iter.fragment do
|
||||
chunk.components[iter.fragment] = {}
|
||||
iter = iter.parent
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(chunk, evolved_chunk_mt)
|
||||
end
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param id integer
|
||||
---@return evolved.query
|
||||
local function create_query(owner, id)
|
||||
---@type evolved.query
|
||||
local query = {
|
||||
owner = owner,
|
||||
id = id,
|
||||
}
|
||||
return setmetatable(query, evolved_query_mt)
|
||||
end
|
||||
|
||||
---@param owner evolved.registry
|
||||
---@param id integer
|
||||
---@return evolved.entity
|
||||
local function create_entity(owner, id)
|
||||
---@type evolved.entity
|
||||
local entity = {
|
||||
owner = owner,
|
||||
id = id,
|
||||
}
|
||||
|
||||
owner.chunks[1]:insert(entity)
|
||||
|
||||
return setmetatable(entity, evolved_entity_mt)
|
||||
end
|
||||
|
||||
---@return evolved.registry
|
||||
local function create_registry()
|
||||
---@type evolved.registry
|
||||
local registry = {
|
||||
nextid = 1,
|
||||
chunks = {},
|
||||
queries = {},
|
||||
entities = {},
|
||||
}
|
||||
|
||||
local chunk = create_chunk(registry)
|
||||
on_new_chunk(registry, chunk)
|
||||
|
||||
return setmetatable(registry, evolved_registry_mt)
|
||||
end
|
||||
|
||||
---
|
||||
---
|
||||
--- CHUNK API
|
||||
@@ -166,17 +71,7 @@ function evolved_chunk_mt:with(fragment)
|
||||
return self
|
||||
end
|
||||
|
||||
if not self.fragment or self.fragment.id < fragment.id then
|
||||
local new_chunk = create_chunk(self.owner, self, fragment)
|
||||
on_new_chunk(self.owner, new_chunk)
|
||||
|
||||
self.with_cache[fragment] = new_chunk
|
||||
new_chunk.without_cache[fragment] = self
|
||||
|
||||
return new_chunk
|
||||
end
|
||||
|
||||
do
|
||||
if self.fragment and self.fragment.id > fragment.id then
|
||||
local sibling_chunk = self.parent
|
||||
:with(fragment)
|
||||
:with(self.fragment)
|
||||
@@ -185,6 +80,52 @@ function evolved_chunk_mt:with(fragment)
|
||||
|
||||
return sibling_chunk
|
||||
end
|
||||
|
||||
---@type evolved.chunk
|
||||
local new_chunk = {
|
||||
owner = self.owner,
|
||||
parent = self,
|
||||
fragment = fragment,
|
||||
children = {},
|
||||
entities = {},
|
||||
components = {},
|
||||
with_cache = {},
|
||||
without_cache = {},
|
||||
}
|
||||
setmetatable(new_chunk, evolved_chunk_mt)
|
||||
self.owner.chunks[#self.owner.chunks + 1] = new_chunk
|
||||
|
||||
do
|
||||
local iter = new_chunk
|
||||
while iter and iter.fragment do
|
||||
new_chunk.components[iter.fragment] = {}
|
||||
iter = iter.parent
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
self.children[#self.children + 1] = new_chunk
|
||||
end
|
||||
|
||||
do
|
||||
local chunks = self.owner.chunks_by_fragment[fragment] or {}
|
||||
chunks[#chunks + 1] = new_chunk
|
||||
self.owner.chunks_by_fragment[fragment] = chunks
|
||||
end
|
||||
|
||||
do
|
||||
local queries = self.owner.queries_by_fragment[fragment] or {}
|
||||
for _, query in ipairs(queries) do
|
||||
if new_chunk:has_all_fragments(unpack(query.fragments)) then
|
||||
query.roots[#query.roots + 1] = new_chunk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.with_cache[fragment] = new_chunk
|
||||
new_chunk.without_cache[fragment] = self
|
||||
|
||||
return new_chunk
|
||||
end
|
||||
|
||||
---@param fragment evolved.entity
|
||||
@@ -199,7 +140,6 @@ function evolved_chunk_mt:without(fragment)
|
||||
return self
|
||||
end
|
||||
|
||||
do
|
||||
local sibling_chunk = self.parent
|
||||
:without(fragment)
|
||||
:with(self.fragment)
|
||||
@@ -207,7 +147,6 @@ function evolved_chunk_mt:without(fragment)
|
||||
self.without_cache[fragment] = sibling_chunk
|
||||
|
||||
return sibling_chunk
|
||||
end
|
||||
end
|
||||
|
||||
---@param entity evolved.entity
|
||||
@@ -268,6 +207,37 @@ function evolved_chunk_mt:has_any_fragment(...)
|
||||
return false
|
||||
end
|
||||
|
||||
---
|
||||
---
|
||||
--- QUERY API
|
||||
---
|
||||
---
|
||||
|
||||
function evolved_query_mt:__tostring()
|
||||
return string.format('evolved.query(%d)', self.id)
|
||||
end
|
||||
|
||||
---@return fun(): evolved.chunk?
|
||||
function evolved_query_mt:chunks()
|
||||
return coroutine.wrap(function()
|
||||
local queue = {}
|
||||
|
||||
for i = #self.roots, 1, -1 do
|
||||
queue[#queue + 1] = self.roots[i]
|
||||
end
|
||||
|
||||
while #queue > 0 do
|
||||
local chunk = table.remove(queue)
|
||||
|
||||
coroutine.yield(chunk)
|
||||
|
||||
for i = #chunk.children, 1, -1 do
|
||||
queue[#queue + 1] = chunk.children[i]
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---
|
||||
--- ENTITY API
|
||||
@@ -306,18 +276,45 @@ end
|
||||
---
|
||||
---
|
||||
|
||||
---@return evolved.chunk
|
||||
function evolved_registry_mt:root()
|
||||
return self.chunks[1]
|
||||
end
|
||||
|
||||
---@param ... evolved.entity
|
||||
---@return evolved.query
|
||||
function evolved_registry_mt:query(...)
|
||||
local id = self.nextid
|
||||
self.nextid = self.nextid + 1
|
||||
local query = create_query(self, id)
|
||||
on_new_query(self, query)
|
||||
|
||||
---@type evolved.query
|
||||
local query = {
|
||||
owner = self,
|
||||
id = id,
|
||||
roots = {},
|
||||
fragments = {},
|
||||
}
|
||||
setmetatable(query, evolved_query_mt)
|
||||
self.queries[#self.queries + 1] = query
|
||||
|
||||
do
|
||||
local fragments = { ... }
|
||||
table.sort(fragments, function(a, b) return a.id < b.id end)
|
||||
query.fragments = fragments
|
||||
end
|
||||
|
||||
if #query.fragments > 0 then
|
||||
local fragment = query.fragments[#query.fragments]
|
||||
local chunks = self.chunks_by_fragment[fragment] or {}
|
||||
for _, chunk in ipairs(chunks) do
|
||||
if chunk:has_all_fragments(...) then
|
||||
query.roots[#query.roots + 1] = chunk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #query.fragments > 0 then
|
||||
local fragment = query.fragments[#query.fragments]
|
||||
local queries = self.queries_by_fragment[fragment] or {}
|
||||
queries[#queries + 1] = query
|
||||
self.queries_by_fragment[fragment] = queries
|
||||
end
|
||||
|
||||
return query
|
||||
end
|
||||
|
||||
@@ -325,8 +322,21 @@ end
|
||||
function evolved_registry_mt:entity()
|
||||
local id = self.nextid
|
||||
self.nextid = self.nextid + 1
|
||||
local entity = create_entity(self, id)
|
||||
on_new_entity(self, entity)
|
||||
|
||||
---@type evolved.entity
|
||||
local entity = {
|
||||
owner = self,
|
||||
id = id,
|
||||
chunk = nil,
|
||||
index_in_chunk = nil,
|
||||
}
|
||||
setmetatable(entity, evolved_entity_mt)
|
||||
self.entities[#self.entities + 1] = entity
|
||||
|
||||
do
|
||||
self.chunks[1]:insert(entity)
|
||||
end
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
@@ -338,7 +348,33 @@ end
|
||||
|
||||
---@return evolved.registry
|
||||
function evolved.registry()
|
||||
return create_registry()
|
||||
---@type evolved.registry
|
||||
local registry = {
|
||||
nextid = 1,
|
||||
chunks = {},
|
||||
queries = {},
|
||||
entities = {},
|
||||
chunks_by_fragment = {},
|
||||
queries_by_fragment = {},
|
||||
}
|
||||
setmetatable(registry, evolved_registry_mt)
|
||||
|
||||
---@type evolved.chunk
|
||||
local root_chunk = {
|
||||
owner = registry,
|
||||
parent = nil,
|
||||
fragment = nil,
|
||||
children = {},
|
||||
entities = {},
|
||||
components = {},
|
||||
with_cache = {},
|
||||
without_cache = {},
|
||||
}
|
||||
setmetatable(root_chunk, evolved_chunk_mt)
|
||||
|
||||
registry.chunks[1] = root_chunk
|
||||
|
||||
return registry
|
||||
end
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user