mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Implement lookups
This commit is contained in:
parent
582b09be66
commit
b89cd10b7b
2 changed files with 151 additions and 117 deletions
247
lib/init.lua
247
lib/init.lua
|
@ -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,
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in a new issue