Revert mirror (#79)

This commit is contained in:
Marcus 2024-07-14 06:35:13 +02:00 committed by GitHub
parent 85a970e9ff
commit 8a7b3de004
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 430 additions and 888 deletions

View file

@ -20,23 +20,23 @@ do
TITLE("one component in common") 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 for _ in world:query(A) do
end end
end) end)
BENCH("2 component", function() BENCH("2 component", function()
for _ in world:query(A, B) do for _ in world:query(B, A) do
end end
end) end)
BENCH("4 component", function() BENCH("4 component", function()
for _ in world:query(A, B, C, D) do for _ in world:query(D, C, B, A) do
end end
end) end)
BENCH("8 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D, E, F, G, H) do for _ in world:query(H, G, F, E, D, C, B, A) do
end end
end) end)
@ -142,17 +142,17 @@ do
end) end)
BENCH("2 component", function() BENCH("2 component", function()
for _ in world:query(A, B) do for _ in world:query(B, A) do
end end
end) end)
BENCH("4 component", function() BENCH("4 component", function()
for _ in world:query(A, B, C, D) do for _ in world:query(D, C, B, A) do
end end
end) end)
BENCH("8 component", function() BENCH("8 component", function()
for _ in world:query(A, B, C, D, E, F, G, H) do for _ in world:query(H, G, F, E, D, C, B, A) do
end end
end) end)

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
--!optimize 2 --!optimize 2
--!native --!native
--!strict --!strict
@ -173,7 +174,7 @@ local function ECS_PAIR_OBJECT(entityIndex, e)
return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) return getAlive(entityIndex, ECS_ENTITY_T_LO(e))
end end
local function nextEntityId(entityIndex: EntityIndex, index: i24): i53 local function entity_index_new_id(entityIndex: EntityIndex, index: i24): i53
--local id = ECS_COMBINE(index, 0) --local id = ECS_COMBINE(index, 0)
local id = index local id = index
entityIndex.sparse[id] = { entityIndex.sparse[id] = {
@ -184,7 +185,7 @@ local function nextEntityId(entityIndex: EntityIndex, index: i24): i53
return id return id
end end
local function transitionArchetype(entityIndex: EntityIndex, to: Archetype, local function archetype_move(entityIndex: EntityIndex, to: Archetype,
destinationRow: i24, from: Archetype, sourceRow: i24) destinationRow: i24, from: Archetype, sourceRow: i24)
local columns = from.columns local columns = from.columns
@ -235,34 +236,34 @@ local function transitionArchetype(entityIndex: EntityIndex, to: Archetype,
record2.row = sourceRow record2.row = sourceRow
end end
local function archetypeAppend(entity: number, archetype: Archetype): number local function archetype_append(entity: number, archetype: Archetype): number
local entities = archetype.entities local entities = archetype.entities
local length = #entities + 1 local length = #entities + 1
entities[length] = entity entities[length] = entity
return length return length
end end
local function newEntity(entityId: i53, record: Record, archetype: Archetype): Record local function new_entity(entityId: i53, record: Record, archetype: Archetype): Record
local row = archetypeAppend(entityId, archetype) local row = archetype_append(entityId, archetype)
record.archetype = archetype record.archetype = archetype
record.row = row record.row = row
return record return record
end end
local function moveEntity(entityIndex: EntityIndex, entityId: i53, record: Record, to: Archetype) local function entity_move(entity_index: EntityIndex, entityId: i53, record: Record, to: Archetype)
local sourceRow = record.row local sourceRow = record.row
local from = record.archetype local from = record.archetype
local destinationRow = archetypeAppend(entityId, to) local dst_row = archetype_append(entityId, to)
transitionArchetype(entityIndex, to, destinationRow, from, sourceRow) archetype_move(entity_index, to, dst_row, from, sourceRow)
record.archetype = to record.archetype = to
record.row = destinationRow record.row = dst_row
end end
local function hash(arr: { number }): string local function hash(arr: { number }): string
return table.concat(arr, "_") return table.concat(arr, "_")
end end
local function ensureComponentRecord( local function id_record_ensure(
componentIndex: ComponentIndex, componentIndex: ComponentIndex,
componentId: number componentId: number
): ArchetypeMap ): ArchetypeMap
@ -283,7 +284,7 @@ local function ECS_ID_IS_WILDCARD(e: i53): boolean
return first == EcsWildcard or second == EcsWildcard return first == EcsWildcard or second == EcsWildcard
end end
local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archetype local function archetype_of(world: any, types: { i24 }, prev: Archetype?): Archetype
local ty = hash(types) local ty = hash(types)
local id = world.nextArchetypeId + 1 local id = world.nextArchetypeId + 1
@ -295,18 +296,19 @@ local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archet
local records = {} local records = {}
for i, componentId in types do for i, componentId in types do
local idr = ensureComponentRecord(componentIndex, componentId) local idr = id_record_ensure(componentIndex, componentId)
idr.cache[id] = i idr.cache[id] = i
idr.size += 1
records[componentId] = i records[componentId] = i
if ECS_IS_PAIR(componentId) then if ECS_IS_PAIR(componentId) then
local relation = ECS_PAIR_RELATION(world.entityIndex, componentId) local relation = ECS_PAIR_RELATION(world.entityIndex, componentId)
local object = ECS_PAIR_OBJECT(world.entityIndex, componentId) local object = ECS_PAIR_OBJECT(world.entityIndex, componentId)
local r = ECS_PAIR(relation, EcsWildcard) local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = ensureComponentRecord(componentIndex, r) local idr_r = id_record_ensure(componentIndex, r)
local o = ECS_PAIR(EcsWildcard, object) local o = ECS_PAIR(EcsWildcard, object)
local idr_o = ensureComponentRecord(componentIndex, o) local idr_o = id_record_ensure(componentIndex, o)
records[r] = i records[r] = i
records[o] = i records[o] = i
@ -347,16 +349,16 @@ export type World = {
ROOT_ARCHETYPE: Archetype ROOT_ARCHETYPE: Archetype
} }
local function entity(world: World): i53 local function world_entity(world: World): i53
local entityId = world.nextEntityId + 1 local entityId = world.nextEntityId + 1
world.nextEntityId = entityId world.nextEntityId = entityId
return nextEntityId(world.entityIndex, entityId + EcsRest) return entity_index_new_id(world.entityIndex, entityId + EcsRest)
end end
-- TODO: -- TODO:
-- should have an additional `nth` parameter which selects the nth target -- should have an additional `nth` parameter which selects the nth target
-- this is important when an entity can have multiple relationships with the same target -- this is important when an entity can have multiple relationships with the same target
local function target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24? local function world_target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
local entityIndex = world.entityIndex local entityIndex = world.entityIndex
local record = entityIndex.sparse[entity] local record = entityIndex.sparse[entity]
local archetype = record.archetype local archetype = record.archetype
@ -377,11 +379,11 @@ local function target(world: World, entity: i53, relation: i24--[[, nth: number]
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord]) return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord])
end end
local function parent(world: World, entity: i53) local function world_parent(world: World, entity: i53)
return target(world, entity, EcsChildOf) return world_target(world, entity, EcsChildOf)
end end
local function ensureArchetype(world: World, types, prev): Archetype local function archetype_ensure(world: World, types, prev): Archetype
if #types < 1 then if #types < 1 then
return world.ROOT_ARCHETYPE return world.ROOT_ARCHETYPE
end end
@ -392,10 +394,10 @@ local function ensureArchetype(world: World, types, prev): Archetype
return archetype return archetype
end end
return archetypeOf(world, types, prev) return archetype_of(world, types, prev)
end end
local function findInsert(types: { i53 }, toAdd: i53): number local function find_insert(types: { i53 }, toAdd: i53): number
for i, id in types do for i, id in types do
if id == toAdd then if id == toAdd then
return -1 return -1
@ -407,14 +409,14 @@ local function findInsert(types: { i53 }, toAdd: i53): number
return #types + 1 return #types + 1
end end
local function findArchetypeWith(world: World, node: Archetype, componentId: i53): Archetype local function find_archetype_with(world: World, node: Archetype, componentId: i53): Archetype
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 destinationType = table.clone(node.types) :: { i53 } local destinationType = table.clone(node.types) :: { i53 }
local at = findInsert(types, componentId) local at = find_insert(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 -- 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. -- directly instead of needing to hash types for a lookup to the archetype.
@ -422,10 +424,10 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
end end
table.insert(destinationType, at, componentId) table.insert(destinationType, at, componentId)
return ensureArchetype(world, destinationType, node) return archetype_ensure(world, destinationType, node)
end end
local function ensureEdge(archetype: Archetype, componentId: i53): ArchetypeEdge local function edge_ensure(archetype: Archetype, componentId: i53): ArchetypeEdge
local edges = archetype.edges local edges = archetype.edges
local edge = edges[componentId] local edge = edges[componentId]
if not edge then if not edge then
@ -435,43 +437,43 @@ local function ensureEdge(archetype: Archetype, componentId: i53): ArchetypeEdge
return edge return edge
end end
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype local function archetype_traverse_add(world: World, componentId: i53, from: Archetype): Archetype
from = from or world.ROOT_ARCHETYPE from = from or world.ROOT_ARCHETYPE
local edge = ensureEdge(from, componentId) local edge = edge_ensure(from, componentId)
local add = edge.add local add = edge.add
if not add then 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.
add = findArchetypeWith(world, from, componentId) add = find_archetype_with(world, from, componentId)
edge.add = add :: never edge.add = add :: never
end end
return add return add
end end
local function add(world: World, entityId: i53, componentId: i53) local function world_add(world: World, entityId: i53, componentId: i53)
local entityIndex = world.entityIndex local entityIndex = world.entityIndex
local record = entityIndex.sparse[entityId] local record = entityIndex.sparse[entityId]
local from = record.archetype local from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from) local to = archetype_traverse_add(world, componentId, from)
if from == to then if from == to then
return return
end end
if from then if from then
moveEntity(entityIndex, entityId, record, to) entity_move(entityIndex, entityId, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
newEntity(entityId, record, to) new_entity(entityId, record, to)
end end
end end
end end
-- Symmetric like `World.add` but idempotent -- Symmetric like `World.add` but idempotent
local function set(world: World, entityId: i53, componentId: i53, data: unknown) local function world_set(world: World, entityId: i53, componentId: i53, data: unknown)
local record = world.entityIndex.sparse[entityId] local record = world.entityIndex.sparse[entityId]
local from = record.archetype local from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from) local to = archetype_traverse_add(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
@ -484,11 +486,11 @@ local function set(world: World, entityId: i53, componentId: i53, data: unknown)
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) entity_move(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) new_entity(entityId, record, to)
end end
end end
@ -496,7 +498,7 @@ local function set(world: World, entityId: i53, componentId: i53, data: unknown)
to.columns[archetypeRecord][record.row] = data to.columns[archetypeRecord][record.row] = data
end end
local function newComponent(world: World): i53 local function world_component(world: World): i53
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,
@ -504,14 +506,14 @@ local function newComponent(world: World): i53
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
local id = nextEntityId(world.entityIndex, componentId) local id = entity_index_new_id(world.entityIndex, componentId)
add(world, id, EcsComponent) world_add(world, id, EcsComponent)
return id return id
end end
local function archetypeTraverseRemove(world: World, componentId: i53, from: Archetype): Archetype local function archetypeTraverseRemove(world: World, componentId: i53, from: Archetype): Archetype
local edge = ensureEdge(from, componentId) local edge = edge_ensure(from, componentId)
local remove = edge.remove local remove = edge.remove
if not remove then if not remove then
@ -521,21 +523,21 @@ local function archetypeTraverseRemove(world: World, componentId: i53, from: Arc
return from return from
end end
table.remove(to, at) table.remove(to, at)
remove = ensureArchetype(world, to, from) remove = archetype_ensure(world, to, from)
edge.remove = remove :: never edge.remove = remove :: never
end end
return remove return remove
end end
local function remove(world: World, entityId: i53, componentId: i53) local function world_remove(world: World, entityId: i53, componentId: i53)
local entityIndex = world.entityIndex local entityIndex = world.entityIndex
local record = entityIndex.sparse[entityId] local record = entityIndex.sparse[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(entityIndex, entityId, record, destinationArchetype) entity_move(entityIndex, entityId, record, destinationArchetype)
end end
end end
@ -561,7 +563,7 @@ local function archetypeDelete(world: World, id: i53)
if archetypesMap then if archetypesMap then
for archetypeId in archetypesMap.cache do for archetypeId in archetypesMap.cache do
for _, entity in archetypes[archetypeId].entities do for _, entity in archetypes[archetypeId].entities do
remove(world, entity, id) world_remove(world, entity, id)
end end
end end
@ -569,7 +571,7 @@ local function archetypeDelete(world: World, id: i53)
end end
end end
local function delete(world: World, entityId: i53) local function world_delete(world: World, entityId: i53)
local record = world.entityIndex.sparse[entityId] local record = world.entityIndex.sparse[entityId]
if not record then if not record then
return return
@ -606,7 +608,7 @@ local function delete(world: World, entityId: i53)
end end
local function clear(world: World, entityId: i53) local function world_clear(world: World, entityId: i53)
--TODO: use sparse_get (stashed) --TODO: use sparse_get (stashed)
local record = world.entityIndex.sparse[entityId] local record = world.entityIndex.sparse[entityId]
if not record then if not record then
@ -620,7 +622,7 @@ local function clear(world: World, entityId: i53)
return return
end end
moveEntity(world.entityIndex, entityId, record, ROOT_ARCHETYPE) entity_move(world.entityIndex, entityId, record, ROOT_ARCHETYPE)
end end
-- Keeping the function as small as possible to enable inlining -- Keeping the function as small as possible to enable inlining
@ -639,7 +641,7 @@ local function fetch(record: Record, componentId: i24): any
return archetype.columns[archetypeRecord][record.row] return archetype.columns[archetypeRecord][record.row]
end end
local function get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any local function world_get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
local id = entityId local id = entityId
local record = world.entityIndex.sparse[id] local record = world.entityIndex.sparse[id]
if not record then if not record then
@ -688,7 +690,7 @@ local function replaceMult(row, columns, ...)
end end
end end
local query: (World, ...i53) -> Query local world_query: (World, ...i53) -> Query
do do
local indices: { { number } } local indices: { { number } }
local compatibleArchetypes: { Archetype } local compatibleArchetypes: { Archetype }
@ -703,7 +705,7 @@ do
local entities: {} local entities: {}
local i: number local i: number
local function query_next() local function world_query_next()
local entityId = entities[i] local entityId = entities[i]
while entityId == nil do while entityId == nil do
lastArchetype += 1 lastArchetype += 1
@ -759,7 +761,7 @@ do
return entityId, unpack(queryOutput, 1, queryLength) return entityId, unpack(queryOutput, 1, queryLength)
end end
local function query_without(self, ...): Query local function world_query_without(self, ...): Query
local withoutComponents = { ... } local withoutComponents = { ... }
for i = #compatibleArchetypes, 1, -1 do for i = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i] local archetype = compatibleArchetypes[i]
@ -785,16 +787,16 @@ do
return self return self
end end
local function query_iter() local function world_query_iter()
lastArchetype = 1 lastArchetype = 1
archetype = compatibleArchetypes[1] archetype = compatibleArchetypes[1]
entities = archetype.entities entities = archetype.entities
i = #entities i = #entities
return query_next return world_query_next
end end
local function query_replace(_, fn: any) local function world_query_replace(_, fn: any)
for i, archetype in compatibleArchetypes do for i, archetype in compatibleArchetypes do
local tr = indices[i] local tr = indices[i]
local columns = archetype.columns local columns = archetype.columns
@ -834,7 +836,7 @@ do
end end
end end
function query(world: World, ...: number): Query function world_query(world: World, ...: number): Query
-- breaking? -- breaking?
if (...) == nil then if (...) == nil then
error("Missing components") error("Missing components")
@ -855,7 +857,7 @@ do
return EmptyQuery return EmptyQuery
end end
if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size < map.size then if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then
firstArchetypeMap = map firstArchetypeMap = map
end end
end end
@ -900,10 +902,10 @@ do
i = #entities i = #entities
local it = { local it = {
__iter = query_iter, __iter = world_query_iter,
next = query_next, next = world_query_next,
without = query_without, without = world_query_without,
replace = query_replace replace = world_query_replace
} }
return setmetatable(it, it) :: any return setmetatable(it, it) :: any
@ -1047,17 +1049,17 @@ export type WorldShim = typeof(setmetatable(
local World = {} local World = {}
World.__index = World World.__index = World
World.entity = entity World.entity = world_entity
World.query = query World.query = world_query
World.remove = remove World.remove = world_remove
World.clear = clear World.clear = world_clear
World.delete = delete World.delete = world_delete
World.component = newComponent World.component = world_component
World.add = add World.add = world_add
World.set = set World.set = world_set
World.get = get World.get = world_get
World.target = target World.target = world_target
World.parent = parent World.parent = world_parent
function World.new() function World.new()
local self = setmetatable({ local self = setmetatable({
@ -1077,10 +1079,10 @@ function World.new()
ROOT_ARCHETYPE = (nil :: any) :: Archetype, ROOT_ARCHETYPE = (nil :: any) :: Archetype,
}, World) }, World)
self.ROOT_ARCHETYPE = archetypeOf(self, {}) self.ROOT_ARCHETYPE = archetype_of(self, {})
-- Initialize built-in components -- Initialize built-in components
nextEntityId(self.entityIndex, EcsChildOf) entity_index_new_id(self.entityIndex, EcsChildOf)
return self return self
end end