This commit is contained in:
HowManySmall 2024-05-04 15:02:03 -06:00
parent b3f8e2504e
commit f1dc668214
4 changed files with 356 additions and 387 deletions

View file

@ -3,4 +3,4 @@ wally = "upliftgames/wally@0.3.1"
rojo = "rojo-rbx/rojo@7.4.1" rojo = "rojo-rbx/rojo@7.4.1"
stylua = "johnnymorganz/stylua@0.19.1" stylua = "johnnymorganz/stylua@0.19.1"
selene = "kampfkarren/selene@0.26.1" selene = "kampfkarren/selene@0.26.1"
wally-patch-package="Barocena/wally-patch-package@1.2.1" wally-patch-package = "Barocena/wally-patch-package@1.2.1"

View file

@ -1,11 +1,11 @@
--!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()
print(testkit.color.white(title)) print(testkit.color.white(title))
end end
local jecs = require("../mirror/init") local jecs = require("../mirror/init")
@ -15,285 +15,285 @@ 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
local ecs = jecs.World.new() TITLE(testkit.color.white_underline("Jecs query"))
do TITLE "one component in common" local ecs = jecs.World.new()
do
TITLE("one component in common")
local function view_bench( local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
world: jecs.World, BENCH("1 component", function()
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53 for _ in world:query(A) do
) end
end)
BENCH("1 component", function() BENCH("2 component", function()
for _ in world:query(A) do end for _ in world:query(A, B) do
end) end
end)
BENCH("2 component", function() BENCH("4 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B, C, D) do
end) end
end)
BENCH("4 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D) do for _ in world:query(A, B, C, D, E, F, G, H) do
end end
end) end)
end
BENCH("8 component", function() local D1 = ecs:component()
for _ in world:query(A, B, C, D, E, F, G, H) do end local D2 = ecs:component()
end) local D3 = ecs:component()
end local D4 = ecs:component()
local D5 = ecs:component()
local D6 = ecs:component()
local D7 = ecs:component()
local D8 = ecs:component()
local D1 = ecs:component() local function flip()
local D2 = ecs:component() return math.random() >= 0.15
local D3 = ecs:component() end
local D4 = ecs:component()
local D5 = ecs:component()
local D6 = ecs:component()
local D7 = ecs:component()
local D8 = ecs:component()
local function flip() local added = 0
return math.random() >= 0.15
end
local added = 0
local archetypes = {} local archetypes = {}
for i = 1, 2^16-2 do for i = 1, 2 ^ 16 - 2 do
local entity = ecs:entity() local entity = ecs:entity()
local combination = "" local combination = ""
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:set(entity, D2, {value = true}) ecs:set(entity, D2, {value = true})
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:set(entity, D3, { value = true }) ecs:set(entity, D3, {value = true})
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:set(entity, D4, { value = true}) ecs:set(entity, D4, {value = true})
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:set(entity, D5, { value = true}) ecs:set(entity, D5, {value = true})
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:set(entity, D6, {value = true}) ecs:set(entity, D6, {value = true})
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:set(entity, D7, { value = true}) ecs:set(entity, D7, {value = true})
end end
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
added += 1
if #combination == 7 then ecs:set(entity, D1, {value = true})
added += 1 end
ecs:set(entity, D1, { value = true})
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("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()
) for _ in world:query(A) do
end
end)
BENCH("1 component", function() BENCH("2 component", function()
for _ in world:query(A) do end for _ in world:query(A, B) do
end) end
end)
BENCH("2 component", function() BENCH("4 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B, C, D) do
end) end
end)
BENCH("4 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D) do for _ in world:query(A, B, C, D, E, F, G, H) do
end end
end) end)
end
BENCH("8 component", function() local D1 = component()
for _ in world:query(A, B, C, D, E, F, G, H) do end local D2 = component()
end) local D3 = component()
end local D4 = component()
local D5 = component()
local D6 = component()
local D7 = component()
local D8 = component()
local D1 = component() local function flip()
local D2 = component() return math.random() >= 0.15
local D3 = component() end
local D4 = component()
local D5 = component()
local D6 = component()
local D7 = component()
local D8 = component()
local function flip() local added = 0
return math.random() >= 0.15
end
local added = 0
local archetypes = {} local archetypes = {}
for i = 1, 2^16-2 do for i = 1, 2 ^ 16 - 2 do
local entity = ecs:spawn() local entity = ecs:spawn()
local combination = "" local combination = ""
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:insert(entity, D2({value = true})) ecs:insert(entity, D2({value = true}))
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:insert(entity, D3({value = true})) ecs:insert(entity, D3({value = true}))
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:insert(entity, D4({value = true})) ecs:insert(entity, D4({value = true}))
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:insert(entity, D5({value = true})) ecs:insert(entity, D5({value = true}))
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:insert(entity, D6({value = true})) ecs:insert(entity, D6({value = true}))
end
if flip() then
combination ..= "G"
ecs:insert(entity, D7({value = true}))
end
if flip() then
combination ..= "H"
ecs:insert(entity, D8({value = true}))
end
end if #combination == 7 then
if flip() then added += 1
combination ..= "G" ecs:insert(entity, D1({value = true}))
ecs:insert(entity, D7({value = true})) end
end
if flip() then
combination ..= "H"
ecs:insert(entity, D8({value = true}))
end
if #combination == 7 then
added += 1
ecs:insert(entity, D1({value = true}))
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
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) end
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
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()
) for _ in world:query(A) do
end
end)
BENCH("1 component", function() BENCH("2 component", function()
for _ in world:query(A) do end for _ in world:query(A, B) do
end) end
end)
BENCH("2 component", function() BENCH("4 component", function()
for _ in world:query(A, B) do end for _ in world:query(A, B, C, D) do
end) end
end)
BENCH("4 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D) do for _ in world:query(A, B, C, D, E, F, G, H) do
end end
end) end)
end
BENCH("8 component", function() local D1 = component()
for _ in world:query(A, B, C, D, E, F, G, H) do end local D2 = component()
end) local D3 = component()
end local D4 = component()
local D5 = component()
local D6 = component()
local D7 = component()
local D8 = component()
local D1 = component() local function flip()
local D2 = component() return math.random() >= 0.15
local D3 = component() end
local D4 = component()
local D5 = component()
local D6 = component()
local D7 = component()
local D8 = component()
local function flip() local added = 0
return math.random() >= 0.15
end
local added = 0
local archetypes = {} local archetypes = {}
for i = 1, 2^16-2 do for i = 1, 2 ^ 16 - 2 do
local entity = ecs:spawn() local entity = ecs:spawn()
local combination = "" local combination = ""
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:insert(entity, D2({value = true})) ecs:insert(entity, D2({value = true}))
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:insert(entity, D3({value = true})) ecs:insert(entity, D3({value = true}))
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:insert(entity, D4({value = true})) ecs:insert(entity, D4({value = true}))
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:insert(entity, D5({value = true})) ecs:insert(entity, D5({value = true}))
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:insert(entity, D6({value = true})) ecs:insert(entity, D6({value = true}))
end
if flip() then
combination ..= "G"
ecs:insert(entity, D7({value = true}))
end
if flip() then
combination ..= "H"
ecs:insert(entity, D8({value = true}))
end
end if #combination == 7 then
if flip() then added += 1
combination ..= "G" ecs:insert(entity, D1({value = true}))
ecs:insert(entity, D7({value = true})) end
end
if flip() then
combination ..= "H"
ecs:insert(entity, D8({value = true}))
end
if #combination == 7 then
added += 1
ecs:insert(entity, D1({value = true}))
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

@ -1,73 +0,0 @@
--!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,29 +3,45 @@
--!strict --!strict
--draft 4 --draft 4
local Types = require(script.Types) type i53 = number
type i24 = number
type i53 = Types.i53 type Ty = {i53}
type i24 = Types.i24 type ArchetypeId = number
type Ty = Types.Ty type Column = {any}
type ArchetypeId = Types.ArchetypeId
type Column = Types.Column type Archetype = {
id: number,
edges: {
[i24]: {
add: Archetype,
remove: Archetype,
},
},
types: Ty,
type: string | number,
entities: {number},
columns: {Column},
records: {},
}
type Archetype = Types.Archetype type Record = {
type Record = Types.Record archetype: Archetype,
row: number,
}
type EntityIndex = Types.EntityIndex type EntityIndex = {[i24]: Record}
type ComponentIndex = Types.ComponentIndex type ComponentIndex = {[i24]: ArchetypeMap}
type ArchetypeRecord = Types.ArchetypeRecord type ArchetypeRecord = number
type ArchetypeMap = Types.ArchetypeMap type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
type Archetypes = Types.Archetypes type Archetypes = {[ArchetypeId]: Archetype}
type ArchetypeDiff = Types.ArchetypeDiff type ArchetypeDiff = {
added: Ty,
-- type World = Types.World removed: Ty,
}
local HI_COMPONENT_ID = 256 local HI_COMPONENT_ID = 256
local ON_ADD = HI_COMPONENT_ID + 1 local ON_ADD = HI_COMPONENT_ID + 1
@ -66,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)
@ -107,32 +126,34 @@ local function hash(arr): string | number
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 = {
@ -146,7 +167,7 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet
} }
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
@ -212,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
@ -222,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)
@ -243,38 +262,48 @@ 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 id = entityId
if not entityIndex[id] then local record = entityIndex[id]
entityIndex[id] = {}
if not record then
record = {}
entityIndex[id] = 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)
@ -310,27 +339,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)
if not edge.remove then local remove = edge.remove
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): number? local function get(_componentIndex: {[i24]: ArchetypeMap}, record: Record, componentId: i24)
local archetype = record.archetype local archetype = record.archetype
local archetypeRecord = archetype.records[componentId] local archetypeRecord = archetype.records[componentId]
@ -364,7 +396,7 @@ function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53
end end
end end
local function noop(self: Query, ...: i53): () -> (number, ...any) local function noop(_self: Query, ...: i53): () -> (number, ...any)
return function() end :: any return function() end :: any
end end
@ -379,6 +411,8 @@ export type Query = typeof(EmptyQuery)
function World.query(world: World, ...: i53): Query function World.query(world: World, ...: i53): Query
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
@ -390,7 +424,7 @@ function World.query(world: World, ...: i53): Query
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
@ -419,7 +453,9 @@ function World.query(world: World, ...: i53): Query
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)
@ -431,18 +467,18 @@ 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 index = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i][1] local archetype = compatibleArchetypes[index][1]
local shouldRemove = false local shouldRemove = false
for _, componentId in components do for _, componentId in withoutComponents 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, index)
end end
end end
@ -460,18 +496,20 @@ function World.query(world: World, ...: i53): Query
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 entities = archetype.entities
local row = next(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) entities = archetype.entities
row = next(entities, row)
end end
lastRow = row lastRow = row
local entityId = archetype.entities[row :: number] local entityId = entities[row :: number]
local columns = archetype.columns local columns = archetype.columns
local tr = compatibleArchetype[2] local tr = compatibleArchetype[2]
@ -519,8 +557,8 @@ function World.query(world: World, ...: i53): Query
columns[tr[8]][row] columns[tr[8]][row]
end end
for i in components do for index in components do
queryOutput[i] = tr[i][row] queryOutput[index] = tr[index][row]
end end
return entityId, unpack(queryOutput, 1, queryLength) return entityId, unpack(queryOutput, 1, queryLength)
@ -542,8 +580,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)
@ -559,11 +598,12 @@ end
function World.observer(world: World, ...) function World.observer(world: World, ...)
local componentIds = {...} local 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()
@ -573,10 +613,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
@ -585,6 +626,7 @@ 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