Add bit flags to id_record (#101)

* Add bit flags to id_record

* set should not invoke OnAdd
This commit is contained in:
Marcus 2024-08-14 17:18:05 +02:00 committed by GitHub
parent 33f359a150
commit 6d6cc37a25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -41,6 +41,7 @@ type ArchetypeRecord = {
type ArchetypeMap = { type ArchetypeMap = {
cache: { ArchetypeRecord }, cache: { ArchetypeRecord },
flags: number,
first: ArchetypeMap, first: ArchetypeMap,
second: ArchetypeMap, second: ArchetypeMap,
parent: ArchetypeMap, parent: ArchetypeMap,
@ -58,6 +59,10 @@ type ArchetypeDiff = {
local HI_COMPONENT_ID = 256 local HI_COMPONENT_ID = 256
--------------------------------------------------
-------- ID_RECORD -------------------------------
--------------------------------------------------
local EcsOnAdd = HI_COMPONENT_ID + 1 local EcsOnAdd = HI_COMPONENT_ID + 1
local EcsOnRemove = HI_COMPONENT_ID + 2 local EcsOnRemove = HI_COMPONENT_ID + 2
local EcsOnSet = HI_COMPONENT_ID + 3 local EcsOnSet = HI_COMPONENT_ID + 3
@ -72,6 +77,10 @@ local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16) local ECS_GENERATION_MASK = bit32.lshift(1, 16)
local ECS_ID_HAS_DELETE = 0b0001
local ECS_ID_HAS_HOOKS = 0b0010
--local EcsIdExclusive = 0b0100
local function FLAGS_ADD(is_pair: boolean): number local function FLAGS_ADD(is_pair: boolean): number
local flags = 0x0 local flags = 0x0
@ -257,115 +266,6 @@ local function hash(arr: { number }): string
return table.concat(arr, "_") return table.concat(arr, "_")
end end
local function id_record_ensure(
componentIndex: ComponentIndex,
componentId: number
): ArchetypeMap
local idr = componentIndex[componentId]
if not idr then
idr = ({ size = 0, cache = {} } :: any) :: ArchetypeMap
componentIndex[componentId] = idr
end
return idr
end
local function ECS_ID_IS_WILDCARD(e: i53): boolean
assert(ECS_IS_PAIR(e))
local first = ECS_ENTITY_T_HI(e)
local second = ECS_ENTITY_T_LO(e)
return first == EcsWildcard or second == EcsWildcard
end
local function archetype_create(world: any, types: { i24 }, prev: Archetype?): Archetype
local ty = hash(types)
local id = world.nextArchetypeId + 1
world.nextArchetypeId = id
local length = #types
local columns = (table.create(length) :: any) :: { Column }
local componentIndex = world.componentIndex
local records: { ArchetypeRecord } = {}
for i, componentId in types do
local tr = { column = i, count = 1 }
local idr = id_record_ensure(componentIndex, componentId)
idr.cache[id] = tr
idr.size += 1
records[componentId] = tr
if ECS_IS_PAIR(componentId) then
local relation = ecs_pair_first(world, componentId)
local object = ecs_pair_second(world, componentId)
local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = id_record_ensure(componentIndex, r)
local o = ECS_PAIR(EcsWildcard, object)
local idr_o = id_record_ensure(componentIndex, o)
records[r] = tr
records[o] = tr
idr_r.cache[id] = tr
idr_o.cache[id] = tr
idr_r.size += 1
idr_o.size += 1
end
columns[i] = {}
end
local archetype: Archetype = {
columns = columns,
edges = {},
entities = {},
id = id,
records = records,
type = ty,
types = types,
}
world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype
return archetype
end
local function world_entity(world: World): i53
local entityId = world.nextEntityId + 1
world.nextEntityId = entityId
return entity_index_new_id(world.entityIndex, entityId + EcsRest)
end
-- TODO:
-- 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
local function world_target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
local record = world.entityIndex.sparse[entity]
local archetype = record.archetype
if not archetype then
return nil
end
local idr = world.componentIndex[ECS_PAIR(relation, EcsWildcard)]
if not idr then
return nil
end
local tr = idr.cache[archetype.id]
if not tr then
return nil
end
return ecs_pair_second(world, archetype.types[tr.column])
end
local function world_parent(world: World, entity: i53)
return world_target(world, entity, EcsChildOf)
end
local world_get: (world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) -> (...any) local world_get: (world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) -> (...any)
do do
-- Keeping the function as small as possible to enable inlining -- Keeping the function as small as possible to enable inlining
@ -468,6 +368,158 @@ local function world_has(world: World, entity: number, ...: i53): boolean
return true return true
end end
local function world_has_any(world: World, entity: number, ...: i53): boolean
local record = world.entityIndex.sparse[entity]
if not record then
return false
end
local archetype = record.archetype
if not archetype then
return false
end
local records = archetype.records
for i = 1, select("#", ...) do
if not records[select(i, ...)] then
return true
end
end
return false
end
local function id_record_ensure(
world,
id: number
): ArchetypeMap
local componentIndex = world.componentIndex
local idr = componentIndex[id]
if not idr then
local flags = 0b0000
local relation = ECS_ENTITY_T_HI(id)
if world_has_one_inline(world, relation, EcsDelete) then
flags = bit32.bor(flags, ECS_ID_HAS_DELETE)
end
if world_has_any(world, relation,
EcsOnAdd, EcsOnSet, EcsOnRemove)
then
flags = bit32.bor(flags, ECS_ID_HAS_HOOKS)
end
-- local FLAG2 = 0b0010
-- local FLAG3 = 0b0100
-- local FLAG4 = 0b1000
idr = {
size = 0,
cache = {},
flags = flags
} :: ArchetypeMap
componentIndex[id] = idr
end
return idr
end
local function ECS_ID_IS_WILDCARD(e: i53): boolean
assert(ECS_IS_PAIR(e))
local first = ECS_ENTITY_T_HI(e)
local second = ECS_ENTITY_T_LO(e)
return first == EcsWildcard or second == EcsWildcard
end
local function archetype_create(world: any, types: { i24 }, prev: Archetype?): Archetype
local ty = hash(types)
local id = world.nextArchetypeId + 1
world.nextArchetypeId = id
local length = #types
local columns = (table.create(length) :: any) :: { Column }
local records: { ArchetypeRecord } = {}
for i, componentId in types do
local tr = { column = i, count = 1 }
local idr = id_record_ensure(world, componentId)
idr.cache[id] = tr
idr.size += 1
records[componentId] = tr
if ECS_IS_PAIR(componentId) then
local relation = ecs_pair_first(world, componentId)
local object = ecs_pair_second(world, componentId)
local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = id_record_ensure(world, r)
local o = ECS_PAIR(EcsWildcard, object)
local idr_o = id_record_ensure(world, o)
records[r] = tr
records[o] = tr
idr_r.cache[id] = tr
idr_o.cache[id] = tr
idr_r.size += 1
idr_o.size += 1
end
columns[i] = {}
end
local archetype: Archetype = {
columns = columns,
edges = {},
entities = {},
id = id,
records = records,
type = ty,
types = types,
}
world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype
return archetype
end
local function world_entity(world: World): i53
local entityId = world.nextEntityId + 1
world.nextEntityId = entityId
return entity_index_new_id(world.entityIndex, entityId + EcsRest)
end
-- TODO:
-- 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
local function world_target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
local record = world.entityIndex.sparse[entity]
local archetype = record.archetype
if not archetype then
return nil
end
local idr = world.componentIndex[ECS_PAIR(relation, EcsWildcard)]
if not idr then
return nil
end
local tr = idr.cache[archetype.id]
if not tr then
return nil
end
return ecs_pair_second(world, archetype.types[tr.column])
end
local function world_parent(world: World, entity: i53)
return world_target(world, entity, EcsChildOf)
end
local function archetype_ensure(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
@ -560,7 +612,12 @@ local function world_add(world: World, entity: i53, id: i53)
end end
end end
invoke_hook(world, EcsOnAdd, id, entity) local idr = world.componentIndex[id]
local has_hooks = bit32.band(idr.flags, ECS_ID_HAS_HOOKS) ~= 0
if has_hooks then
invoke_hook(world, EcsOnAdd, id, entity)
end
end end
-- Symmetric like `World.add` but idempotent -- Symmetric like `World.add` but idempotent
@ -568,13 +625,18 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown)
local record = world.entityIndex.sparse[entity] local record = world.entityIndex.sparse[entity]
local from = record.archetype local from = record.archetype
local to = archetype_traverse_add(world, id, from) local to = archetype_traverse_add(world, id, from)
local idr = world.componentIndex[id]
local has_hooks = bit32.band(idr.flags, ECS_ID_HAS_HOOKS) ~= 0
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 tr = to.records[id] local tr = to.records[id]
from.columns[tr.column][record.row] = data from.columns[tr.column][record.row] = data
invoke_hook(world, EcsOnSet, id, entity, data) if has_hooks then
invoke_hook(world, EcsOnSet, id, entity, data)
end
return return
end end
@ -588,10 +650,12 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown)
end end
end end
local tr = to.records[id] local tr = to.records[id]
to.columns[tr.column][record.row] = data to.columns[tr.column][record.row] = data
invoke_hook(world, EcsOnSet, id, entity, data) if has_hooks then
invoke_hook(world, EcsOnSet, id, entity, data)
end
end end
local function world_component(world: World): i53 local function world_component(world: World): i53
@ -607,7 +671,6 @@ local function world_component(world: World): i53
return id return id
end end
local function archetype_traverse_remove(world: World, id: i53, from: Archetype): Archetype local function archetype_traverse_remove(world: World, id: i53, from: Archetype): Archetype
local edge = edge_ensure(from, id) local edge = edge_ensure(from, id)
@ -658,20 +721,6 @@ local function world_clear(world: World, entity: i53)
entity_move(world.entityIndex, entity, record, ROOT_ARCHETYPE) entity_move(world.entityIndex, entity, record, ROOT_ARCHETYPE)
end end
-- should reuse this logic in World.set instead of swap removing in transition archetype
local function columns_destruct(columns: { Column }, count: number, row: number)
if row == count then
for _, column in columns do
column[count] = nil
end
else
for _, column in columns do
column[row] = column[count]
column[count] = nil
end
end
end
local function archetype_fast_delete_last(world, columns, local function archetype_fast_delete_last(world, columns,
column_count, types, entity) column_count, types, entity)
@ -731,7 +780,6 @@ local function archetype_delete(world: World, archetype,
local idr = component_index[delete] local idr = component_index[delete]
if idr then if idr then
component_index[delete] = nil
local children = {} local children = {}
for archetype_id in idr.cache do for archetype_id in idr.cache do
local idr_archetype = archetypes[archetype_id] local idr_archetype = archetypes[archetype_id]
@ -740,13 +788,19 @@ local function archetype_delete(world: World, archetype,
table.insert(children, child) table.insert(children, child)
end end
end end
for _, child in children do local flags = idr.flags
if world_has_one_inline(world, child, EcsDelete) then if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
for _, child in children do
-- Cascade deletion to children
world_delete(world, child) world_delete(world, child)
else end
else
for _, child in children do
world_remove(world, child, delete) world_remove(world, child, delete)
end end
end end
component_index[delete] = nil
end end
-- TODO: iterate each linked record. -- TODO: iterate each linked record.
@ -793,24 +847,27 @@ local function archetype_delete(world: World, archetype,
continue continue
end end
local relation = ECS_ENTITY_T_HI(id) local id_record = component_index[id]
if world_has_one_inline(world, relation, EcsDelete) then if id_record then
for _, child in children do local flags = id_record.flags
-- Cascade deletions of it has Delete as component trait if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
world_delete(world, child) for _, child in children do
-- Cascade deletions of it has Delete as component trait
world_delete(world, child)
end
end end
else else
local object = ECS_ENTITY_T_LO(id) local object = ECS_ENTITY_T_LO(id)
if object == delete then if object == delete then
local p = ECS_PAIR(relation, object)
for _, child in children do for _, child in children do
world_remove(world, child, p) world_remove(world, child, id)
end end
end end
end end
end end
end end
component_index[o] = nil
end end
end end