From 71fd59371148f371fc80ac67a729cd432cfef422 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 19 Nov 2024 21:19:15 +0700 Subject: [PATCH] from scratch --- .vscode/extensions.json | 2 +- .vscode/settings.json | 2 +- develop/example.lua | 35 +--- develop/iterators.lua | 78 -------- develop/unbench.lua | 42 +---- develop/untests.lua | 153 +--------------- develop/utilities.lua | 11 -- evolved.lua | 397 ---------------------------------------- evolved/evolved.lua | 4 + 9 files changed, 9 insertions(+), 715 deletions(-) delete mode 100644 develop/iterators.lua delete mode 100644 develop/utilities.lua delete mode 100644 evolved.lua create mode 100644 evolved/evolved.lua diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 488eee7..d9b86f8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,5 @@ { "recommendations": [ - "sumneko.lua", + "sumneko.lua" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e2b5f3f..e01d82d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,6 @@ "editor.formatOnSave": true, "files.insertFinalNewline": true, "files.trimFinalNewlines": true, - "files.trimTrailingWhitespace": true, + "files.trimTrailingWhitespace": true } } diff --git a/develop/example.lua b/develop/example.lua index bdc3bb7..21eae22 100644 --- a/develop/example.lua +++ b/develop/example.lua @@ -1,34 +1 @@ -local evolved = require 'evolved' - -local registry = evolved.registry() - -local fragments = { - position = registry:entity(), - velocity = registry:entity(), -} - -do - registry:entity( - fragments.position, - fragments.velocity) -end - -do - local entity = registry:entity() - entity:insert(fragments.position) - entity:insert(fragments.velocity) -end - -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 +local evolved = require 'evolved.evolved' diff --git a/develop/iterators.lua b/develop/iterators.lua deleted file mode 100644 index 04a6a8d..0000000 --- a/develop/iterators.lua +++ /dev/null @@ -1,78 +0,0 @@ -local iterators = {} - ----@generic K ----@param t table ----@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 ----@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 diff --git a/develop/unbench.lua b/develop/unbench.lua index 24ca578..21eae22 100644 --- a/develop/unbench.lua +++ b/develop/unbench.lua @@ -1,41 +1 @@ -local evolved = require 'evolved' -local utilities = require 'develop.utilities' - ----@param name string ----@param func fun(...):... ----@param ... any -local function describe(name, func, ...) - collectgarbage('stop') - - print(string.format('| unbench | %s ...', name)) - - local start_s = os.clock() - local start_kb = collectgarbage('count') - - local success, result = pcall(func, ...) - - local finish_s = os.clock() - start_s - local finish_kb = collectgarbage('count') - start_kb - - print(string.format(' %s | ms: %.2f | mb: %.2f', - success and 'OK' or 'FAILED', finish_s * 1000, finish_kb / 1024)) - - if not success then print(' ' .. result) end - - collectgarbage('restart') -end - -describe('memory footprint of 1k entities', function() - local registry = evolved.registry() - for _ = 1, 1000 do registry:entity() end -end) - -describe('memory footprint of 10k entities', function() - local registry = evolved.registry() - for _ = 1, 10000 do registry:entity() end -end) - -describe('memory footprint of 100k entities', function() - local registry = evolved.registry() - for _ = 1, 100000 do registry:entity() end -end) +local evolved = require 'evolved.evolved' diff --git a/develop/untests.lua b/develop/untests.lua index 96125e6..21eae22 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -1,152 +1 @@ -local evolved = require 'evolved' - -local iterators = require 'develop.iterators' -local utilities = require 'develop.utilities' - ----@param name string ----@param func fun(...):... ----@param ... any -local function describe(name, func, ...) - collectgarbage('stop') - - print(string.format('| untests | %s ...', name)) - - local start_s = os.clock() - local start_kb = collectgarbage('count') - - local success, result = pcall(func, ...) - - local finish_s = os.clock() - start_s - local finish_kb = collectgarbage('count') - start_kb - - print(string.format(' %s | ms: %.2f | mb: %.2f', - success and 'OK' or 'FAILED', finish_s * 1000, finish_kb / 1024)) - - if not success then print(' ' .. result) end - - collectgarbage('restart') -end - -describe('entity:entity', 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 - - for _ = 1, 100 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 e1 = registry:entity() - for _, fragment in ipairs(insert_fragments) do e1:insert(fragment) end - - local e2 = registry:entity(unpack(insert_fragments)) - - assert(e1.chunk == e2.chunk) - end - end -end) - -describe('entity:chunk', 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 - - 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 - - ---@type evolved.entity[] - local remove_fragments = {} - local remove_fragment_count = math.random(1, 10) - for i = 1, remove_fragment_count do remove_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) - - utilities.shuffle_array(remove_fragments) - for _, fragment in ipairs(remove_fragments) do e1:remove(fragment) end - - utilities.shuffle_array(remove_fragments) - for _, fragment in ipairs(remove_fragments) do e2:remove(fragment) end - - assert(e1.chunk == e2.chunk) - 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) +local evolved = require 'evolved.evolved' diff --git a/develop/utilities.lua b/develop/utilities.lua deleted file mode 100644 index 33cc5d5..0000000 --- a/develop/utilities.lua +++ /dev/null @@ -1,11 +0,0 @@ -local utilities = {} - ----@param vs any[] -function utilities.shuffle_array(vs) - for i = 1, #vs do - local j = math.random(i, #vs) - vs[i], vs[j] = vs[j], vs[i] - end -end - -return utilities diff --git a/evolved.lua b/evolved.lua deleted file mode 100644 index 5f3ea55..0000000 --- a/evolved.lua +++ /dev/null @@ -1,397 +0,0 @@ ----@class evolved -local evolved = {} - ---- ---- ---- ---- ---- - ----@class evolved.chunk ----@field owner evolved.registry ----@field parent? evolved.chunk ----@field fragment? evolved.entity ----@field children evolved.chunk[] ----@field entities evolved.entity[] ----@field components table ----@field with_cache table ----@field without_cache table -local evolved_chunk_mt = {} -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 - ----@class evolved.entity ----@field owner evolved.registry ----@field id integer ----@field chunk? evolved.chunk ----@field index_in_chunk? integer -local evolved_entity_mt = {} -evolved_entity_mt.__index = evolved_entity_mt - ----@class evolved.registry ----@field nextid integer ----@field chunks evolved.chunk[] ----@field queries evolved.query[] ----@field entities evolved.entity[] ----@field chunks_by_fragment table ----@field queries_by_fragment table -local evolved_registry_mt = {} -evolved_registry_mt.__index = evolved_registry_mt - ---- ---- ---- CHUNK API ---- ---- - -function evolved_chunk_mt:__tostring() - local id, iter = '', self - while iter and iter.parent and iter.fragment do - id, iter = iter.fragment.id .. (id == '' and '' or ',') .. id, iter.parent - end - return string.format('evolved.chunk(%s)', id) -end - ----@param fragment evolved.entity ----@return evolved.chunk -function evolved_chunk_mt:with(fragment) - do - local with_chunk = self.with_cache[fragment] - if with_chunk then return with_chunk end - end - - if self.fragment and self.fragment.id == fragment.id then - return self - end - - if self.fragment and self.fragment.id > fragment.id then - local sibling_chunk = self.parent - :with(fragment) - :with(self.fragment) - - self.with_cache[fragment] = sibling_chunk - - 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 ----@return evolved.chunk -function evolved_chunk_mt:without(fragment) - do - local without_chunk = self.without_cache[fragment] - if without_chunk then return without_chunk end - end - - if not self.fragment or self.fragment.id < fragment.id then - return self - end - - local sibling_chunk = self.parent - :without(fragment) - :with(self.fragment) - - self.without_cache[fragment] = sibling_chunk - - return sibling_chunk -end - ----@param entity evolved.entity -function evolved_chunk_mt:insert(entity) - self.entities[#self.entities + 1] = entity - entity.chunk, entity.index_in_chunk = self, #self.entities -end - ----@param entity evolved.entity -function evolved_chunk_mt:remove(entity) - local last_entity = self.entities[#self.entities] - - if entity ~= last_entity then - self.entities[entity.index_in_chunk] = last_entity - last_entity.index_in_chunk = entity.index_in_chunk - end - - self.entities[#self.entities] = nil - entity.chunk, entity.index_in_chunk = nil, 0 -end - ----@param fragment evolved.entity ----@return boolean ----@nodiscard -function evolved_chunk_mt:has_fragment(fragment) - return self.components[fragment] ~= nil -end - ----@param ... evolved.entity ----@return boolean ----@nodiscard -function evolved_chunk_mt:has_all_fragments(...) - local fragment_count = select('#', ...) - - for i = 1, fragment_count do - local fragment = select(i, ...) - if self.components[fragment] == nil then - return false - end - end - - return true -end - ----@param ... evolved.entity ----@return boolean ----@nodiscard -function evolved_chunk_mt:has_any_fragment(...) - local fragment_count = select('#', ...) - - for i = 1, fragment_count do - local fragment = select(i, ...) - if self.components[fragment] ~= nil then - return true - end - end - - 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 ---- ---- - -function evolved_entity_mt:__tostring() - return string.format('evolved.entity(%d)', self.id) -end - -function evolved_entity_mt:destroy() - self.chunk:remove(self) -end - ----@param fragment evolved.entity -function evolved_entity_mt:insert(fragment) - local old_chunk = assert(self.chunk) - local new_chunk = old_chunk:with(fragment) - - old_chunk:remove(self) - new_chunk:insert(self) -end - ----@param fragment evolved.entity -function evolved_entity_mt:remove(fragment) - local old_chunk = assert(self.chunk) - local new_chunk = old_chunk:without(fragment) - - old_chunk:remove(self) - new_chunk:insert(self) -end - ---- ---- ---- REGISTRY API ---- ---- - ----@param ... evolved.entity ----@return evolved.chunk -function evolved_registry_mt:chunk(...) - local chunk = self.chunks[1] - - for i = 1, select('#', ...) do - chunk = chunk:with(select(i, ...)) - end - - return chunk -end - ----@param ... evolved.entity ----@return evolved.query -function evolved_registry_mt:query(...) - local id = self.nextid - self.nextid = self.nextid + 1 - - ---@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 - ----@param ... evolved.entity ----@return evolved.entity -function evolved_registry_mt:entity(...) - local id = self.nextid - self.nextid = self.nextid + 1 - - ---@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 - - self:chunk(...):insert(entity) - - return entity -end - ---- ---- ---- MODULE API ---- ---- - ----@return evolved.registry -function evolved.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 - ---- ---- ---- ---- ---- - -return evolved diff --git a/evolved/evolved.lua b/evolved/evolved.lua new file mode 100644 index 0000000..5c5d2c6 --- /dev/null +++ b/evolved/evolved.lua @@ -0,0 +1,4 @@ +---@class evolved +local evolved = {} + +return evolved