Less memory footprint

This commit is contained in:
Ukendio 2024-07-14 05:38:44 +02:00
parent 8bea43a9fc
commit 85a970e9ff
5 changed files with 1089 additions and 579 deletions

View file

@ -1,6 +1,7 @@
{
"aliases": {
"jecs": "src",
"testkit": "testkit"
"testkit": "testkit",
"mirror": "mirror"
}
}

View file

@ -12,7 +12,7 @@
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Lib": {
"$path": "lib"
"$path": "src"
},
"rgb": {
"$path": "rgb.luau"

File diff suppressed because it is too large Load diff

View file

@ -688,24 +688,22 @@ local function replaceMult(row, columns, ...)
end
end
local function preparedQuery(compatibleArchetypes: { Archetype },
components: { i53? }, indices: { { number } })
local query: (World, ...i53) -> Query
do
local indices: { { number } }
local compatibleArchetypes: { Archetype }
local length
local components: { number }
local queryLength: number
local lastArchetype: number
local archetype: Archetype
local queryLength = #components
local queryOutput: { any }
local lastArchetype = 1
local archetype: Archetype = compatibleArchetypes[lastArchetype]
local entities: {}
local i: number
if not archetype then
return EmptyQuery
end
local queryOutput = {}
local entities = archetype.entities
local i = #entities
local function queryNext(): ...any
local function query_next()
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
@ -735,35 +733,19 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
elseif queryLength == 4 then
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
elseif queryLength == 5 then
return entityId,
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row],
columns[tr[4]][row],
return entityId,columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
columns[tr[5]][row]
elseif queryLength == 6 then
return entityId,
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row],
columns[tr[4]][row],
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
columns[tr[5]][row],
columns[tr[6]][row]
elseif queryLength == 7 then
return entityId,
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row],
columns[tr[4]][row],
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
columns[tr[5]][row],
columns[tr[6]][row],
columns[tr[7]][row]
elseif queryLength == 8 then
return entityId,
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row],
columns[tr[4]][row],
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
columns[tr[5]][row],
columns[tr[6]][row],
columns[tr[7]][row],
@ -777,7 +759,7 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
return entityId, unpack(queryOutput, 1, queryLength)
end
local function without(self, ...): Query
local function query_without(self, ...): Query
local withoutComponents = { ... }
for i = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i]
@ -803,16 +785,16 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
return self
end
local function iter()
local function query_iter()
lastArchetype = 1
archetype = compatibleArchetypes[1]
entities = archetype.entities
i = #entities
return queryNext
return query_next
end
local function replace(_, fn: any)
local function query_replace(_, fn: any)
for i, archetype in compatibleArchetypes do
local tr = indices[i]
local columns = archetype.columns
@ -852,29 +834,18 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
end
end
local it = {
__iter = iter,
next = queryNext,
without = without,
replace = replace
}
return setmetatable(it, it) :: any
end
local function query(world: World, ...: number): Query
function query(world: World, ...: number): Query
-- breaking?
if (...) == nil then
error("Missing components")
end
local indices: { { number } } = {}
local compatibleArchetypes: { Archetype } = {}
local length = 0
indices = {}
compatibleArchetypes = {}
length = 0
components = { ... }
local components: { number } = { ... }
local archetypes: { Archetype } = world.archetypes :: any
local firstArchetypeMap: ArchetypeMap
local componentIndex = world.componentIndex
@ -890,8 +861,8 @@ local function query(world: World, ...: number): Query
end
for id in firstArchetypeMap.cache do
local archetype = archetypes[id]
local archetypeRecords = archetype.records
local compatibleArchetype = archetypes[id]
local archetypeRecords = compatibleArchetype.records
local records: { number } = {}
local skip = false
@ -911,11 +882,32 @@ local function query(world: World, ...: number): Query
end
length += 1
compatibleArchetypes[length] = archetype
compatibleArchetypes[length] = compatibleArchetype
indices[length] = records
end
return preparedQuery(compatibleArchetypes, components, indices)
lastArchetype = 1
archetype = compatibleArchetypes[lastArchetype]
if not archetype then
return EmptyQuery
end
queryOutput = {}
queryLength = #components
entities = archetype.entities
i = #entities
local it = {
__iter = query_iter,
next = query_next,
without = query_without,
replace = query_replace
}
return setmetatable(it, it) :: any
end
end
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
@ -1055,6 +1047,18 @@ export type WorldShim = typeof(setmetatable(
local World = {}
World.__index = World
World.entity = entity
World.query = query
World.remove = remove
World.clear = clear
World.delete = delete
World.component = newComponent
World.add = add
World.set = set
World.get = get
World.target = target
World.parent = parent
function World.new()
local self = setmetatable({
archetypeIndex = {} :: { [string]: Archetype },
@ -1072,6 +1076,7 @@ function World.new()
nextEntityId = 0,
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
}, World)
self.ROOT_ARCHETYPE = archetypeOf(self, {})
-- Initialize built-in components
@ -1080,18 +1085,6 @@ function World.new()
return self
end
World.entity = entity
World.query = query
World.remove = remove
World.clear = clear
World.delete = delete
World.component = newComponent
World.add = add
World.set = set
World.get = get
World.target = target
World.parent = parent
return {
World = World :: { new: () -> WorldShim } ,

56
test/leaky.luau Normal file
View file

@ -0,0 +1,56 @@
local function calculateAverage(times)
local sum = 0
for _, time in ipairs(times) do
sum = sum + time
end
return sum / #times
end
-- Main logic to time the test function
local CASES = {
jecs = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
end
end,
mirror = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
end
end
}
for name, fn in CASES do
local times = {}
local allocations = {}
local ecs = require("@"..name)
local world = ecs.World.new()
local A, B, C = world:component(), world:component(), world:component()
for i = 1, 5 do
local e = world:entity()
world:add(e, A)
world:add(e, B)
world:add(e, C)
end
collectgarbage("collect")
local count = collectgarbage("count")
for i = 1, 50000 do
local startTime = os.clock()
fn(world, A, B, C)
local allocated = collectgarbage("count")
collectgarbage("collect")
local endTime = os.clock()
table.insert(times, endTime - startTime)
table.insert(allocations, allocated)
end
print(name, "gc cycle time", calculateAverage(times))
print(name, "memory allocated", calculateAverage(allocations))
end