mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Initial commit
This commit is contained in:
parent
77bbe3e285
commit
ebb81749fb
2 changed files with 168 additions and 130 deletions
253
lib/init.lua
253
lib/init.lua
|
@ -44,12 +44,115 @@ type ArchetypeDiff = {
|
|||
removed: Ty,
|
||||
}
|
||||
|
||||
local FLAGS_PAIR = 0x8
|
||||
local HI_COMPONENT_ID = 256
|
||||
local ON_ADD = HI_COMPONENT_ID + 1
|
||||
local ON_REMOVE = HI_COMPONENT_ID + 2
|
||||
local ON_SET = HI_COMPONENT_ID + 3
|
||||
local WILDCARD = HI_COMPONENT_ID + 4
|
||||
local REST = HI_COMPONENT_ID + 5
|
||||
local REST = HI_COMPONENT_ID + 5
|
||||
|
||||
local ECS_ID_FLAGS_MASK = 0x10
|
||||
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||
|
||||
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 function newId(source: number, target: number)
|
||||
local e = source * 2^28 + target * ECS_ID_FLAGS_MASK
|
||||
return e
|
||||
end
|
||||
|
||||
local function ECS_IS_PAIR(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
|
||||
if source == WILDCARD then
|
||||
id = newId(ECS_PAIR_SECOND(target), WILDCARD)
|
||||
elseif target == WILDCARD then
|
||||
id = newId(WILDCARD, ECS_PAIR_SECOND(source))
|
||||
else
|
||||
id = newId(ECS_PAIR_SECOND(target), ECS_PAIR_SECOND(source))
|
||||
end
|
||||
|
||||
return id + addFlags({ isPair = true })
|
||||
end
|
||||
|
||||
local function getAlive(entityIndex: EntityIndex, id: i53)
|
||||
return entityIndex.dense[id]
|
||||
end
|
||||
|
||||
local function ecs_get_source(entityIndex, e)
|
||||
assert(ECS_IS_PAIR(e))
|
||||
return getAlive(entityIndex, ECS_PAIR_FIRST(e))
|
||||
end
|
||||
local function ecs_get_target(entityIndex, e)
|
||||
assert(ECS_IS_PAIR(e))
|
||||
return getAlive(entityIndex, ECS_PAIR_SECOND(e))
|
||||
end
|
||||
|
||||
local function nextEntityId(entityIndex, index: i24)
|
||||
local id = newId(index, 0)
|
||||
entityIndex.sparse[id] = {
|
||||
dense = index
|
||||
} :: Record
|
||||
entityIndex.dense[index] = id
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function transitionArchetype(
|
||||
entityIndex: EntityIndex,
|
||||
|
@ -158,10 +261,10 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet
|
|||
world.nextArchetypeId = id
|
||||
|
||||
local length = #types
|
||||
local columns = table.create(length) :: {any}
|
||||
local columns = {}
|
||||
|
||||
for index in types do
|
||||
columns[index] = {}
|
||||
for index, componentId in types do
|
||||
table.insert(columns, {})
|
||||
end
|
||||
|
||||
local archetype = {
|
||||
|
@ -204,103 +307,6 @@ function World.new()
|
|||
return self
|
||||
end
|
||||
|
||||
local FLAGS_PAIR = 0x8
|
||||
|
||||
local function ADD_FLAGS(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
|
||||
|
||||
-- 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 NEW_ID(source: number, target: number)
|
||||
local e = source * 2^28 + target * ECS_ID_FLAGS_MASK
|
||||
return e
|
||||
end
|
||||
|
||||
local function ECS_IS_PAIR(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 NEW_ID(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 = NEW_ID(ECS_PAIR_SECOND(target), ECS_PAIR_SECOND(source)) + ADD_FLAGS({ isPair = true })
|
||||
return id
|
||||
end
|
||||
|
||||
local function getAlive(entityIndex: EntityIndex, id: i53)
|
||||
return assert(entityIndex.dense[id], id .. "is not alive")
|
||||
end
|
||||
|
||||
local function ecs_get_source(entityIndex, e)
|
||||
assert(ECS_IS_PAIR(e))
|
||||
return getAlive(entityIndex, ECS_PAIR_FIRST(e))
|
||||
end
|
||||
local function ecs_get_target(entityIndex, e)
|
||||
assert(ECS_IS_PAIR(e))
|
||||
return getAlive(entityIndex, ECS_PAIR_SECOND(e))
|
||||
end
|
||||
|
||||
local function nextEntityId(entityIndex, index: i24)
|
||||
local id = NEW_ID(index, 0)
|
||||
entityIndex.sparse[id] = {
|
||||
dense = index
|
||||
} :: Record
|
||||
entityIndex.dense[index] = id
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
function World.component(world: World)
|
||||
local componentId = world.nextComponentId + 1
|
||||
if componentId > HI_COMPONENT_ID then
|
||||
|
@ -398,19 +404,37 @@ local function findInsert(types: {i53}, toAdd: i53)
|
|||
end
|
||||
|
||||
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
|
||||
local entityIndex = world.entityIndex
|
||||
local types = node.types
|
||||
-- Component IDs are added incrementally, so inserting and sorting
|
||||
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
||||
-- point in the types array.
|
||||
|
||||
local destinationType = table.clone(node.types)
|
||||
local at = findInsert(types, componentId)
|
||||
if at == -1 then
|
||||
-- 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.
|
||||
return node
|
||||
end
|
||||
|
||||
local destinationType = table.clone(node.types)
|
||||
table.insert(destinationType, at, componentId)
|
||||
if ECS_IS_PAIR(componentId) then
|
||||
local source = ECS_PAIR(
|
||||
ecs_get_source(entityIndex, componentId), WILDCARD)
|
||||
local sourceAt = findInsert(destinationType, source)
|
||||
if sourceAt ~= -1 then
|
||||
table.insert(destinationType, sourceAt, source)
|
||||
end
|
||||
|
||||
local target = ECS_PAIR(
|
||||
WILDCARD, ecs_get_target(entityIndex, componentId))
|
||||
|
||||
local targetAt = findInsert(destinationType, target)
|
||||
if targetAt ~= -1 then
|
||||
table.insert(destinationType, targetAt, target)
|
||||
end
|
||||
end
|
||||
|
||||
return ensureArchetype(world, destinationType, node)
|
||||
end
|
||||
|
||||
|
@ -583,7 +607,6 @@ function World.query(world: World, ...: i53): Query
|
|||
|
||||
local firstArchetypeMap
|
||||
local componentIndex = world.componentIndex
|
||||
local entityIndex = world.entityIndex
|
||||
|
||||
for _, componentId in components do
|
||||
local map = componentIndex[componentId]
|
||||
|
@ -603,22 +626,6 @@ function World.query(world: World, ...: i53): Query
|
|||
local skip = false
|
||||
|
||||
for i, componentId in components do
|
||||
if ECS_IS_PAIR(componentId) then
|
||||
local source = ecs_get_source(entityIndex, componentId)
|
||||
local target = ecs_get_target(entityIndex, componentId)
|
||||
|
||||
if target == WILDCARD then
|
||||
local matched = false
|
||||
for c in archetypeRecords do
|
||||
if ecs_get_source(entityIndex, c) == source then
|
||||
matched = true
|
||||
end
|
||||
end
|
||||
if matched then
|
||||
fr
|
||||
end
|
||||
end
|
||||
end
|
||||
local index = archetypeRecords[componentId]
|
||||
if not index then
|
||||
skip = true
|
||||
|
@ -785,11 +792,13 @@ return table.freeze({
|
|||
ON_REMOVE = ON_REMOVE;
|
||||
ON_SET = ON_SET;
|
||||
ECS_ID = ECS_ID,
|
||||
ECS_IS_PAIR = ECS_IS_PAIR,
|
||||
IS_PAIR = ECS_IS_PAIR,
|
||||
ECS_PAIR = ECS_PAIR,
|
||||
ECS_GENERATION = ECS_GENERATION,
|
||||
ECS_GENERATION_INC = ECS_GENERATION_INC,
|
||||
getAlive = getAlive,
|
||||
ecs_get_target = ecs_get_target,
|
||||
ecs_get_source = ecs_get_source
|
||||
ecs_get_source = ecs_get_source,
|
||||
Wildcard = WILDCARD,
|
||||
REST = REST
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ local testkit = require("../testkit")
|
|||
local jecs = require("../lib/init")
|
||||
local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION
|
||||
local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC
|
||||
local IS_PAIR = jecs.ECS_IS_PAIR
|
||||
local IS_PAIR = jecs.IS_PAIR
|
||||
local ECS_PAIR = jecs.ECS_PAIR
|
||||
local getAlive = jecs.getAlive
|
||||
local ecs_get_source = jecs.ecs_get_source
|
||||
|
@ -14,6 +14,7 @@ local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
|||
local N = 10
|
||||
|
||||
TEST("world", function()
|
||||
--[[
|
||||
do CASE "should be iterable"
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
|
@ -48,7 +49,6 @@ TEST("world", function()
|
|||
end
|
||||
|
||||
do CASE "should query all matching entities"
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
@ -71,7 +71,6 @@ TEST("world", function()
|
|||
end
|
||||
|
||||
do CASE "should query all matching entities when irrelevant component is removed"
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
@ -99,7 +98,6 @@ TEST("world", function()
|
|||
end
|
||||
|
||||
do CASE "should query all entities without B"
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
@ -171,14 +169,13 @@ TEST("world", function()
|
|||
world:remove(id, Poison)
|
||||
|
||||
CHECK(world:get(id, Poison) == nil)
|
||||
print(world:get(id, Health))
|
||||
CHECK(world:get(id, Health) == 50)
|
||||
end
|
||||
|
||||
do CASE "should increment generation"
|
||||
local world = jecs.World.new()
|
||||
local e = world:entity()
|
||||
CHECK(ECS_ID(e) == 1 + REST)
|
||||
CHECK(ECS_ID(e) == 1 + jecs.REST)
|
||||
CHECK(getAlive(world.entityIndex, ECS_ID(e)) == e)
|
||||
CHECK(ECS_GENERATION(e) == 0) -- 0
|
||||
e = ECS_GENERATION_INC(e)
|
||||
|
@ -190,8 +187,8 @@ TEST("world", function()
|
|||
local _e = world:entity()
|
||||
local e2 = world:entity()
|
||||
local e3 = world:entity()
|
||||
CHECK(ECS_ID(e2) == 2 + REST)
|
||||
CHECK(ECS_ID(e3) == 3 + REST)
|
||||
CHECK(ECS_ID(e2) == 2 +jecs.REST)
|
||||
CHECK(ECS_ID(e3) == 3 + jecs.REST)
|
||||
CHECK(ECS_GENERATION(e2) == 0)
|
||||
CHECK(ECS_GENERATION(e3) == 0)
|
||||
|
||||
|
@ -203,6 +200,38 @@ TEST("world", function()
|
|||
CHECK(ecs_get_target(world.entityIndex, pair) == e3)
|
||||
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 allow wildcards in queries"
|
||||
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)
|
||||
--testkit.print(world.componentIndex)
|
||||
|
||||
local w = jecs.Wildcard
|
||||
for e, bool in world:query(ECS_PAIR(Eats, w)) do
|
||||
CHECK(e == bob)
|
||||
CHECK(bool)
|
||||
end
|
||||
for e, bool in world:query(ECS_PAIR(w, Apples)) do
|
||||
CHECK(e == bob)
|
||||
CHECK(bool)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
FINISH()
|
Loading…
Reference in a new issue