Update mirror

This commit is contained in:
Ukendio 2024-05-05 00:48:18 +02:00
parent cda04ce5a9
commit a28d57c3c3

View file

@ -51,31 +51,48 @@ local REST = HI_COMPONENT_ID + 4
local function transitionArchetype(
entityIndex: EntityIndex,
destinationArchetype: Archetype,
to: Archetype,
destinationRow: i24,
sourceArchetype: Archetype,
from: Archetype,
sourceRow: i24
)
local columns = sourceArchetype.columns
local sourceEntities = sourceArchetype.entities
local destinationEntities = destinationArchetype.entities
local destinationColumns = destinationArchetype.columns
local columns = from.columns
local sourceEntities = from.entities
local destinationEntities = to.entities
local destinationColumns = to.columns
local tr = to.records
local types = from.types
for componentId, column in columns do
local targetColumn = destinationColumns[componentId]
for i, column in columns do
-- 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.
local targetColumn = destinationColumns[tr[types[i]]]
-- Sometimes target column may not exist, e.g. when you remove a component.
if targetColumn then
targetColumn[destinationRow] = column[sourceRow]
end
column[sourceRow] = column[#column]
column[#column] = nil
-- If the entity is the last row in the archetype then swapping it would be meaningless.
local last = #column
if sourceRow ~= last then
-- Swap rempves columns to ensure there are no holes in the archetype.
column[sourceRow] = column[last]
end
column[last] = nil
end
-- Move the entity from the source to the destination archetype.
destinationEntities[destinationRow] = sourceEntities[sourceRow]
entityIndex[sourceEntities[sourceRow]].row = destinationRow
-- Because we have swapped columns we now have to update the records
-- corresponding to the entities' rows that were swapped.
local movedAway = #sourceEntities
sourceEntities[sourceRow] = sourceEntities[movedAway]
entityIndex[sourceEntities[movedAway]].row = sourceRow
if sourceRow ~= movedAway then
sourceEntities[sourceRow] = sourceEntities[movedAway]
entityIndex[sourceEntities[movedAway]].row = sourceRow
end
sourceEntities[movedAway] = nil
end
@ -145,7 +162,9 @@ local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Arch
}
world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype
createArchetypeRecords(world.componentIndex, archetype, prev)
if #types > 0 then
createArchetypeRecords(world.componentIndex, archetype, prev)
end
return archetype
end
@ -180,8 +199,6 @@ local function emit(world, eventDescription)
})
end
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
if #added > 0 then
emit(world, {
@ -194,13 +211,13 @@ local function onNotifyAdd(world, archetype, otherArchetype, row: number, added:
end
end
export type World = typeof(World.new())
local function ensureArchetype(world: World, types, prev)
if #types < 1 then
return world.ROOT_ARCHETYPE
end
local ty = hash(types)
local archetype = world.archetypeIndex[ty]
if archetype then
@ -226,8 +243,13 @@ end
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
local types = node.types
-- Component IDs are added incrementally, so inserting and sorting
-- them each time would be expensive. Instead this insertion sort can find the insertion
-- point in the types array.
local at = findInsert(types, componentId)
if at == -1 then
-- If it finds a duplicate, it just means it is the same archetype so it can return it
-- directly instead of needing to hash types for a lookup to the archetype.
return node
end
@ -245,6 +267,7 @@ end
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
if not from then
-- If there was no source archetype then it should return the ROOT_ARCHETYPE
if not world.ROOT_ARCHETYPE then
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
@ -254,6 +277,8 @@ local function archetypeTraverseAdd(world: World, componentId: i53, from: Archet
local edge = ensureEdge(from, componentId)
if not edge.add then
-- Save an edge using the component ID to the archetype to allow
-- faster traversals to adjacent archetypes.
edge.add = findArchetypeWith(world, from, componentId)
end
@ -270,26 +295,31 @@ end
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
local record = ensureRecord(world.entityIndex, entityId)
local sourceArchetype = record.archetype
local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype)
local from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from)
if sourceArchetype == destinationArchetype then
local archetypeRecord = destinationArchetype.records[componentId]
destinationArchetype.columns[archetypeRecord][record.row] = data
if from == to then
-- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly.
local archetypeRecord = to.records[componentId]
from.columns[archetypeRecord][record.row] = data
-- Should fire an OnSet event here.
return
end
if sourceArchetype then
moveEntity(world.entityIndex, entityId, record, destinationArchetype)
if from then
-- If there was a previous archetype, then the entity needs to move the archetype
moveEntity(world.entityIndex, entityId, record, to)
else
if #destinationArchetype.types > 0 then
newEntity(entityId, record, destinationArchetype)
onNotifyAdd(world, destinationArchetype, sourceArchetype, record.row, { componentId })
if #to.types > 0 then
-- When there is no previous archetype it should create the archetype
newEntity(entityId, record, to)
onNotifyAdd(world, to, from, record.row, { componentId })
end
end
local archetypeRecord = destinationArchetype.records[componentId]
destinationArchetype.columns[archetypeRecord][record.row] = data
local archetypeRecord = to.records[componentId]
to.columns[archetypeRecord][record.row] = data
end
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
@ -316,9 +346,10 @@ function World.remove(world: World, entityId: i53, componentId: i53)
end
end
-- Keeping the function as small as possible to enable inlining
local function get(componentIndex: { [i24]: ArchetypeMap }, record: Record, componentId: i24)
local archetype = record.archetype
local archetypeRecord = componentIndex[componentId].sparse[archetype.id]
local archetypeRecord = archetype.records[componentId]
if not archetypeRecord then
return nil
@ -388,26 +419,24 @@ function World.query(world: World, ...: i53): Query
end
end
local i = 0
for id in firstArchetypeMap.sparse do
local archetype = archetypes[id]
local archetypeRecords = archetype.records
local indices = {}
local skip = false
for j, componentId in components do
for i, componentId in components do
local index = archetypeRecords[componentId]
if not index then
skip = true
break
end
indices[j] = archetypeRecords[componentId]
indices[i] = archetypeRecords[componentId]
end
if skip then
continue
end
i += 1
table.insert(compatibleArchetypes, { archetype, indices })
end
@ -464,7 +493,7 @@ function World.query(world: World, ...: i53): Query
local entityId = archetype.entities[row :: number]
local columns = archetype.columns
local tr = compatibleArchetype[2]
if queryLength == 1 then
return entityId, columns[tr[1]][row]
elseif queryLength == 2 then
@ -530,7 +559,9 @@ end
function World.component(world: World)
local componentId = world.nextComponentId + 1
if componentId > HI_COMPONENT_ID then
error("Too many components")
-- IDs are partitioned into ranges because component IDs are not nominal,
-- so it needs to error when IDs intersect into the entity range.
error("Too many components, consider using world:entity() instead to create components.")
end
world.nextComponentId = componentId
return componentId
@ -541,6 +572,17 @@ function World.entity(world: World)
return world.nextEntityId + REST
end
function World.delete(world: World, entityId: i53)
local entityIndex = world.entityIndex
local record = entityIndex[entityId]
moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE)
-- Since we just appended an entity to the ROOT_ARCHETYPE we have to remove it from
-- the entities array and delete the record. We know there won't be the hole since
-- we are always removing the last row.
--world.ROOT_ARCHETYPE.entities[record.row] = nil
--entityIndex[entityId] = nil
end
function World.observer(world: World, ...)
local componentIds = { ... }