This commit is contained in:
HowManySmall 2024-05-04 14:10:59 -06:00
parent cda04ce5a9
commit b3f8e2504e
8 changed files with 19491 additions and 178 deletions

2
.gitignore vendored
View file

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

73
lib/Types.luau Normal file
View file

@ -0,0 +1,73 @@
--!native
--!optimize 2
--!strict
export type i53 = number
export type i24 = number
export type Ty = {i53}
export type ArchetypeId = number
export type Column = {any}
export type Archetype = {
id: number,
edges: {
[i24]: {
add: Archetype,
remove: Archetype,
},
},
types: Ty,
type: string | number,
entities: {number},
columns: {Column},
records: {},
}
export type Record = {
archetype: Archetype,
row: number,
}
export type EntityIndex = {[i24]: Record}
export type ComponentIndex = {[i24]: ArchetypeMap}
export type ArchetypeRecord = number
export type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
export type Archetypes = {[ArchetypeId]: Archetype}
export type ArchetypeDiff = {
added: Ty,
removed: Ty,
}
export type Hook = {
ids: Ty,
archetype: Archetype,
otherArchetype: Archetype,
offset: number,
}
export type EventDescription = Hook & {
event: number,
}
export type World = {
archetypeIndex: {[number | string]: Archetype},
archetypes: {[number]: Archetype},
componentIndex: {[i24]: ArchetypeMap},
entityIndex: {[i53]: Record},
hooks: {[number]: {Hook}},
nextComponentId: number,
nextEntityId: number,
ROOT_ARCHETYPE: Archetype,
get: (self: World) -> (),
}
export type WorldStatic = {
new: () -> World,
}
return false

View file

@ -3,45 +3,29 @@
--!strict --!strict
--draft 4 --draft 4
type i53 = number local Types = require(script.Types)
type i24 = number
type Ty = { i53 } type i53 = Types.i53
type ArchetypeId = number type i24 = Types.i24
type Column = { any } type Ty = Types.Ty
type ArchetypeId = Types.ArchetypeId
type Archetype = { type Column = Types.Column
id: number,
edges: {
[i24]: {
add: Archetype,
remove: Archetype,
},
},
types: Ty,
type: string | number,
entities: { number },
columns: { Column },
records: {},
}
type Record = { type Archetype = Types.Archetype
archetype: Archetype, type Record = Types.Record
row: number,
}
type EntityIndex = { [i24]: Record } type EntityIndex = Types.EntityIndex
type ComponentIndex = { [i24]: ArchetypeMap} type ComponentIndex = Types.ComponentIndex
type ArchetypeRecord = number type ArchetypeRecord = Types.ArchetypeRecord
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord } , size: number } type ArchetypeMap = Types.ArchetypeMap
type Archetypes = { [ArchetypeId]: Archetype } type Archetypes = Types.Archetypes
type ArchetypeDiff = { type ArchetypeDiff = Types.ArchetypeDiff
added: Ty,
removed: Ty, -- type World = Types.World
}
local HI_COMPONENT_ID = 256 local HI_COMPONENT_ID = 256
local ON_ADD = HI_COMPONENT_ID + 1 local ON_ADD = HI_COMPONENT_ID + 1
@ -64,17 +48,17 @@ local function transitionArchetype(
local types = from.types local types = from.types
for i, column in columns do for i, column in columns do
-- Retrieves the new column index from the source archetype's record from each component -- Retrieves the new column index from the source archetype's record from each component
-- We have to do this because the columns are tightly packed and indexes may not correspond to each other. -- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
local targetColumn = destinationColumns[tr[types[i]]] local targetColumn = destinationColumns[tr[types[i]]]
-- Sometimes target column may not exist, e.g. when you remove a component. -- Sometimes target column may not exist, e.g. when you remove a component.
if targetColumn then if targetColumn then
targetColumn[destinationRow] = column[sourceRow] targetColumn[destinationRow] = column[sourceRow]
end end
-- If the entity is the last row in the archetype then swapping it would be meaningless. -- If the entity is the last row in the archetype then swapping it would be meaningless.
local last = #column local last = #column
if sourceRow ~= last then if sourceRow ~= last then
-- Swap rempves columns to ensure there are no holes in the archetype. -- Swap rempves columns to ensure there are no holes in the archetype.
column[sourceRow] = column[last] column[sourceRow] = column[last]
end end
@ -88,11 +72,11 @@ local function transitionArchetype(
-- 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] sourceEntities[sourceRow] = sourceEntities[movedAway]
entityIndex[sourceEntities[movedAway]].row = sourceRow entityIndex[sourceEntities[movedAway]].row = sourceRow
end end
sourceEntities[movedAway] = nil sourceEntities[movedAway] = nil
end end
@ -130,7 +114,7 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
local destinationId = destinationIds[i] local destinationId = destinationIds[i]
if not componentIndex[destinationId] then if not componentIndex[destinationId] then
componentIndex[destinationId] = { size = 0, sparse = {} } componentIndex[destinationId] = {size = 0, sparse = {}}
end end
local archetypesMap = componentIndex[destinationId] local archetypesMap = componentIndex[destinationId]
@ -139,30 +123,30 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
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 world.nextArchetypeId = (world.nextArchetypeId :: number) + 1
local id = world.nextArchetypeId local id = world.nextArchetypeId
local columns = {} :: { any } local columns = {} :: {any}
for _ in types do for _ in types do
table.insert(columns, {}) table.insert(columns, {})
end end
local archetype = { local archetype = {
id = id, id = id;
types = types, types = types;
type = ty, type = ty;
columns = columns, columns = columns;
entities = {}, entities = {};
edges = {}, edges = {};
records = {}, records = {};
} }
world.archetypeIndex[ty] = archetype world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype world.archetypes[id] = archetype
if #types > 0 then if #types > 0 then
createArchetypeRecords(world.componentIndex, archetype, prev) createArchetypeRecords(world.componentIndex, archetype, prev)
end end
@ -171,42 +155,42 @@ end
local World = {} local World = {}
World.__index = World World.__index = World
function World.new() function World.new()
local self = setmetatable({ local self = setmetatable({
entityIndex = {}, entityIndex = {};
componentIndex = {}, componentIndex = {};
archetypes = {}, archetypes = {};
archetypeIndex = {}, archetypeIndex = {};
ROOT_ARCHETYPE = (nil :: any) :: Archetype, ROOT_ARCHETYPE = (nil :: any) :: Archetype;
nextEntityId = 0, nextEntityId = 0;
nextComponentId = 0, nextComponentId = 0;
nextArchetypeId = 0, nextArchetypeId = 0;
hooks = { hooks = {
[ON_ADD] = {} [ON_ADD] = {};
} };
}, World) }, World)
return self return self
end end
local function emit(world, eventDescription) 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, ids = eventDescription.ids;
archetype = eventDescription.archetype, archetype = eventDescription.archetype;
otherArchetype = eventDescription.otherArchetype, otherArchetype = eventDescription.otherArchetype;
offset = eventDescription.offset offset = eventDescription.offset;
}) })
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, event = ON_ADD;
ids = added, ids = added;
archetype = archetype, archetype = archetype;
otherArchetype = otherArchetype, otherArchetype = otherArchetype;
offset = row, offset = row;
}) })
end end
end end
@ -217,7 +201,7 @@ local function ensureArchetype(world: World, types, prev)
if #types < 1 then if #types < 1 then
return world.ROOT_ARCHETYPE return world.ROOT_ARCHETYPE
end end
local ty = hash(types) local ty = hash(types)
local archetype = world.archetypeIndex[ty] local archetype = world.archetypeIndex[ty]
if archetype then if archetype then
@ -227,7 +211,7 @@ local function ensureArchetype(world: World, types, prev)
return archetypeOf(world, types, prev) return archetypeOf(world, types, prev)
end end
local function findInsert(types: { i53 }, toAdd: i53) local function findInsert(types: {i53}, toAdd: i53)
local count = #types local count = #types
for i = 1, count do for i = 1, count do
local id = types[i] local id = types[i]
@ -244,7 +228,7 @@ end
local function findArchetypeWith(world: World, node: Archetype, componentId: i53) local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
local types = node.types local types = node.types
-- Component IDs are added incrementally, so inserting and sorting -- Component IDs are added incrementally, so inserting and sorting
-- them each time would be expensive. Instead this insertion sort can find the insertion -- them each time would be expensive. Instead this insertion sort can find the insertion
-- point in the types array. -- point in the types array.
local at = findInsert(types, componentId) local at = findInsert(types, componentId)
if at == -1 then if at == -1 then
@ -266,18 +250,18 @@ local function ensureEdge(archetype: Archetype, componentId: i53)
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 if not world.ROOT_ARCHETYPE then
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil) local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
end end
from = world.ROOT_ARCHETYPE from = world.ROOT_ARCHETYPE
end end
local edge = ensureEdge(from, componentId) local edge = ensureEdge(from, componentId)
if not edge.add then if not edge.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) edge.add = findArchetypeWith(world, from, componentId)
end end
@ -293,14 +277,14 @@ local function ensureRecord(entityIndex, entityId: i53): Record
return entityIndex[id] :: Record return entityIndex[id] :: 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)
local record = ensureRecord(world.entityIndex, entityId) local record = ensureRecord(world.entityIndex, entityId)
local from = record.archetype local from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from) local to = archetypeTraverseAdd(world, componentId, from)
if from == to then if from == to then
-- If the archetypes are the same it can avoid moving the entity -- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly. -- and just set the data directly.
local archetypeRecord = to.records[componentId] local archetypeRecord = to.records[componentId]
from.columns[archetypeRecord][record.row] = data from.columns[archetypeRecord][record.row] = data
-- Should fire an OnSet event here. -- Should fire an OnSet event here.
@ -308,13 +292,13 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
end end
if from then if from then
-- If there was a previous archetype, then the entity needs to move the archetype -- If there was a previous archetype, then the entity needs to move the archetype
moveEntity(world.entityIndex, entityId, record, to) moveEntity(world.entityIndex, entityId, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
-- When there is no previous archetype it should create the archetype -- When there is no previous archetype it should create the archetype
newEntity(entityId, record, to) newEntity(entityId, record, to)
onNotifyAdd(world, to, from, record.row, { componentId }) onNotifyAdd(world, to, from, record.row, {componentId})
end end
end end
@ -326,9 +310,8 @@ 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)
if not edge.remove then if not edge.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) edge.remove = ensureArchetype(world, to, from)
end end
@ -336,18 +319,18 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype
return edge.remove return edge.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 record = ensureRecord(world.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(world.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(componentIndex: {[i24]: ArchetypeMap}, record: Record, componentId: i24): number?
local archetype = record.archetype local archetype = record.archetype
local archetypeRecord = archetype.records[componentId] local archetypeRecord = archetype.records[componentId]
@ -360,7 +343,7 @@ 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 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
@ -382,13 +365,12 @@ function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53
end end
local function noop(self: Query, ...: i53): () -> (number, ...any) local function noop(self: Query, ...: i53): () -> (number, ...any)
return function() return function() end :: any
end :: 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)
@ -397,24 +379,24 @@ export type Query = typeof(EmptyQuery)
function World.query(world: World, ...: i53): Query function World.query(world: World, ...: i53): Query
local compatibleArchetypes = {} local compatibleArchetypes = {}
local components = { ... } local components = {...}
local archetypes = world.archetypes local archetypes = world.archetypes
local queryLength = #components local queryLength = #components
if queryLength == 0 then if queryLength == 0 then
error("Missing components") error("Missing components")
end end
local firstArchetypeMap local firstArchetypeMap
local componentIndex = world.componentIndex local componentIndex = world.componentIndex
for i, componentId in components do for i, componentId in components do
local map = componentIndex[componentId] local map = componentIndex[componentId]
if not map then if not map then
return EmptyQuery return EmptyQuery
end end
if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then
firstArchetypeMap = map firstArchetypeMap = map
end end
end end
@ -422,110 +404,102 @@ function World.query(world: World, ...: i53): Query
for id in firstArchetypeMap.sparse do for id in firstArchetypeMap.sparse do
local archetype = archetypes[id] local archetype = archetypes[id]
local archetypeRecords = archetype.records local archetypeRecords = archetype.records
local indices = {} local indices = {}
local skip = false local skip = false
for i, componentId in components do for i, componentId in components do
local index = archetypeRecords[componentId] local index = archetypeRecords[componentId]
if not index then if not index then
skip = true skip = true
break break
end end
indices[i] = archetypeRecords[componentId] indices[i] = archetypeRecords[componentId]
end end
if skip then if skip then
continue continue
end end
table.insert(compatibleArchetypes, { archetype, indices }) table.insert(compatibleArchetypes, {archetype, indices})
end end
local lastArchetype, compatibleArchetype = next(compatibleArchetypes) local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
if not lastArchetype then if not lastArchetype then
return EmptyQuery return EmptyQuery
end end
local preparedQuery = {} local preparedQuery = {}
preparedQuery.__index = preparedQuery preparedQuery.__index = preparedQuery
function preparedQuery:without(...) function preparedQuery:without(...)
local components = { ... } local components = {...}
for i = #compatibleArchetypes, 1, -1 do for i = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i][1] local archetype = compatibleArchetypes[i][1]
local shouldRemove = false local shouldRemove = false
for _, componentId in components do for _, componentId in components do
if archetype.records[componentId] then if archetype.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
end end
lastArchetype, compatibleArchetype = next(compatibleArchetypes) lastArchetype, compatibleArchetype = next(compatibleArchetypes)
if not lastArchetype then if not lastArchetype then
return EmptyQuery return EmptyQuery
end end
return self return self
end end
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]
local row = next(archetype.entities, lastRow) local row = next(archetype.entities, lastRow)
while row == nil do while row == nil do
lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype)
if lastArchetype == nil then if lastArchetype == nil then
return return
end end
archetype = compatibleArchetype[1] archetype = compatibleArchetype[1]
row = next(archetype.entities, row) row = next(archetype.entities, row)
end end
lastRow = row lastRow = row
local entityId = archetype.entities[row :: number] local entityId = archetype.entities[row :: number]
local columns = archetype.columns local columns = archetype.columns
local tr = compatibleArchetype[2] local tr = compatibleArchetype[2]
if queryLength == 1 then if queryLength == 1 then
return entityId, columns[tr[1]][row] return entityId, columns[tr[1]][row]
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], elseif queryLength == 4 then
columns[tr[2]][row], return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
columns[tr[3]][row] elseif queryLength == 5 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]
elseif queryLength == 5 then
return entityId,
columns[tr[1]][row], columns[tr[1]][row],
columns[tr[2]][row], columns[tr[2]][row],
columns[tr[3]][row], columns[tr[3]][row],
columns[tr[4]][row], columns[tr[4]][row],
columns[tr[5]][row] columns[tr[5]][row]
elseif queryLength == 6 then elseif queryLength == 6 then
return entityId, return entityId,
columns[tr[1]][row], columns[tr[1]][row],
columns[tr[2]][row], columns[tr[2]][row],
columns[tr[3]][row], columns[tr[3]][row],
columns[tr[4]][row], columns[tr[4]][row],
columns[tr[5]][row], columns[tr[5]][row],
columns[tr[6]][row] columns[tr[6]][row]
elseif queryLength == 7 then elseif queryLength == 7 then
return entityId, return entityId,
columns[tr[1]][row], columns[tr[1]][row],
columns[tr[2]][row], columns[tr[2]][row],
columns[tr[3]][row], columns[tr[3]][row],
@ -533,8 +507,8 @@ function World.query(world: World, ...: i53): Query
columns[tr[5]][row], columns[tr[5]][row],
columns[tr[6]][row], columns[tr[6]][row],
columns[tr[7]][row] columns[tr[7]][row]
elseif queryLength == 8 then elseif queryLength == 8 then
return entityId, return entityId,
columns[tr[1]][row], columns[tr[1]][row],
columns[tr[2]][row], columns[tr[2]][row],
columns[tr[3]][row], columns[tr[3]][row],
@ -545,7 +519,7 @@ function World.query(world: World, ...: i53): Query
columns[tr[8]][row] columns[tr[8]][row]
end end
for i in components do for i in components do
queryOutput[i] = tr[i][row] queryOutput[i] = tr[i][row]
end end
@ -556,12 +530,12 @@ function World.query(world: World, ...: i53): Query
return setmetatable({}, preparedQuery) :: any return setmetatable({}, preparedQuery) :: any
end end
function World.component(world: World) function World.component(world: World)
local componentId = world.nextComponentId + 1 local componentId = world.nextComponentId + 1
if componentId > HI_COMPONENT_ID then if componentId > HI_COMPONENT_ID then
-- IDs are partitioned into ranges because component IDs are not nominal, -- IDs are partitioned into ranges because component IDs are not nominal,
-- so it needs to error when IDs intersect into the entity range. -- so it needs to error when IDs intersect into the entity range.
error("Too many components, consider using world:entity() instead to create components.") error("Too many components, consider using world:entity() instead to create components.")
end end
world.nextComponentId = componentId world.nextComponentId = componentId
return componentId return componentId
@ -572,7 +546,7 @@ function World.entity(world: World)
return world.nextEntityId + REST return world.nextEntityId + REST
end end
function World.delete(world: World, entityId: i53) function World.delete(world: World, entityId: i53)
local entityIndex = world.entityIndex local entityIndex = world.entityIndex
local record = entityIndex[entityId] local record = entityIndex[entityId]
moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE) moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE)
@ -584,57 +558,57 @@ function World.delete(world: World, entityId: i53)
end end
function World.observer(world: World, ...) function World.observer(world: World, ...)
local componentIds = { ... } local componentIds = {...}
return { return {
event = function(event) event = function(event)
local hook = world.hooks[event] local hook = world.hooks[event]
world.hooks[event] = nil world.hooks[event] = nil
local last, change local last, change
return function() return function()
last, change = next(hook, last) last, change = next(hook, last)
if not last then if not last then
return return
end end
local matched = false local matched = false
while not matched do while not matched do
local skip = false local skip = false
for _, id in change.ids do for _, id in change.ids do
if not table.find(componentIds, id) then if not table.find(componentIds, id) then
skip = true skip = true
break break
end end
end end
if skip then if skip then
last, change = next(hook, last) last, change = next(hook, last)
continue continue
end end
matched = true matched = true
end end
local queryOutput = {} local queryOutput = {}
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 _, id in componentIds do
table.insert(queryOutput, columns[archetypeRecords[id]][row]) table.insert(queryOutput, columns[archetypeRecords[id]][row])
end end
return archetype.entities[row], unpack(queryOutput, 1, #queryOutput) return archetype.entities[row], unpack(queryOutput, 1, #queryOutput)
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;
}) })

19255
roblox.yml Normal file

File diff suppressed because it is too large Load diff

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"