Change casing

This commit is contained in:
Ukendio 2024-07-15 20:29:06 +02:00
parent 1b6cdd4791
commit 459e670ce9
5 changed files with 319 additions and 158 deletions

View file

@ -2,6 +2,5 @@
"aliases": {
"jecs": "src",
"testkit": "testkit",
"mirror": "mirror"
}
}

161
benches/cached.luau Normal file
View file

@ -0,0 +1,161 @@
local jecs = require("@jecs")
local mirror = require("../mirror/init")
type i53 = number
do
TITLE(testkit.color.white_underline("Jecs query"))
local ecs = jecs.World.new()
do
TITLE("one component in common")
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
BENCH("4 component", function()
for _ in world:query(D, C, B, A) do
end
end)
end
local D1 = ecs:component()
local D2 = ecs:component()
local D3 = ecs:component()
local D4 = ecs:component()
local D5 = ecs:component()
local D6 = ecs:component()
local D7 = ecs:component()
local D8 = ecs:component()
local function flip()
return math.random() >= 0.15
end
local added = 0
local archetypes = {}
for i = 1, 2 ^ 16 - 2 do
local entity = ecs:entity()
local combination = ""
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
end
archetypes[combination] = true
end
local a = 0
for _ in archetypes do
a += 1
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
end
end
do
TITLE(testkit.color.white_underline("Mirror query"))
local ecs = mirror.World.new()
do
TITLE("one component in common")
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
BENCH("4 component", function()
for _ in world:query(D, C, B, A) do
end
end)
end
local D1 = ecs:component()
local D2 = ecs:component()
local D3 = ecs:component()
local D4 = ecs:component()
local D5 = ecs:component()
local D6 = ecs:component()
local D7 = ecs:component()
local D8 = ecs:component()
local function flip()
return math.random() >= 0.15
end
local added = 0
local archetypes = {}
for i = 1, 2 ^ 16 - 2 do
local entity = ecs:entity()
local combination = ""
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
end
archetypes[combination] = true
end
local a = 0
for _ in archetypes do
a += 1
end
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
end
end

View file

@ -170,30 +170,16 @@ return {
end,
Functions = {
Mirror = function()
local matched = 0
for entityId, firstComponent in mcs:query(E1, E4, E6, E8) do
matched += 1
end
end,
Matter = function()
local matched = 0
for entityId, firstComponent in newWorld:query(A1, A4, A6, A8) do
matched += 1
end
end,
ECR = function()
local matched = 0
for entityId, firstComponent in registry2:view(B1, B4, B6, B8) do
for entityId, firstComponent in registry2:view(B1, B4) do
matched += 1
end
end,
Jecs = function()
local matched = 0
for entityId, firstComponent in ecs:query(D1, D4, D6, D8) do
for entityId, firstComponent in ecs:query(D1, D4) do
matched += 1
end

View file

@ -142,7 +142,7 @@ end
local ERROR_ENTITY_NOT_ALIVE = "Entity is not alive"
local ERROR_GENERATION_INVALID = "INVALID GENERATION"
local function getAlive(index: EntityIndex, e: i24): i53
local function entity_index_get_alive(index: EntityIndex, e: i24): i53
local denseArray = index.dense
local id = denseArray[ECS_ENTITY_T_LO(e)]
@ -159,18 +159,18 @@ local function getAlive(index: EntityIndex, e: i24): i53
error(ERROR_ENTITY_NOT_ALIVE)
end
local function sparseGet(entityIndex, id)
return entityIndex.sparse[getAlive(entityIndex, id)]
local function entity_index_sparse_get(entityIndex, id)
return entityIndex.sparse[entity_index_get_alive(entityIndex, id)]
end
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
local function ECS_PAIR_RELATION(entityIndex, e)
return getAlive(entityIndex, ECS_ENTITY_T_HI(e))
local function ecs_pair_relation(entityIndex, e)
return entity_index_get_alive(entityIndex, ECS_ENTITY_T_HI(e))
end
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
local function ECS_PAIR_OBJECT(entityIndex, e)
return getAlive(entityIndex, ECS_ENTITY_T_LO(e))
local function ecs_pair_object(entityIndex, e)
return entity_index_get_alive(entityIndex, ECS_ENTITY_T_LO(e))
end
local function entity_index_new_id(entityIndex: EntityIndex, index: i24): i53
@ -300,8 +300,8 @@ local function archetype_of(world: any, types: { i24 }, prev: Archetype?): Arche
idr.size += 1
records[componentId] = i
if ECS_IS_PAIR(componentId) then
local relation = ECS_PAIR_RELATION(world.entityIndex, componentId)
local object = ECS_PAIR_OBJECT(world.entityIndex, componentId)
local relation = ecs_pair_relation(world.entityIndex, componentId)
local object = ecs_pair_object(world.entityIndex, componentId)
local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = id_record_ensure(componentIndex, r)
@ -375,7 +375,7 @@ local function world_target(world: World, entity: i53, relation: i24--[[, nth: n
return nil
end
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord])
return ecs_pair_object(entityIndex, archetype.types[archetypeRecord])
end
local function world_parent(world: World, entity: i53)
@ -408,22 +408,22 @@ local function find_insert(types: { i53 }, toAdd: i53): number
return #types + 1
end
local function find_archetype_with(world: World, node: Archetype, componentId: i53): Archetype
local function find_archetype_with(world: World, node: Archetype, id: i53): Archetype
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) :: { i53 }
local at = find_insert(types, componentId)
local dst_type = table.clone(node.types) :: { i53 }
local at = find_insert(types, id)
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
table.insert(destinationType, at, componentId)
table.insert(dst_type, at, id)
return archetype_ensure(world, destinationType, node)
return archetype_ensure(world, dst_type, node)
end
local function edge_ensure(archetype: Archetype, componentId: i53): ArchetypeEdge
@ -624,42 +624,49 @@ local function world_clear(world: World, entityId: i53)
entity_move(world.entityIndex, entityId, record, ROOT_ARCHETYPE)
end
-- Keeping the function as small as possible to enable inlining
local function fetch(record: Record, componentId: i24): any
local archetype = record.archetype
if not archetype then
return nil
end
local world_get: (world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) -> (...any)
do
-- Keeping the function as small as possible to enable inlining
local function fetch(id: i24, records, columns, row): any
local tr = records[id]
local archetypeRecord = archetype.records[componentId]
if not tr then
return nil
end
if not archetypeRecord then
return nil
end
return columns[tr][row]
end
return archetype.columns[archetypeRecord][record.row]
end
function world_get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
local id = entityId
local record = world.entityIndex.sparse[id]
if not record then
return nil
end
local function world_get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
local id = entityId
local record = world.entityIndex.sparse[id]
if not record then
return nil
end
local archetype = record.archetype
if not archetype then
return nil
end
local va = fetch(record, a)
local records = archetype.records
local columns = archetype.records
local row = record.row
if b == nil then
return va
elseif c == nil then
return va, fetch(record, b)
elseif d == nil then
return va, fetch(record, b), fetch(record, c)
elseif e == nil then
return va, fetch(record, b), fetch(record, c), fetch(record, d)
else
error("args exceeded")
end
local va = fetch(a, tr, columns, row)
if b == nil then
return va
elseif c == nil then
return va, fetch(b, tr, columns, row)
elseif d == nil then
return va, fetch(b, tr, columns, row), fetch(c, tr, columns, row)
elseif e == nil then
return va, fetch(b, tr, columns, row), fetch(c, tr, columns, row), fetch(d, tr, columns, row)
else
error("args exceeded")
end
end
end
type Item = () -> (number, ...any)
@ -848,58 +855,66 @@ do
end
end
local cache
function world_query(world: World, ...: number): Query
-- breaking?
if (...) == nil then
error("Missing components")
end
indices = {}
compatibleArchetypes = {}
length = 0
components = { ... }
local archetypes: { Archetype } = world.archetypes :: any
local firstArchetypeMap: ArchetypeMap
local componentIndex = world.componentIndex
if cache then
compatibleArchetypes = cache
else
compatibleArchetypes = {}
local archetypes: { Archetype } = world.archetypes :: any
local firstArchetypeMap: ArchetypeMap
local componentIndex = world.componentIndex
for _, componentId in components do
local map: ArchetypeMap = componentIndex[componentId] :: any
if not map then
return EmptyQuery
end
for _, componentId in components do
local map: ArchetypeMap = componentIndex[componentId] :: any
if not map then
return EmptyQuery
end
if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then
firstArchetypeMap = map
end
end
if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then
firstArchetypeMap = map
end
end
for id in firstArchetypeMap.cache do
local compatibleArchetype = archetypes[id]
local archetypeRecords = compatibleArchetype.records
for id in firstArchetypeMap.cache do
local compatibleArchetype = archetypes[id]
local archetypeRecords = compatibleArchetype.records
local records: { number } = {}
local skip = false
local records: { number } = {}
local skip = false
for i, componentId in components do
local index = archetypeRecords[componentId]
if not index then
skip = true
break
end
-- index should be index.offset
records[i] = index
end
for i, componentId in components do
local index = archetypeRecords[componentId]
if not index then
skip = true
break
end
-- index should be index.offset
records[i] = index
end
if skip then
continue
end
if skip then
continue
end
length += 1
compatibleArchetypes[length] = compatibleArchetype
indices[length] = records
end
length += 1
compatibleArchetypes[length] = compatibleArchetype
indices[length] = records
end
cache = compatibleArchetypes
end
lastArchetype = 1
archetype = compatibleArchetypes[lastArchetype]
@ -1113,14 +1128,15 @@ return {
Rest = EcsRest,
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,
-- Inwards facing API for testing
IS_PAIR = ECS_IS_PAIR,
ECS_ID = ECS_ENTITY_T_LO,
ECS_PAIR = ECS_PAIR,
ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION,
ECS_PAIR_RELATION = ECS_PAIR_RELATION,
ECS_PAIR_OBJECT = ECS_PAIR_OBJECT,
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,
getAlive = getAlive,
ecs_pair_relation = ecs_pair_relation,
ecs_pair_object = ecs_pair_object,
entity_index_get_alive = entity_index_get_alive,
}

View file

@ -4,10 +4,10 @@ local __ = jecs.Wildcard
local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION
local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC
local IS_PAIR = jecs.IS_PAIR
local ECS_PAIR = jecs.ECS_PAIR
local getAlive = jecs.getAlive
local ECS_PAIR_RELATION = jecs.ECS_PAIR_RELATION
local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT
local pair = jecs.pair
local getAlive = jecs.entity_index_get_alive
local ecs_pair_relation = jecs.ecs_pair_relation
local ecs_pair_object = jecs.ecs_pair_object
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
@ -256,11 +256,11 @@ TEST("world", function()
CHECK(IS_PAIR(world:entity()) == false)
local pair = ECS_PAIR(e2, e3)
local pair = pair(e2, e3)
CHECK(IS_PAIR(pair) == true)
CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2)
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
CHECK(ecs_pair_relation(world.entityIndex, pair) == e2)
CHECK(ecs_pair_object(world.entityIndex, pair) == e3)
end
do CASE("should allow querying for relations")
@ -269,8 +269,8 @@ TEST("world", function()
local Apples = world:entity()
local bob = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), true)
for e, bool in world:query(ECS_PAIR(Eats, Apples)) do
world:set(bob, pair(Eats, Apples), true)
for e, bool in world:query(pair(Eats, Apples)) do
CHECK(e == bob)
CHECK(bool)
end
@ -282,14 +282,14 @@ TEST("world", function()
local Apples = world:entity()
local bob = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
world:set(bob, pair(Eats, Apples), "bob eats apples")
local w = jecs.Wildcard
for e, data in world:query(ECS_PAIR(Eats, w)) do
for e, data in world:query(pair(Eats, w)) do
CHECK(e == bob)
CHECK(data == "bob eats apples")
end
for e, data in world:query(ECS_PAIR(w, Apples)) do
for e, data in world:query(pair(w, Apples)) do
CHECK(e == bob)
CHECK(data == "bob eats apples")
end
@ -303,12 +303,12 @@ TEST("world", function()
local bob = world:entity()
local alice = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges")
world:set(bob, pair(Eats, Apples), "bob eats apples")
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
local w = jecs.Wildcard
local count = 0
for e, data in world:query(ECS_PAIR(Eats, w)) do
for e, data in world:query(pair(Eats, w)) do
count += 1
if e == bob then
CHECK(data == "bob eats apples")
@ -320,7 +320,7 @@ TEST("world", function()
CHECK(count == 2)
count = 0
for e, data in world:query(ECS_PAIR(w, Apples)) do
for e, data in world:query(pair(w, Apples)) do
count += 1
CHECK(data == "bob eats apples")
end
@ -337,21 +337,21 @@ TEST("world", function()
local alice = world:entity()
world:set(bob, Apples, "apples")
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges")
world:set(bob, pair(Eats, Apples), "bob eats apples")
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
world:delete(Apples)
local Wildcard = jecs.Wildcard
local count = 0
for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do
for _, data in world:query(pair(Wildcard, Apples)) do
count += 1
end
world:delete(ECS_PAIR(Eats, Apples))
world:delete(pair(Eats, Apples))
CHECK(count == 0)
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
CHECK(world:get(bob, pair(Eats, Apples)) == nil)
end
do CASE("should error when setting invalid pair")
@ -362,13 +362,12 @@ TEST("world", function()
world:delete(Apples)
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
world:set(bob, pair(Eats, Apples), "bob eats apples")
end
do CASE("should find target for ChildOf")
local world = jecs.World.new()
local ChildOf = jecs.ChildOf
local pair = ECS_PAIR
local Name = world:component()
@ -383,7 +382,7 @@ TEST("world", function()
CHECK(world:parent(bob) == alice) -- O(1)
local count = 0
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
for _, name in world:query(Name, pair(ChildOf, alice)) do
count += 1
end
CHECK(count == 2)
@ -521,11 +520,11 @@ TEST("world", function()
local Bob = world:component()
local helloBob = world:entity()
world:add(helloBob, ECS_PAIR(Hello, Bob))
world:add(helloBob, pair(Hello, Bob))
world:add(helloBob, Bob)
local withoutCount = 0
for _ in world:query(ECS_PAIR(Hello, Bob)):without(Bob) do
for _ in world:query(pair(Hello, Bob)):without(Bob) do
withoutCount += 1
end