Merge branch 'main' of https://github.com/Ukendio/jecs into add-relations

This commit is contained in:
Ukendio 2024-05-05 02:00:38 +02:00
commit 93165e0ad9
9 changed files with 496 additions and 469 deletions

3
.gitignore vendored
View file

@ -50,3 +50,6 @@ WallyPatches
roblox.toml roblox.toml
sourcemap.json sourcemap.json
drafts/*.lua drafts/*.lua
*.code-workspace
roblox.yml

View file

@ -1,7 +1,7 @@
--!optimize 2 --!optimize 2
--!native --!native
local testkit = require('../testkit') local testkit = require("../testkit")
local BENCH, START = testkit.benchmark() local BENCH, START = testkit.benchmark()
local function TITLE(title: string) local function TITLE(title: string)
print() print()
@ -15,21 +15,21 @@ local oldMatter = require("../oldMatter")
local newMatter = require("../newMatter") local newMatter = require("../newMatter")
type i53 = number type i53 = number
do TITLE (testkit.color.white_underline("Jecs query")) do
TITLE(testkit.color.white_underline("Jecs query"))
local ecs = jecs.World.new() local ecs = jecs.World.new()
do TITLE "one component in common" do
TITLE("one component in common")
local function view_bench(
world: jecs.World,
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53
)
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
BENCH("1 component", function() BENCH("1 component", function()
for _ in world:query(A) do end for _ in world:query(A) do
end
end) end)
BENCH("2 component", function() BENCH("2 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B) do
end
end) end)
BENCH("4 component", function() BENCH("4 component", function()
@ -38,7 +38,8 @@ do TITLE (testkit.color.white_underline("Jecs query"))
end) end)
BENCH("8 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D, E, F, G, H) do end for _ in world:query(A, B, C, D, E, F, G, H) do
end
end) end)
end end
@ -89,7 +90,6 @@ do TITLE (testkit.color.white_underline("Jecs query"))
if flip() then if flip() then
combination ..= "H" combination ..= "H"
ecs:set(entity, D8, {value = true}) ecs:set(entity, D8, {value = true})
end end
if #combination == 7 then if #combination == 7 then
@ -100,29 +100,31 @@ do TITLE (testkit.color.white_underline("Jecs query"))
end end
local a = 0 local a = 0
for _ in archetypes do a+= 1 end for _ in archetypes do
a += 1
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
end end
end end
do TITLE(testkit.color.white_underline("OldMatter query")) do
TITLE(testkit.color.white_underline("OldMatter query"))
local ecs = oldMatter.World.new() local ecs = oldMatter.World.new()
local component = oldMatter.component local component = oldMatter.component
do TITLE "one component in common" do
local function view_bench( TITLE("one component in common")
world: jecs.World, local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53
)
BENCH("1 component", function() BENCH("1 component", function()
for _ in world:query(A) do end for _ in world:query(A) do
end
end) end)
BENCH("2 component", function() BENCH("2 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B) do
end
end) end)
BENCH("4 component", function() BENCH("4 component", function()
@ -131,7 +133,8 @@ do TITLE(testkit.color.white_underline("OldMatter query"))
end) end)
BENCH("8 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D, E, F, G, H) do end for _ in world:query(A, B, C, D, E, F, G, H) do
end
end) end)
end end
@ -174,12 +177,10 @@ do TITLE(testkit.color.white_underline("OldMatter query"))
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:insert(entity, D6({value = true})) ecs:insert(entity, D6({value = true}))
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:insert(entity, D7({value = true})) ecs:insert(entity, D7({value = true}))
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
@ -189,36 +190,36 @@ do TITLE(testkit.color.white_underline("OldMatter query"))
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:insert(entity, D1({value = true})) ecs:insert(entity, D1({value = true}))
end end
archetypes[combination] = true archetypes[combination] = true
end end
local a = 0 local a = 0
for _ in archetypes do a+= 1 end for _ in archetypes do
a += 1
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
end end
end end
do TITLE(testkit.color.white_underline("NewMatter query")) do
TITLE(testkit.color.white_underline("NewMatter query"))
local ecs = newMatter.World.new() local ecs = newMatter.World.new()
local component = newMatter.component local component = newMatter.component
do TITLE "one component in common" do
local function view_bench( TITLE("one component in common")
world: jecs.World, local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53
)
BENCH("1 component", function() BENCH("1 component", function()
for _ in world:query(A) do end for _ in world:query(A) do
end
end) end)
BENCH("2 component", function() BENCH("2 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B) do
end
end) end)
BENCH("4 component", function() BENCH("4 component", function()
@ -227,7 +228,8 @@ do TITLE(testkit.color.white_underline("NewMatter query"))
end) end)
BENCH("8 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D, E, F, G, H) do end for _ in world:query(A, B, C, D, E, F, G, H) do
end
end) end)
end end
@ -270,12 +272,10 @@ do TITLE(testkit.color.white_underline("NewMatter query"))
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:insert(entity, D6({value = true})) ecs:insert(entity, D6({value = true}))
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:insert(entity, D7({value = true})) ecs:insert(entity, D7({value = true}))
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
@ -285,15 +285,15 @@ do TITLE(testkit.color.white_underline("NewMatter query"))
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:insert(entity, D1({value = true})) ecs:insert(entity, D1({value = true}))
end end
archetypes[combination] = true archetypes[combination] = true
end end
local a = 0 local a = 0
for _ in archetypes do a+= 1 end for _ in archetypes do
a += 1
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
end end
end end

View file

@ -2,41 +2,37 @@
--!native --!native
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local rgb = require(ReplicatedStorage.rgb)
local Matter = require(ReplicatedStorage.DevPackages.Matter) local Matter = require(ReplicatedStorage.DevPackages.Matter)
local jecs = require(ReplicatedStorage.Lib)
local ecr = require(ReplicatedStorage.DevPackages.ecr) local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib)
local rgb = require(ReplicatedStorage.rgb)
local newWorld = Matter.World.new() local newWorld = Matter.World.new()
local ecs = jecs.World.new() local ecs = jecs.World.new()
return { return {
ParameterGenerator = function() ParameterGenerator = function()
local registry2 = ecr.registry() local registry2 = ecr.registry()
return registry2 return registry2
end, end;
Functions = { Functions = {
Matter = function() Matter = function()
for i = 1, 1000 do for i = 1, 1000 do
newWorld:spawn() newWorld:spawn()
end end
end, end;
ECR = function(_, registry2) ECR = function(_, registry2)
for i = 1, 1000 do for i = 1, 1000 do
registry2.create() registry2.create()
end end
end, end;
Jecs = function() Jecs = function()
for i = 1, 1000 do for i = 1, 1000 do
ecs:entity() ecs:entity()
end end
end end;
};
},
} }

View file

@ -82,24 +82,27 @@ local function transitionArchetype(
end end
-- Move the entity from the source to the destination archetype. -- Move the entity from the source to the destination archetype.
destinationEntities[destinationRow] = sourceEntities[sourceRow] local atSourceRow = sourceEntities[sourceRow]
entityIndex[sourceEntities[sourceRow]].row = destinationRow destinationEntities[destinationRow] = atSourceRow
entityIndex[atSourceRow].row = destinationRow
-- Because we have swapped columns we now have to update the records -- Because we have swapped columns we now have to update the records
-- corresponding to the entities' rows that were swapped. -- corresponding to the entities' rows that were swapped.
local movedAway = #sourceEntities local movedAway = #sourceEntities
if sourceRow ~= movedAway then if sourceRow ~= movedAway then
sourceEntities[sourceRow] = sourceEntities[movedAway] local atMovedAway = sourceEntities[movedAway]
entityIndex[sourceEntities[movedAway]].row = sourceRow sourceEntities[sourceRow] = atMovedAway
entityIndex[atMovedAway].row = sourceRow
end end
sourceEntities[movedAway] = nil sourceEntities[movedAway] = nil
end end
local function archetypeAppend(entity: i53, archetype: Archetype): i24 local function archetypeAppend(entity: number, archetype: Archetype): number
local entities = archetype.entities local entities = archetype.entities
table.insert(entities, entity) local length = #entities + 1
return #entities entities[length] = entity
return length
end end
local function newEntity(entityId: i53, record: Record, archetype: Archetype) local function newEntity(entityId: i53, record: Record, archetype: Archetype)
@ -122,47 +125,49 @@ local function hash(arr): string | number
return table.concat(arr, "_") return table.concat(arr, "_")
end end
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?) local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?)
local destinationCount = #to.types
local destinationIds = to.types local destinationIds = to.types
local records = to.records
local id = to.id
for i = 1, destinationCount do for i, destinationId in destinationIds do
local destinationId = destinationIds[i] local archetypesMap = componentIndex[destinationId]
if not componentIndex[destinationId] then if not archetypesMap then
componentIndex[destinationId] = { size = 0, sparse = {} } archetypesMap = {size = 0, sparse = {}}
componentIndex[destinationId] = archetypesMap
end end
local archetypesMap = componentIndex[destinationId] archetypesMap.sparse[id] = i
archetypesMap.sparse[to.id] = i records[destinationId] = i
to.records[destinationId] = i
end end
end end
local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype
local ty = hash(types) local ty = hash(types)
world.nextArchetypeId = (world.nextArchetypeId::number)+ 1 local id = world.nextArchetypeId + 1
local id = world.nextArchetypeId world.nextArchetypeId = id
local columns = {} :: { any } local length = #types
local columns = table.create(length) :: {any}
for _ in types do for index in types do
table.insert(columns, {}) columns[index] = {}
end end
local archetype = { local archetype = {
id = id, columns = columns;
types = types, edges = {};
type = ty, entities = {};
columns = columns, id = id;
entities = {}, records = {};
edges = {}, type = ty;
records = {}, types = types;
} }
world.archetypeIndex[ty] = archetype world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype world.archetypes[id] = archetype
if #types > 0 then if length > 0 then
createArchetypeRecords(world.componentIndex, archetype, prev) createArchetypeRecords(world.componentIndex, archetype, prev)
end end
@ -173,17 +178,17 @@ local World = {}
World.__index = World World.__index = World
function World.new() function World.new()
local self = setmetatable({ local self = setmetatable({
entityIndex = {}, archetypeIndex = {};
componentIndex = {}, archetypes = {};
archetypes = {}, componentIndex = {};
archetypeIndex = {}, entityIndex = {};
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
nextEntityId = 0,
nextComponentId = 0,
nextArchetypeId = 0,
hooks = { hooks = {
[ON_ADD] = {} [ON_ADD] = {};
} };
nextArchetypeId = 0;
nextComponentId = 0;
nextEntityId = 0;
ROOT_ARCHETYPE = (nil :: any) :: Archetype;
}, World) }, World)
return self return self
end end
@ -192,21 +197,21 @@ local function emit(world, eventDescription)
local event = eventDescription.event local event = eventDescription.event
table.insert(world.hooks[event], { table.insert(world.hooks[event], {
ids = eventDescription.ids, archetype = eventDescription.archetype;
archetype = eventDescription.archetype, ids = eventDescription.ids;
otherArchetype = eventDescription.otherArchetype, offset = eventDescription.offset;
offset = eventDescription.offset otherArchetype = eventDescription.otherArchetype;
}) })
end end
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty) local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
if #added > 0 then if #added > 0 then
emit(world, { emit(world, {
event = ON_ADD, archetype = archetype;
ids = added, event = ON_ADD;
archetype = archetype, ids = added;
otherArchetype = otherArchetype, offset = row;
offset = row, otherArchetype = otherArchetype;
}) })
end end
end end
@ -228,9 +233,7 @@ local function ensureArchetype(world: World, types, prev)
end end
local function findInsert(types: {i53}, toAdd: i53) local function findInsert(types: {i53}, toAdd: i53)
local count = #types for i, id in types do
for i = 1, count do
local id = types[i]
if id == toAdd then if id == toAdd then
return -1 return -1
end end
@ -238,7 +241,7 @@ local function findInsert(types: { i53 }, toAdd: i53)
return i return i
end end
end end
return count + 1 return #types + 1
end end
local function findArchetypeWith(world: World, node: Archetype, componentId: i53) local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
@ -259,38 +262,47 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
end end
local function ensureEdge(archetype: Archetype, componentId: i53) local function ensureEdge(archetype: Archetype, componentId: i53)
if not archetype.edges[componentId] then local edges = archetype.edges
archetype.edges[componentId] = {} :: any local edge = edges[componentId]
if not edge then
edge = {} :: any
edges[componentId] = edge
end end
return archetype.edges[componentId] return edge
end end
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
if not from then if not from then
-- If there was no source archetype then it should return the ROOT_ARCHETYPE -- If there was no source archetype then it should return the ROOT_ARCHETYPE
if not world.ROOT_ARCHETYPE then local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil) if not ROOT_ARCHETYPE then
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never
end end
from = world.ROOT_ARCHETYPE from = ROOT_ARCHETYPE
end end
local edge = ensureEdge(from, componentId)
if not edge.add then local edge = ensureEdge(from, componentId)
local add = edge.add
if not add then
-- Save an edge using the component ID to the archetype to allow -- Save an edge using the component ID to the archetype to allow
-- faster traversals to adjacent archetypes. -- faster traversals to adjacent archetypes.
edge.add = findArchetypeWith(world, from, componentId) add = findArchetypeWith(world, from, componentId)
edge.add = add :: never
end end
return edge.add return add
end end
local function ensureRecord(entityIndex, entityId: i53): Record local function ensureRecord(entityIndex, entityId: i53): Record
local id = entityId local record = entityIndex[entityId]
if not entityIndex[id] then
entityIndex[id] = {} if not record then
record = {}
entityIndex[entityId] = record
end end
return entityIndex[id] :: Record
return record :: Record
end end
function World.set(world: World, entityId: i53, componentId: i53, data: unknown) function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
@ -326,28 +338,30 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
local edge = ensureEdge(from, componentId) local edge = ensureEdge(from, componentId)
local remove = edge.remove
if not edge.remove then if not remove then
local to = table.clone(from.types) local to = table.clone(from.types)
table.remove(to, table.find(to, componentId)) table.remove(to, table.find(to, componentId))
edge.remove = ensureArchetype(world, to, from) remove = ensureArchetype(world, to, from)
edge.remove = remove :: never
end end
return edge.remove return remove
end end
function World.remove(world: World, entityId: i53, componentId: i53) function World.remove(world: World, entityId: i53, componentId: i53)
local record = ensureRecord(world.entityIndex, entityId) local entityIndex = world.entityIndex
local record = ensureRecord(entityIndex, entityId)
local sourceArchetype = record.archetype local sourceArchetype = record.archetype
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
if sourceArchetype and not (sourceArchetype == destinationArchetype) then if sourceArchetype and not (sourceArchetype == destinationArchetype) then
moveEntity(world.entityIndex, entityId, record, destinationArchetype) moveEntity(entityIndex, entityId, record, destinationArchetype)
end end
end end
-- Keeping the function as small as possible to enable inlining -- Keeping the function as small as possible to enable inlining
local function get(componentIndex: { [i24]: ArchetypeMap }, record: Record, componentId: i24) local function get(record: Record, componentId: i24)
local archetype = record.archetype local archetype = record.archetype
local archetypeRecord = archetype.records[componentId] local archetypeRecord = archetype.records[componentId]
@ -360,35 +374,35 @@ end
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
local id = entityId local id = entityId
local componentIndex = world.componentIndex
local record = world.entityIndex[id] local record = world.entityIndex[id]
if not record then if not record then
return nil return nil
end end
local va = get(componentIndex, record, a) local va = get(record, a)
if b == nil then if b == nil then
return va return va
elseif c == nil then elseif c == nil then
return va, get(componentIndex, record, b) return va, get(record, b)
elseif d == nil then elseif d == nil then
return va, get(componentIndex, record, b), get(componentIndex, record, c) return va, get(record, b), get(record, c)
elseif e == nil then elseif e == nil then
return va, get(componentIndex, record, b), get(componentIndex, record, c), get(componentIndex, record, d) return va, get(record, b), get(record, c), get(record, d)
else else
error("args exceeded") error("args exceeded")
end end
end end
local function noop(self: Query, ...: i53): () -> (number, ...any) -- the less creation the better
return function() local function actualNoOperation() end
end :: any local function noop(_self: Query, ...: i53): () -> (number, ...any)
return actualNoOperation :: any
end end
local EmptyQuery = { local EmptyQuery = {
__iter = noop, __iter = noop;
without = noop without = noop;
} }
EmptyQuery.__index = EmptyQuery EmptyQuery.__index = EmptyQuery
setmetatable(EmptyQuery, EmptyQuery) setmetatable(EmptyQuery, EmptyQuery)
@ -396,19 +410,22 @@ setmetatable(EmptyQuery, EmptyQuery)
export type Query = typeof(EmptyQuery) export type Query = typeof(EmptyQuery)
function World.query(world: World, ...: i53): Query function World.query(world: World, ...: i53): Query
-- breaking?
if (...) == nil then
error("Missing components")
end
local compatibleArchetypes = {} local compatibleArchetypes = {}
local length = 0
local components = {...} local components = {...}
local archetypes = world.archetypes local archetypes = world.archetypes
local queryLength = #components local queryLength = #components
if queryLength == 0 then
error("Missing components")
end
local firstArchetypeMap local firstArchetypeMap
local componentIndex = world.componentIndex local componentIndex = world.componentIndex
for i, componentId in components do for _, componentId in components do
local map = componentIndex[componentId] local map = componentIndex[componentId]
if not map then if not map then
return EmptyQuery return EmptyQuery
@ -431,13 +448,15 @@ function World.query(world: World, ...: i53): Query
skip = true skip = true
break break
end end
indices[i] = archetypeRecords[componentId] indices[i] = index
end end
if skip then if skip then
continue continue
end end
table.insert(compatibleArchetypes, { archetype, indices })
length += 1
compatibleArchetypes[length] = {archetype, indices}
end end
local lastArchetype, compatibleArchetype = next(compatibleArchetypes) local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
@ -449,16 +468,19 @@ function World.query(world: World, ...: i53): Query
preparedQuery.__index = preparedQuery preparedQuery.__index = preparedQuery
function preparedQuery:without(...) function preparedQuery:without(...)
local components = { ... } local withoutComponents = {...}
for i = #compatibleArchetypes, 1, -1 do for i = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i][1] local archetype = compatibleArchetypes[i][1]
local records = archetype.records
local shouldRemove = false local shouldRemove = false
for _, componentId in components do
if archetype.records[componentId] then for _, componentId in withoutComponents do
if records[componentId] then
shouldRemove = true shouldRemove = true
break break
end end
end end
if shouldRemove then if shouldRemove then
table.remove(compatibleArchetypes, i) table.remove(compatibleArchetypes, i)
end end
@ -475,7 +497,6 @@ function World.query(world: World, ...: i53): Query
local lastRow local lastRow
local queryOutput = {} local queryOutput = {}
function preparedQuery:__iter() function preparedQuery:__iter()
return function() return function()
local archetype = compatibleArchetype[1] local archetype = compatibleArchetype[1]
@ -499,16 +520,9 @@ function World.query(world: World, ...: i53): Query
elseif queryLength == 2 then elseif queryLength == 2 then
return entityId, columns[tr[1]][row], columns[tr[2]][row] return entityId, columns[tr[1]][row], columns[tr[2]][row]
elseif queryLength == 3 then elseif queryLength == 3 then
return entityId, return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row]
elseif queryLength == 4 then elseif queryLength == 4 then
return entityId, return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
columns[tr[1]][row],
columns[tr[2]][row],
columns[tr[3]][row],
columns[tr[4]][row]
elseif queryLength == 5 then elseif queryLength == 5 then
return entityId, return entityId,
columns[tr[1]][row], columns[tr[1]][row],
@ -568,8 +582,9 @@ function World.component(world: World)
end end
function World.entity(world: World) function World.entity(world: World)
world.nextEntityId += 1 local nextEntityId = world.nextEntityId + 1
return world.nextEntityId + REST world.nextEntityId = nextEntityId
return nextEntityId + REST
end end
function World.delete(world: World, entityId: i53) function World.delete(world: World, entityId: i53)
@ -585,11 +600,13 @@ end
function World.observer(world: World, ...) function World.observer(world: World, ...)
local componentIds = {...} local componentIds = {...}
local idsCount = #componentIds
local hooks = world.hooks
return { return {
event = function(event) event = function(event)
local hook = world.hooks[event] local hook = hooks[event]
world.hooks[event] = nil hooks[event] = nil
local last, change local last, change
return function() return function()
@ -599,10 +616,11 @@ function World.observer(world: World, ...)
end end
local matched = false local matched = false
local ids = change.ids
while not matched do while not matched do
local skip = false local skip = false
for _, id in change.ids do for _, id in ids do
if not table.find(componentIds, id) then if not table.find(componentIds, id) then
skip = true skip = true
break break
@ -611,30 +629,31 @@ function World.observer(world: World, ...)
if skip then if skip then
last, change = next(hook, last) last, change = next(hook, last)
ids = change.ids
continue continue
end end
matched = true matched = true
end end
local queryOutput = {} local queryOutput = table.create(idsCount)
local row = change.offset local row = change.offset
local archetype = change.archetype local archetype = change.archetype
local columns = archetype.columns local columns = archetype.columns
local archetypeRecords = archetype.records local archetypeRecords = archetype.records
for _, id in componentIds do for index, id in componentIds do
table.insert(queryOutput, columns[archetypeRecords[id]][row]) queryOutput[index] = columns[archetypeRecords[id]][row]
end end
return archetype.entities[row], unpack(queryOutput, 1, #queryOutput) return archetype.entities[row], unpack(queryOutput, 1, idsCount)
end
end end
end;
} }
end end
return table.freeze({ return table.freeze({
World = World, World = World;
ON_ADD = ON_ADD, ON_ADD = ON_ADD;
ON_REMOVE = ON_REMOVE, ON_REMOVE = ON_REMOVE;
ON_SET = ON_SET ON_SET = ON_SET;
}) })

4
selene.toml Normal file
View file

@ -0,0 +1,4 @@
std = "roblox"
[lints]
global_usage = "allow"

5
stylua.toml Normal file
View file

@ -0,0 +1,5 @@
column_width = 120
quote_style = "ForceDouble"
[sort_requires]
enabled = true

3
testez-companion.toml Normal file
View file

@ -0,0 +1,3 @@
roots = ["ServerStorage"]
[extraOptions]

View file

@ -10,6 +10,3 @@ include = ["default.project.json", "lib", "wally.toml", "README.md"]
TestEZ = "roblox/testez@0.4.1" TestEZ = "roblox/testez@0.4.1"
Matter = "matter-ecs/matter@0.8.0" Matter = "matter-ecs/matter@0.8.0"
ecr = "centau/ecr@0.8.0" ecr = "centau/ecr@0.8.0"