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": { "aliases": {
"jecs": "src", "jecs": "src",
"testkit": "testkit", "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, end,
Functions = { 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() ECR = function()
local matched = 0 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 matched += 1
end end
end, end,
Jecs = function() Jecs = function()
local matched = 0 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 matched += 1
end end

View file

@ -142,7 +142,7 @@ end
local ERROR_ENTITY_NOT_ALIVE = "Entity is not alive" local ERROR_ENTITY_NOT_ALIVE = "Entity is not alive"
local ERROR_GENERATION_INVALID = "INVALID GENERATION" 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 denseArray = index.dense
local id = denseArray[ECS_ENTITY_T_LO(e)] 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) error(ERROR_ENTITY_NOT_ALIVE)
end end
local function sparseGet(entityIndex, id) local function entity_index_sparse_get(entityIndex, id)
return entityIndex.sparse[getAlive(entityIndex, id)] return entityIndex.sparse[entity_index_get_alive(entityIndex, id)]
end end
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits -- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
local function ECS_PAIR_RELATION(entityIndex, e) local function ecs_pair_relation(entityIndex, e)
return getAlive(entityIndex, ECS_ENTITY_T_HI(e)) return entity_index_get_alive(entityIndex, ECS_ENTITY_T_HI(e))
end end
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits -- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
local function ECS_PAIR_OBJECT(entityIndex, e) local function ecs_pair_object(entityIndex, e)
return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) return entity_index_get_alive(entityIndex, ECS_ENTITY_T_LO(e))
end end
local function entity_index_new_id(entityIndex: EntityIndex, index: i24): i53 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 idr.size += 1
records[componentId] = i records[componentId] = i
if ECS_IS_PAIR(componentId) then if ECS_IS_PAIR(componentId) then
local relation = ECS_PAIR_RELATION(world.entityIndex, componentId) local relation = ecs_pair_relation(world.entityIndex, componentId)
local object = ECS_PAIR_OBJECT(world.entityIndex, componentId) local object = ecs_pair_object(world.entityIndex, componentId)
local r = ECS_PAIR(relation, EcsWildcard) local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = id_record_ensure(componentIndex, r) 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 return nil
end end
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord]) return ecs_pair_object(entityIndex, archetype.types[archetypeRecord])
end end
local function world_parent(world: World, entity: i53) local function world_parent(world: World, entity: i53)
@ -408,22 +408,22 @@ local function find_insert(types: { i53 }, toAdd: i53): number
return #types + 1 return #types + 1
end 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 local types = node.types
-- Component IDs are added incrementally, so inserting and sorting -- Component IDs are added incrementally, so inserting and sorting
-- them each time would be expensive. Instead this insertion sort can find the insertion -- them each time would be expensive. Instead this insertion sort can find the insertion
-- point in the types array. -- point in the types array.
local destinationType = table.clone(node.types) :: { i53 } local dst_type = table.clone(node.types) :: { i53 }
local at = find_insert(types, componentId) local at = find_insert(types, id)
if at == -1 then if at == -1 then
-- If it finds a duplicate, it just means it is the same archetype so it can return it -- 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. -- directly instead of needing to hash types for a lookup to the archetype.
return node return node
end 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 end
local function edge_ensure(archetype: Archetype, componentId: i53): ArchetypeEdge local function edge_ensure(archetype: Archetype, componentId: i53): ArchetypeEdge
@ -624,43 +624,50 @@ local function world_clear(world: World, entityId: i53)
entity_move(world.entityIndex, entityId, record, ROOT_ARCHETYPE) entity_move(world.entityIndex, entityId, record, ROOT_ARCHETYPE)
end 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 -- Keeping the function as small as possible to enable inlining
local function fetch(record: Record, componentId: i24): any local function fetch(id: i24, records, columns, row): any
local archetype = record.archetype local tr = records[id]
if not archetype then
if not tr then
return nil return nil
end end
local archetypeRecord = archetype.records[componentId] return columns[tr][row]
if not archetypeRecord then
return nil
end end
return archetype.columns[archetypeRecord][record.row] function world_get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
end
local function world_get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
local id = entityId local id = entityId
local record = world.entityIndex.sparse[id] local record = world.entityIndex.sparse[id]
if not record then if not record then
return nil return nil
end end
local va = fetch(record, a) local archetype = record.archetype
if not archetype then
return nil
end
local records = archetype.records
local columns = archetype.records
local row = record.row
local va = fetch(a, tr, columns, row)
if b == nil then if b == nil then
return va return va
elseif c == nil then elseif c == nil then
return va, fetch(record, b) return va, fetch(b, tr, columns, row)
elseif d == nil then elseif d == nil then
return va, fetch(record, b), fetch(record, c) return va, fetch(b, tr, columns, row), fetch(c, tr, columns, row)
elseif e == nil then elseif e == nil then
return va, fetch(record, b), fetch(record, c), fetch(record, d) return va, fetch(b, tr, columns, row), fetch(c, tr, columns, row), fetch(d, tr, columns, row)
else else
error("args exceeded") error("args exceeded")
end end
end end
end
type Item = () -> (number, ...any) type Item = () -> (number, ...any)
export type Query = typeof({ export type Query = typeof({
@ -848,17 +855,23 @@ do
end end
end end
local cache
function world_query(world: World, ...: number): Query function world_query(world: World, ...: number): Query
-- breaking? -- breaking?
if (...) == nil then if (...) == nil then
error("Missing components") error("Missing components")
end end
indices = {} indices = {}
compatibleArchetypes = {}
length = 0 length = 0
components = { ... } components = { ... }
if cache then
compatibleArchetypes = cache
else
compatibleArchetypes = {}
local archetypes: { Archetype } = world.archetypes :: any local archetypes: { Archetype } = world.archetypes :: any
local firstArchetypeMap: ArchetypeMap local firstArchetypeMap: ArchetypeMap
local componentIndex = world.componentIndex local componentIndex = world.componentIndex
@ -900,6 +913,8 @@ do
indices[length] = records indices[length] = records
end end
cache = compatibleArchetypes
end
lastArchetype = 1 lastArchetype = 1
archetype = compatibleArchetypes[lastArchetype] archetype = compatibleArchetypes[lastArchetype]
@ -1113,14 +1128,15 @@ return {
Rest = EcsRest, Rest = EcsRest,
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,
-- Inwards facing API for testing
IS_PAIR = ECS_IS_PAIR, IS_PAIR = ECS_IS_PAIR,
ECS_ID = ECS_ENTITY_T_LO, ECS_ID = ECS_ENTITY_T_LO,
ECS_PAIR = ECS_PAIR,
ECS_GENERATION_INC = ECS_GENERATION_INC, ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION, 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, ecs_pair_relation = ecs_pair_relation,
getAlive = getAlive, 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_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION
local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC
local IS_PAIR = jecs.IS_PAIR local IS_PAIR = jecs.IS_PAIR
local ECS_PAIR = jecs.ECS_PAIR local pair = jecs.pair
local getAlive = jecs.getAlive local getAlive = jecs.entity_index_get_alive
local ECS_PAIR_RELATION = jecs.ECS_PAIR_RELATION local ecs_pair_relation = jecs.ecs_pair_relation
local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT local ecs_pair_object = jecs.ecs_pair_object
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test() local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...) local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
@ -256,11 +256,11 @@ TEST("world", function()
CHECK(IS_PAIR(world:entity()) == false) CHECK(IS_PAIR(world:entity()) == false)
local pair = ECS_PAIR(e2, e3) local pair = pair(e2, e3)
CHECK(IS_PAIR(pair) == true) CHECK(IS_PAIR(pair) == true)
CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2) CHECK(ecs_pair_relation(world.entityIndex, pair) == e2)
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3) CHECK(ecs_pair_object(world.entityIndex, pair) == e3)
end end
do CASE("should allow querying for relations") do CASE("should allow querying for relations")
@ -269,8 +269,8 @@ TEST("world", function()
local Apples = world:entity() local Apples = world:entity()
local bob = world:entity() local bob = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), true) world:set(bob, pair(Eats, Apples), true)
for e, bool in world:query(ECS_PAIR(Eats, Apples)) do for e, bool in world:query(pair(Eats, Apples)) do
CHECK(e == bob) CHECK(e == bob)
CHECK(bool) CHECK(bool)
end end
@ -282,14 +282,14 @@ TEST("world", function()
local Apples = world:entity() local Apples = world:entity()
local bob = 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 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(e == bob)
CHECK(data == "bob eats apples") CHECK(data == "bob eats apples")
end 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(e == bob)
CHECK(data == "bob eats apples") CHECK(data == "bob eats apples")
end end
@ -303,12 +303,12 @@ TEST("world", function()
local bob = world:entity() local bob = world:entity()
local alice = world:entity() local alice = world:entity()
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") world:set(bob, pair(Eats, Apples), "bob eats apples")
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") world:set(alice, pair(Eats, Oranges), "alice eats oranges")
local w = jecs.Wildcard local w = jecs.Wildcard
local count = 0 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 count += 1
if e == bob then if e == bob then
CHECK(data == "bob eats apples") CHECK(data == "bob eats apples")
@ -320,7 +320,7 @@ TEST("world", function()
CHECK(count == 2) CHECK(count == 2)
count = 0 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 count += 1
CHECK(data == "bob eats apples") CHECK(data == "bob eats apples")
end end
@ -337,21 +337,21 @@ TEST("world", function()
local alice = world:entity() local alice = world:entity()
world:set(bob, Apples, "apples") world:set(bob, Apples, "apples")
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") world:set(bob, pair(Eats, Apples), "bob eats apples")
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") world:set(alice, pair(Eats, Oranges), "alice eats oranges")
world:delete(Apples) world:delete(Apples)
local Wildcard = jecs.Wildcard local Wildcard = jecs.Wildcard
local count = 0 local count = 0
for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do for _, data in world:query(pair(Wildcard, Apples)) do
count += 1 count += 1
end end
world:delete(ECS_PAIR(Eats, Apples)) world:delete(pair(Eats, Apples))
CHECK(count == 0) CHECK(count == 0)
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil) CHECK(world:get(bob, pair(Eats, Apples)) == nil)
end end
do CASE("should error when setting invalid pair") do CASE("should error when setting invalid pair")
@ -362,13 +362,12 @@ TEST("world", function()
world:delete(Apples) world:delete(Apples)
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") world:set(bob, pair(Eats, Apples), "bob eats apples")
end end
do CASE("should find target for ChildOf") do CASE("should find target for ChildOf")
local world = jecs.World.new() local world = jecs.World.new()
local ChildOf = jecs.ChildOf local ChildOf = jecs.ChildOf
local pair = ECS_PAIR
local Name = world:component() local Name = world:component()
@ -383,7 +382,7 @@ TEST("world", function()
CHECK(world:parent(bob) == alice) -- O(1) CHECK(world:parent(bob) == alice) -- O(1)
local count = 0 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 count += 1
end end
CHECK(count == 2) CHECK(count == 2)
@ -521,11 +520,11 @@ TEST("world", function()
local Bob = world:component() local Bob = world:component()
local helloBob = world:entity() local helloBob = world:entity()
world:add(helloBob, ECS_PAIR(Hello, Bob)) world:add(helloBob, pair(Hello, Bob))
world:add(helloBob, Bob) world:add(helloBob, Bob)
local withoutCount = 0 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 withoutCount += 1
end end