Implement lookups

This commit is contained in:
Ukendio 2024-05-12 04:50:14 +02:00
parent 582b09be66
commit b89cd10b7b
2 changed files with 151 additions and 117 deletions

View file

@ -50,6 +50,90 @@ local ON_REMOVE = HI_COMPONENT_ID + 2
local ON_SET = HI_COMPONENT_ID + 3 local ON_SET = HI_COMPONENT_ID + 3
local REST = HI_COMPONENT_ID + 4 local REST = HI_COMPONENT_ID + 4
local FLAGS_PAIR = 0x8
local function addFlags(flags)
local typeFlags = 0x0
if flags.isPair then
typeFlags = bit32.bor(typeFlags, FLAGS_PAIR) -- HIGHEST bit in the ID.
end
if false then
typeFlags = bit32.bor(typeFlags, 0x4) -- Set the second flag to true
end
if false then
typeFlags = bit32.bor(typeFlags, 0x2) -- Set the third flag to true
end
if false then
typeFlags = bit32.bor(typeFlags, 0x1) -- LAST BIT in the ID.
end
return typeFlags
end
local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
local function newId(source: number, target: number)
local e = source * 2^28 + target * ECS_ID_FLAGS_MASK
return e
end
local function isPair(e: number)
return (e % 2^4) // FLAGS_PAIR ~= 0
end
function separate(entity: number)
local _typeFlags = entity % 0x10
entity //= ECS_ID_FLAGS_MASK
return entity // ECS_ENTITY_MASK, entity % ECS_GENERATION_MASK, _typeFlags
end
-- HIGH 24 bits LOW 24 bits
local function ECS_GENERATION(e: i53)
e //= 0x10
return e % ECS_GENERATION_MASK
end
local function ECS_ID(e: i53)
e //= 0x10
return e // ECS_ENTITY_MASK
end
local function ECS_GENERATION_INC(e: i53)
local id, generation, flags = separate(e)
return newId(id, generation + 1) + flags
end
-- gets the high ID
local function ECS_PAIR_FIRST(entity: i53): i24
entity //= 0x10
local first = entity % ECS_ENTITY_MASK
return first
end
-- gets the low ID
local ECS_PAIR_SECOND = ECS_ID
local function ECS_PAIR(source: number, target: number)
local id = newId(ECS_PAIR_SECOND(target), ECS_PAIR_SECOND(source)) + addFlags({ isPair = true })
return id
end
local function getAlive(entityIndex: EntityIndex, id: i53)
return entityIndex.dense[id]
end
local function ecs_get_source(entityIndex, e)
assert(isPair(e))
return getAlive(entityIndex, ECS_PAIR_FIRST(e))
end
local function ecs_get_target(entityIndex, e)
assert(isPair(e))
return getAlive(entityIndex, ECS_PAIR_SECOND(e))
end
local function transitionArchetype( local function transitionArchetype(
entityIndex: EntityIndex, entityIndex: EntityIndex,
to: Archetype, to: Archetype,
@ -164,13 +248,13 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet
end end
local archetype = { local archetype = {
columns = columns; columns = columns,
edges = {}; edges = {},
entities = {}; entities = {},
id = id; id = id,
records = {}; records = {},
type = ty; type = ty,
types = types; types = types,
} }
world.archetypeIndex[ty] = archetype world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype world.archetypes[id] = archetype
@ -185,123 +269,46 @@ local World = {}
World.__index = World World.__index = World
function World.new() function World.new()
local self = setmetatable({ local self = setmetatable({
archetypeIndex = {}; archetypeIndex = {},
archetypes = {}; archetypes = {},
componentIndex = {}; componentIndex = {},
entityIndex = { entityIndex = {
dense = {}, dense = {},
sparse = {} sparse = {}
} :: EntityIndex; } :: EntityIndex,
hooks = { hooks = {
[ON_ADD] = {}; [ON_ADD] = {},
}; },
nextArchetypeId = 0; nextArchetypeId = 0,
nextComponentId = 0; nextComponentId = 0,
nextEntityId = 0; nextEntityId = 0,
ROOT_ARCHETYPE = (nil :: any) :: Archetype; ROOT_ARCHETYPE = (nil :: any) :: Archetype,
entityLookup = {
id = {},
name = {}
}
}, World) }, World)
return self return self
end end
local FLAGS_PAIR = 0x8 type World = typeof(World.new())
local function addFlags(flags) local function nextEntityId(world: World, index: i24, name: string?)
local typeFlags = 0x0 local entityIndex = world.entityIndex
if flags.isPair then
typeFlags = bit32.bor(typeFlags, FLAGS_PAIR) -- HIGHEST bit in the ID.
end
if false then
typeFlags = bit32.bor(typeFlags, 0x4) -- Set the second flag to true
end
if false then
typeFlags = bit32.bor(typeFlags, 0x2) -- Set the third flag to true
end
if false then
typeFlags = bit32.bor(typeFlags, 0x1) -- LAST BIT in the ID.
end
return typeFlags
end
local ECS_ID_FLAGS_MASK = 0x10
-- ECS_ENTITY_MASK (0xFFFFFFFFull << 28)
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
-- ECS_GENERATION_MASK (0xFFFFull << 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
local function newId(source: number, target: number)
local e = source * 2^28 + target * ECS_ID_FLAGS_MASK
return e
end
local function isPair(e: number)
return (e % 2^4) // FLAGS_PAIR ~= 0
end
function separate(entity: number)
local _typeFlags = entity % 0x10
entity //= ECS_ID_FLAGS_MASK
return entity // ECS_ENTITY_MASK, entity % ECS_GENERATION_MASK, _typeFlags
end
-- HIGH 24 bits LOW 24 bits
local function ECS_GENERATION(e: i53)
e //= 0x10
return e % ECS_GENERATION_MASK
end
local function ECS_ID(e: i53)
e //= 0x10
return e // ECS_ENTITY_MASK
end
local function ECS_GENERATION_INC(e: i53)
local id, generation, flags = separate(e)
return newId(id, generation + 1) + flags
end
-- gets the high ID
local function ECS_PAIR_FIRST(entity: i53): i24
entity //= 0x10
local first = entity % ECS_ENTITY_MASK
return first
end
-- gets the low ID
local ECS_PAIR_SECOND = ECS_ID
local function ECS_PAIR(source: number, target: number)
local id = newId(ECS_PAIR_SECOND(target), ECS_PAIR_SECOND(source)) + addFlags({ isPair = true })
return id
end
local function getAlive(entityIndex: EntityIndex, id: i53)
return entityIndex.dense[id]
end
local function ecs_get_source(entityIndex, e)
assert(isPair(e))
return getAlive(entityIndex, ECS_PAIR_FIRST(e))
end
local function ecs_get_target(entityIndex, e)
assert(isPair(e))
return getAlive(entityIndex, ECS_PAIR_SECOND(e))
end
local function nextEntityId(entityIndex, index: i24)
local id = newId(index, 0) local id = newId(index, 0)
entityIndex.sparse[id] = { entityIndex.sparse[id] = {
dense = index dense = index
} :: Record } :: Record
entityIndex.dense[index] = id entityIndex.dense[index] = id
if name then
world.entityLookup.id[id] = name
world.entityLookup.name[name] = id
end
return id return id
end end
function World.component(world: World) function World.component(world: World, name: string?)
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,
@ -309,13 +316,21 @@ function World.component(world: World)
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
return nextEntityId(world.entityIndex, componentId) return nextEntityId(world, componentId, name)
end end
function World.entity(world: World) function World.entity(world: World, name: string?)
local entityId = world.nextEntityId + 1 local entityId = world.nextEntityId + 1
world.nextEntityId = entityId world.nextEntityId = entityId
return nextEntityId(world.entityIndex, entityId + REST) return nextEntityId(world, entityId + REST, name)
end
function World.lookup(world: World, name: string): i53
return world.entityLookup.name[name]
end
function World.name(world: World, entity: i53): string
return world.entityLookup.id[entity]
end end
-- should reuse this logic in World.set instead of swap removing in transition archetype -- should reuse this logic in World.set instead of swap removing in transition archetype
@ -369,8 +384,6 @@ function World.delete(world: World, entityId: i53)
archetypeDelete(entityIndex, record, entityId, true) archetypeDelete(entityIndex, record, entityId, true)
end end
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
@ -560,8 +573,8 @@ local function noop(_self: Query, ...: i53): () -> (number, ...any)
end end
local EmptyQuery = { local EmptyQuery = {
__iter = noop; __iter = noop,
without = noop; without = noop,
} }
EmptyQuery.__index = EmptyQuery EmptyQuery.__index = EmptyQuery
setmetatable(EmptyQuery, EmptyQuery) setmetatable(EmptyQuery, EmptyQuery)
@ -763,10 +776,10 @@ function World.__iter(world: World): () -> (number?, unknown?)
end end
return table.freeze({ return table.freeze({
World = World; World = World,
ON_ADD = ON_ADD; ON_ADD = ON_ADD,
ON_REMOVE = ON_REMOVE; ON_REMOVE = ON_REMOVE,
ON_SET = ON_SET; ON_SET = ON_SET,
ECS_ID = ECS_ID, ECS_ID = ECS_ID,
IS_PAIR = isPair, IS_PAIR = isPair,
ECS_PAIR = ECS_PAIR, ECS_PAIR = ECS_PAIR,

View file

@ -203,6 +203,27 @@ TEST("world", function()
CHECK(ecs_get_target(world.entityIndex, pair) == e3) CHECK(ecs_get_target(world.entityIndex, pair) == e3)
end end
do CASE "should allow querying for relations"
local world = jecs.World.new()
local Eats = world:entity()
local Apples = world:entity()
local bob = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), true)
for e in world:query(ECS_PAIR(Eats, Apples)) do
CHECK(e == bob)
end
end
do CASE "should lookup entity name by id"
local world = jecs.World.new()
local Eats = world:entity("Eats")
local Apples = world:entity("Apples")
CHECK(world:lookup("Eats") == Eats)
CHECK(world:name(Apples) == "Apples")
end
end) end)
FINISH() FINISH()