mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Add bit flags to id_record (#101)
* Add bit flags to id_record * set should not invoke OnAdd
This commit is contained in:
parent
33f359a150
commit
6d6cc37a25
1 changed files with 197 additions and 140 deletions
317
src/init.luau
317
src/init.luau
|
@ -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,21 +612,31 @@ local function world_add(world: World, entity: i53, id: i53)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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)
|
invoke_hook(world, EcsOnAdd, id, entity)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Symmetric like `World.add` but idempotent
|
-- Symmetric like `World.add` but idempotent
|
||||||
local function world_set(world: World, entity: i53, id: i53, data: unknown)
|
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
|
||||||
|
if has_hooks then
|
||||||
invoke_hook(world, EcsOnSet, id, entity, data)
|
invoke_hook(world, EcsOnSet, id, entity, data)
|
||||||
|
end
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -591,8 +653,10 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown)
|
||||||
local tr = to.records[id]
|
local tr = to.records[id]
|
||||||
to.columns[tr.column][record.row] = data
|
to.columns[tr.column][record.row] = data
|
||||||
|
|
||||||
|
if has_hooks then
|
||||||
invoke_hook(world, EcsOnSet, id, entity, data)
|
invoke_hook(world, EcsOnSet, id, entity, data)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function world_component(world: World): i53
|
local function world_component(world: World): i53
|
||||||
local componentId = world.nextComponentId + 1
|
local componentId = world.nextComponentId + 1
|
||||||
|
@ -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
|
||||||
|
local flags = idr.flags
|
||||||
|
if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
|
||||||
for _, child in children do
|
for _, child in children do
|
||||||
if world_has_one_inline(world, child, EcsDelete) then
|
-- Cascade deletion to children
|
||||||
world_delete(world, child)
|
world_delete(world, child)
|
||||||
|
end
|
||||||
else
|
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
|
||||||
|
local flags = id_record.flags
|
||||||
|
if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
|
||||||
for _, child in children do
|
for _, child in children do
|
||||||
-- Cascade deletions of it has Delete as component trait
|
-- Cascade deletions of it has Delete as component trait
|
||||||
world_delete(world, child)
|
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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue