mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 01:20:04 +00:00
Update mirror
This commit is contained in:
parent
cda04ce5a9
commit
a28d57c3c3
1 changed files with 77 additions and 35 deletions
112
mirror/init.lua
112
mirror/init.lua
|
@ -51,31 +51,48 @@ local REST = HI_COMPONENT_ID + 4
|
||||||
|
|
||||||
local function transitionArchetype(
|
local function transitionArchetype(
|
||||||
entityIndex: EntityIndex,
|
entityIndex: EntityIndex,
|
||||||
destinationArchetype: Archetype,
|
to: Archetype,
|
||||||
destinationRow: i24,
|
destinationRow: i24,
|
||||||
sourceArchetype: Archetype,
|
from: Archetype,
|
||||||
sourceRow: i24
|
sourceRow: i24
|
||||||
)
|
)
|
||||||
local columns = sourceArchetype.columns
|
local columns = from.columns
|
||||||
local sourceEntities = sourceArchetype.entities
|
local sourceEntities = from.entities
|
||||||
local destinationEntities = destinationArchetype.entities
|
local destinationEntities = to.entities
|
||||||
local destinationColumns = destinationArchetype.columns
|
local destinationColumns = to.columns
|
||||||
|
local tr = to.records
|
||||||
|
local types = from.types
|
||||||
|
|
||||||
for componentId, column in columns do
|
for i, column in columns do
|
||||||
local targetColumn = destinationColumns[componentId]
|
-- 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
|
if targetColumn then
|
||||||
targetColumn[destinationRow] = column[sourceRow]
|
targetColumn[destinationRow] = column[sourceRow]
|
||||||
end
|
end
|
||||||
column[sourceRow] = column[#column]
|
-- If the entity is the last row in the archetype then swapping it would be meaningless.
|
||||||
column[#column] = nil
|
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
|
end
|
||||||
|
|
||||||
|
-- Move the entity from the source to the destination archetype.
|
||||||
destinationEntities[destinationRow] = sourceEntities[sourceRow]
|
destinationEntities[destinationRow] = sourceEntities[sourceRow]
|
||||||
entityIndex[sourceEntities[sourceRow]].row = destinationRow
|
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
|
local movedAway = #sourceEntities
|
||||||
sourceEntities[sourceRow] = sourceEntities[movedAway]
|
if sourceRow ~= movedAway then
|
||||||
entityIndex[sourceEntities[movedAway]].row = sourceRow
|
sourceEntities[sourceRow] = sourceEntities[movedAway]
|
||||||
|
entityIndex[sourceEntities[movedAway]].row = sourceRow
|
||||||
|
end
|
||||||
|
|
||||||
sourceEntities[movedAway] = nil
|
sourceEntities[movedAway] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -145,7 +162,9 @@ local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Arch
|
||||||
}
|
}
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[id] = archetype
|
world.archetypes[id] = archetype
|
||||||
createArchetypeRecords(world.componentIndex, archetype, prev)
|
if #types > 0 then
|
||||||
|
createArchetypeRecords(world.componentIndex, archetype, prev)
|
||||||
|
end
|
||||||
|
|
||||||
return archetype
|
return archetype
|
||||||
end
|
end
|
||||||
|
@ -180,8 +199,6 @@ local function emit(world, eventDescription)
|
||||||
})
|
})
|
||||||
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, {
|
||||||
|
@ -194,13 +211,13 @@ local function onNotifyAdd(world, archetype, otherArchetype, row: number, added:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export type World = typeof(World.new())
|
export type World = typeof(World.new())
|
||||||
|
|
||||||
local function ensureArchetype(world: World, types, prev)
|
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
|
||||||
|
@ -226,8 +243,13 @@ 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
|
||||||
|
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
||||||
|
-- point in the types array.
|
||||||
local at = findInsert(types, componentId)
|
local at = findInsert(types, componentId)
|
||||||
if at == -1 then
|
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
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -245,6 +267,7 @@ 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 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
|
||||||
|
@ -254,6 +277,8 @@ local function archetypeTraverseAdd(world: World, componentId: i53, from: Archet
|
||||||
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
|
||||||
|
-- faster traversals to adjacent archetypes.
|
||||||
edge.add = findArchetypeWith(world, from, componentId)
|
edge.add = findArchetypeWith(world, from, componentId)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -270,26 +295,31 @@ 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 sourceArchetype = record.archetype
|
local from = record.archetype
|
||||||
local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype)
|
local to = archetypeTraverseAdd(world, componentId, from)
|
||||||
|
|
||||||
if sourceArchetype == destinationArchetype then
|
if from == to then
|
||||||
local archetypeRecord = destinationArchetype.records[componentId]
|
-- If the archetypes are the same it can avoid moving the entity
|
||||||
destinationArchetype.columns[archetypeRecord][record.row] = data
|
-- and just set the data directly.
|
||||||
|
local archetypeRecord = to.records[componentId]
|
||||||
|
from.columns[archetypeRecord][record.row] = data
|
||||||
|
-- Should fire an OnSet event here.
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if sourceArchetype then
|
if from then
|
||||||
moveEntity(world.entityIndex, entityId, record, destinationArchetype)
|
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||||
|
moveEntity(world.entityIndex, entityId, record, to)
|
||||||
else
|
else
|
||||||
if #destinationArchetype.types > 0 then
|
if #to.types > 0 then
|
||||||
newEntity(entityId, record, destinationArchetype)
|
-- When there is no previous archetype it should create the archetype
|
||||||
onNotifyAdd(world, destinationArchetype, sourceArchetype, record.row, { componentId })
|
newEntity(entityId, record, to)
|
||||||
|
onNotifyAdd(world, to, from, record.row, { componentId })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetypeRecord = destinationArchetype.records[componentId]
|
local archetypeRecord = to.records[componentId]
|
||||||
destinationArchetype.columns[archetypeRecord][record.row] = data
|
to.columns[archetypeRecord][record.row] = data
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 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)
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
local archetypeRecord = componentIndex[componentId].sparse[archetype.id]
|
local archetypeRecord = archetype.records[componentId]
|
||||||
|
|
||||||
if not archetypeRecord then
|
if not archetypeRecord then
|
||||||
return nil
|
return nil
|
||||||
|
@ -388,26 +419,24 @@ function World.query(world: World, ...: i53): Query
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local i = 0
|
|
||||||
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 j, 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[j] = archetypeRecords[componentId]
|
indices[i] = archetypeRecords[componentId]
|
||||||
end
|
end
|
||||||
|
|
||||||
if skip then
|
if skip then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
i += 1
|
|
||||||
table.insert(compatibleArchetypes, { archetype, indices })
|
table.insert(compatibleArchetypes, { archetype, indices })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -464,7 +493,7 @@ function World.query(world: World, ...: i53): Query
|
||||||
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
|
||||||
|
@ -530,7 +559,9 @@ 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
|
||||||
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
|
end
|
||||||
world.nextComponentId = componentId
|
world.nextComponentId = componentId
|
||||||
return componentId
|
return componentId
|
||||||
|
@ -541,6 +572,17 @@ function World.entity(world: World)
|
||||||
return world.nextEntityId + REST
|
return world.nextEntityId + REST
|
||||||
end
|
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, ...)
|
function World.observer(world: World, ...)
|
||||||
local componentIds = { ... }
|
local componentIds = { ... }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue