From c95bc2a582f825d89f9316e2e19b813019692576 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 24 Jun 2024 19:02:27 +0200 Subject: [PATCH 1/6] Update benchmarks (#55) * Update bench.project.json for luau files * Stress test insertion --- bench.project.json | 2 +- benches/visual/insertion.bench.luau | 30 +++++------------------------ 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/bench.project.json b/bench.project.json index e55b3ec..33ded9b 100644 --- a/bench.project.json +++ b/bench.project.json @@ -15,7 +15,7 @@ "$path": "lib" }, "rgb": { - "$path": "rgb.lua" + "$path": "rgb.luau" }, "benches": { "$path": "benches" diff --git a/benches/visual/insertion.bench.luau b/benches/visual/insertion.bench.luau index 8e24f29..40bea46 100644 --- a/benches/visual/insertion.bench.luau +++ b/benches/visual/insertion.bench.luau @@ -54,8 +54,9 @@ return { Functions = { Matter = function() - for i = 1, 500 do - newWorld:spawn( + local e = newWorld:spawn() + for i = 1, 5000 do + newWorld:insert(e, A1({ value = true }), A2({ value = true }), A3({ value = true }), @@ -71,7 +72,7 @@ return { ECR = function() local e = registry2.create() - for i = 1, 500 do + for i = 1, 5000 do registry2:set(e, B1, {value = false}) registry2:set(e, B2, {value = false}) registry2:set(e, B3, {value = false}) @@ -85,11 +86,8 @@ return { Jecs = function() - local e = ecs:entity() - - for i = 1, 500 do - + for i = 1, 5000 do ecs:set(e, C1, {value = false}) ecs:set(e, C2, {value = false}) ecs:set(e, C3, {value = false}) @@ -101,23 +99,5 @@ return { end end, - Mirror = function() - - local e = ecs:entity() - - for i = 1, 500 do - - mcs:set(e, E1, {value = false}) - mcs:set(e, E2, {value = false}) - mcs:set(e, E3, {value = false}) - mcs:set(e, E4, {value = false}) - mcs:set(e, E5, {value = false}) - mcs:set(e, E6, {value = false}) - mcs:set(e, E7, {value = false}) - mcs:set(e, E8, {value = false}) - - end - end - }, } From bc43ee336b337284179d2dd82ed6e3e4649eed49 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 25 Jun 2024 01:44:27 +0200 Subject: [PATCH 2/6] Freeze the world inside export declaration --- lib/init.luau | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/init.luau b/lib/init.luau index 76e9e9e..54b642f 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -865,10 +865,6 @@ function World.__iter(world: World): WorldIterator return iterator :: any end --- freezing it incase somebody tries doing something stupid and modifying it --- (unlikely but its easy to add extra safety so) -table.freeze(World) - -- __nominal_type_dont_use could not be any or T as it causes a type error -- or produces a union export type Entity = number & { __nominal_type_dont_use: T } @@ -999,7 +995,7 @@ export type WorldShim = typeof(setmetatable( )) return table.freeze({ - World = (World :: any) :: { new: () -> WorldShim }, + World = (table.freeze(World) :: any) :: { new: () -> WorldShim }, OnAdd = (ON_ADD :: any) :: Entity, OnRemove = (ON_REMOVE :: any) :: Entity, From 6b4597ab96ed0093879eccf1a88e0831da6024e0 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Wed, 26 Jun 2024 11:51:49 +0200 Subject: [PATCH 3/6] Remove allocations per compatible archetype to optimize fragmented iterations --- lib/init.luau | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/init.luau b/lib/init.luau index 54b642f..479cce6 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -640,13 +640,15 @@ export type Query = typeof(EmptyQuery) type CompatibleArchetype = { archetype: Archetype, indices: { number } } -local function PreparedQuery(compatibleArchetypes: { CompatibleArchetype } , components: { i53 }) +local function PreparedQuery( + compatibleArchetypes: { Archetype } , components: { i53 }, indices: { [number]: number }) + local queryLength = #components local lastArchetype = 1 - local compatibleArchetype: CompatibleArchetype = compatibleArchetypes[lastArchetype] + local archetype: Archetype = compatibleArchetypes[lastArchetype] - if not compatibleArchetype then + if not archetype then return EmptyQuery end @@ -657,17 +659,17 @@ local function PreparedQuery(compatibleArchetypes: { CompatibleArchetype } , com local i = 1 + local length = #compatibleArchetypes + local function queryNext(): ...any - local archetype = compatibleArchetype.archetype local entityId = archetype.entities[i] while entityId == nil do lastArchetype += 1 - if lastArchetype > #compatibleArchetypes then + archetype = compatibleArchetypes[lastArchetype] + if lastArchetype > length then return end - compatibleArchetype = compatibleArchetypes[lastArchetype] - archetype = compatibleArchetype.archetype i = 1 entityId = archetype.entities[i] end @@ -676,7 +678,7 @@ local function PreparedQuery(compatibleArchetypes: { CompatibleArchetype } , com i+=1 local columns = archetype.columns - local tr = compatibleArchetype.indices + local tr = indices[lastArchetype] if queryLength == 1 then return entityId, columns[tr[1]][row] @@ -740,7 +742,7 @@ local function PreparedQuery(compatibleArchetypes: { CompatibleArchetype } , com function preparedQuery:without(...: any): Query local withoutComponents = { ... } for i = #compatibleArchetypes, 1, -1 do - local archetype = compatibleArchetypes[i].archetype + local archetype = compatibleArchetypes[i] local records = archetype.records local shouldRemove = false @@ -756,7 +758,7 @@ local function PreparedQuery(compatibleArchetypes: { CompatibleArchetype } , com end end - lastArchetype, compatibleArchetype = next(compatibleArchetypes) + lastArchetype, archetype = next(compatibleArchetypes) if not lastArchetype then return EmptyQuery end @@ -773,6 +775,7 @@ function World.query(world: World, ...: any): Query error("Missing components") end + local columns = {} local compatibleArchetypes: { CompatibleArchetype } = {} local length = 0 @@ -797,7 +800,7 @@ function World.query(world: World, ...: any): Query local archetype = archetypes[id] local archetypeRecords = archetype.records - local indices = {} + local records = {} local skip = false for i, componentId in components do @@ -807,7 +810,7 @@ function World.query(world: World, ...: any): Query break end -- index should be index.offset - indices[i] = index + records[i] = index end if skip then @@ -815,13 +818,11 @@ function World.query(world: World, ...: any): Query end length += 1 - compatibleArchetypes[length] = { - archetype = archetype, - indices = indices, - } + compatibleArchetypes[length] = archetype + columns[length] = records end - return PreparedQuery(compatibleArchetypes, components) + return PreparedQuery(compatibleArchetypes, components, columns) end type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53) From 0ff2348a6ed528a9f745dfbbb2f36b53cff7c543 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Wed, 26 Jun 2024 15:50:00 +0200 Subject: [PATCH 4/6] Uniform function declarations --- lib/init.luau | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/init.luau b/lib/init.luau index 479cce6..f49a6d6 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -652,22 +652,18 @@ local function PreparedQuery( return EmptyQuery end - local preparedQuery = {} - preparedQuery.__index = preparedQuery local queryOutput = {} local i = 1 - local length = #compatibleArchetypes - local function queryNext(): ...any local entityId = archetype.entities[i] while entityId == nil do lastArchetype += 1 archetype = compatibleArchetypes[lastArchetype] - if lastArchetype > length then + if not archetype then return end i = 1 @@ -730,16 +726,8 @@ local function PreparedQuery( return entityId, unpack(queryOutput, 1, queryLength) end - - function preparedQuery:__iter(): () -> ...any - return queryNext - end - function preparedQuery:next(): ...any - return queryNext() - end - - function preparedQuery:without(...: any): Query + local function without(self, ...): Query local withoutComponents = { ... } for i = #compatibleArchetypes, 1, -1 do local archetype = compatibleArchetypes[i] @@ -765,8 +753,16 @@ local function PreparedQuery( return self end + + local preparedQuery = { + __iter = function() + return queryNext + end, + next = queryNext, + without = without + } - return setmetatable({}, preparedQuery) :: any + return (setmetatable(preparedQuery, preparedQuery) :: any):: Query end function World.query(world: World, ...: any): Query From 0256c765a0666e262c2164a28de32ae9b34de6b4 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 2 Jul 2024 12:53:33 +0200 Subject: [PATCH 5/6] Drainless iterators --- lib/init.luau | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/init.luau b/lib/init.luau index f49a6d6..110cc54 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -640,8 +640,8 @@ export type Query = typeof(EmptyQuery) type CompatibleArchetype = { archetype: Archetype, indices: { number } } -local function PreparedQuery( - compatibleArchetypes: { Archetype } , components: { i53 }, indices: { [number]: number }) +local function preparedQuery(compatibleArchetypes: { Archetype }, + components: { i53? }, indices: { { number } }) local queryLength = #components @@ -652,7 +652,6 @@ local function PreparedQuery( return EmptyQuery end - local queryOutput = {} local i = 1 @@ -746,48 +745,47 @@ local function PreparedQuery( end end - lastArchetype, archetype = next(compatibleArchetypes) - if not lastArchetype then - return EmptyQuery - end - return self end - local preparedQuery = { + local query = { __iter = function() + i = 1 + lastArchetype = 1 + archetype = compatibleArchetypes[1] + return queryNext end, next = queryNext, without = without } - return (setmetatable(preparedQuery, preparedQuery) :: any):: Query + return setmetatable(query, query) :: any end -function World.query(world: World, ...: any): Query +function World.query(world: World, ...: number?): Query -- breaking? if (...) == nil then error("Missing components") end - local columns = {} - local compatibleArchetypes: { CompatibleArchetype } = {} + local indices: { { number } } = {} + local compatibleArchetypes: { Archetype } = {} local length = 0 - local components = { ... } :: any - local archetypes = world.archetypes + local components: { number? } = { ... } + local archetypes: { Archetype } = world.archetypes :: any local firstArchetypeMap: ArchetypeMap local componentIndex = world.componentIndex for _, componentId in components do - local map = componentIndex[componentId] + local map: ArchetypeMap = componentIndex[componentId] :: any if not map then return EmptyQuery end - if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then + if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size < map.size then firstArchetypeMap = map end end @@ -796,7 +794,7 @@ function World.query(world: World, ...: any): Query local archetype = archetypes[id] local archetypeRecords = archetype.records - local records = {} + local records: { number } = {} local skip = false for i, componentId in components do @@ -815,25 +813,27 @@ function World.query(world: World, ...: any): Query length += 1 compatibleArchetypes[length] = archetype - columns[length] = records + indices[length] = records end - return PreparedQuery(compatibleArchetypes, components, columns) + return preparedQuery(compatibleArchetypes, components, indices) end type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53) function World.__iter(world: World): WorldIterator - local dense = world.entityIndex.dense - local sparse = world.entityIndex.sparse + local entityIndex = world.entityIndex + local dense = entityIndex.dense + local sparse = entityIndex.sparse local last -- new solver doesnt like the world iterator type even tho its correct -- so any cast here i come + local i = 0 local function iterator() - local lastEntity: number?, entityId: number = next(dense, last) - if not lastEntity then - -- ignore type error + i+=1 + local entityId = dense[i] + if not entityId then return end From 0f67cb1c863c74684d0a82603836d96db9fbd2a3 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 2 Jul 2024 14:20:35 +0200 Subject: [PATCH 6/6] tuple isnt optional --- lib/init.luau | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/init.luau b/lib/init.luau index 110cc54..1242cba 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -763,7 +763,7 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, return setmetatable(query, query) :: any end -function World.query(world: World, ...: number?): Query +function World.query(world: World, ...: number): Query -- breaking? if (...) == nil then error("Missing components") @@ -773,7 +773,7 @@ function World.query(world: World, ...: number?): Query local compatibleArchetypes: { Archetype } = {} local length = 0 - local components: { number? } = { ... } + local components: { number } = { ... } local archetypes: { Archetype } = world.archetypes :: any local firstArchetypeMap: ArchetypeMap