Fix nth count for target

This commit is contained in:
Ukendio 2025-03-26 03:39:04 +01:00
parent f3befa3adb
commit 59e7fd1f41
2 changed files with 102 additions and 54 deletions

109
jecs.luau
View file

@ -135,7 +135,11 @@ local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16) local ECS_GENERATION_MASK = bit32.lshift(1, 16)
local NULL_ARRAY = table.freeze({}) local NULL_ARRAY = table.freeze({})
local ECS_INTERNAL_ERROR = [[
This is an internal error, please file a bug report via the following link:
https://github.com/Ukendio/jecs/issues/new?template=BUG-REPORT.md
]]
local function ECS_COMBINE(id: number, generation: number): i53 local function ECS_COMBINE(id: number, generation: number): i53
return id + (generation * ECS_ENTITY_MASK) return id + (generation * ECS_ENTITY_MASK)
@ -180,6 +184,14 @@ local function ECS_PAIR(pred: i53, obj: i53): i53
return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET
end end
local function ECS_PAIR_FIRST(e: i53): i24
return (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK
end
local function ECS_PAIR_SECOND(e: i53): i24
return (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK
end
local function entity_index_try_get_any( local function entity_index_try_get_any(
entity_index: ecs_entity_index_t, entity_index: ecs_entity_index_t,
entity: number entity: number
@ -217,16 +229,39 @@ local function entity_index_try_get_fast(entity_index: ecs_entity_index_t, entit
return r return r
end end
local function entity_index_get_alive(index: ecs_entity_index_t, id: i24): i53 local function entity_index_is_alive(entity_index: ecs_entity_index_t, entity: i53)
local r = entity_index_try_get_any(index, id) return entity_index_try_get(entity_index, entity) ~= nil
end
local function entity_index_get_alive(index: ecs_entity_index_t, entity: i53): i53
local r = entity_index_try_get_any(index, entity)
if r then if r then
return index.dense_array[r.dense] return index.dense_array[r.dense]
end end
return 0 return 0
end end
local function entity_index_is_alive(entity_index: ecs_entity_index_t, entity: i53) local function ecs_get_alive(world, entity)
return entity_index_try_get(entity_index, entity) ~= nil if entity == 0 then
return 0
end
local eindex = world.entity_index
if entity_index_is_alive(eindex, entity) then
return entity
end
if entity > ECS_ENTITY_MASK then
return 0
end
local current = entity_index_get_alive(eindex, entity)
if not current or not entity_index_is_alive(eindex, current) then
return 0
end
return current
end end
local function entity_index_new_id(entity_index: ecs_entity_index_t): i53 local function entity_index_new_id(entity_index: ecs_entity_index_t): i53
@ -251,13 +286,13 @@ local function entity_index_new_id(entity_index: ecs_entity_index_t): i53
end end
local function ecs_pair_first(world: ecs_world_t, e: i53) local function ecs_pair_first(world: ecs_world_t, e: i53)
local pred = (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK local pred = ECS_PAIR_FIRST(e)
return entity_index_get_alive(world.entity_index, pred) return ecs_get_alive(world, pred)
end end
local function ecs_pair_second(world: ecs_world_t, e: i53) local function ecs_pair_second(world: ecs_world_t, e: i53)
local obj = (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK local obj = ECS_PAIR_SECOND(e)
return entity_index_get_alive(world.entity_index, obj) return ecs_get_alive(world, obj)
end end
local function query_match(query: ecs_query_data_t, local function query_match(query: ecs_query_data_t,
@ -433,24 +468,6 @@ local function world_get(world: ecs_world_t, entity: i53,
end end
end end
local function world_get_one_inline(world: ecs_world_t, entity: i53, id: i53): any
local record = entity_index_try_get_fast(world.entity_index, entity)
if not record then
return nil
end
local archetype = record.archetype
if not archetype then
return nil
end
local tr = archetype.records[id]
if not tr then
return nil
end
return archetype.columns[tr][record.row]
end
local function world_has_one_inline(world: ecs_world_t, entity: i53, id: i53): boolean local function world_has_one_inline(world: ecs_world_t, entity: i53, id: i53): boolean
local record = entity_index_try_get_fast(world.entity_index, entity) local record = entity_index_try_get_fast(world.entity_index, entity)
if not record then if not record then
@ -501,30 +518,23 @@ local function world_target(world: ecs_world_t, entity: i53, relation: i24, inde
return nil return nil
end end
local idr = world.component_index[ECS_PAIR(relation, EcsWildcard)] local r = ECS_PAIR(relation, EcsWildcard)
if not idr then
return nil
end
local archetype_id = archetype.id local count = archetype.counts[r]
local count = idr.counts[archetype.id]
if not count then if not count then
return nil return nil
end end
if nth > count then if nth >= count then
nth = nth + count nth = nth + count + 1
end end
local tr = idr.cache[archetype_id] nth = archetype.types[nth + archetype.records[r]]
nth = archetype.types[nth + tr]
if not nth then if not nth then
return nil return nil
end end
return ecs_pair_second(world, nth) return ECS_PAIR_SECOND(nth)
end end
local function ECS_ID_IS_WILDCARD(e: i53): boolean local function ECS_ID_IS_WILDCARD(e: i53): boolean
@ -535,14 +545,21 @@ end
local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t
local component_index = world.component_index local component_index = world.component_index
local entity_index = world.entity_index
local idr: ecs_id_record_t = component_index[id] local idr: ecs_id_record_t = component_index[id]
if not idr then if not idr then
local flags = ECS_ID_MASK local flags = ECS_ID_MASK
local relation = id local relation = id
local target = 0
local is_pair = ECS_IS_PAIR(id) local is_pair = ECS_IS_PAIR(id)
if is_pair then if is_pair then
relation = ecs_pair_first(world, id) relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id))
assert(relation ~= 0 and entity_index_is_alive(
entity_index, relation), ECS_INTERNAL_ERROR)
target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id))
assert(target ~= 0 and entity_index_is_alive(
entity_index, target), ECS_INTERNAL_ERROR)
end end
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
@ -559,7 +576,7 @@ local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t
local is_tag = not world_has_one_inline(world, relation, EcsComponent) local is_tag = not world_has_one_inline(world, relation, EcsComponent)
if is_tag and is_pair then if is_tag and is_pair then
is_tag = not world_has_one_inline(world, ecs_pair_second(world, id), EcsComponent) is_tag = not world_has_one_inline(world, target, EcsComponent)
end end
flags = bit32.bor( flags = bit32.bor(
@ -624,6 +641,7 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
local records: { number } = {} local records: { number } = {}
local counts: {number} = {} local counts: {number} = {}
local entity_index = world.entity_index
local archetype: ecs_archetype_t = { local archetype: ecs_archetype_t = {
columns = columns, columns = columns,
entities = {}, entities = {},
@ -643,9 +661,10 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
archetype_append_to_records(idr, archetype, component_id, i) archetype_append_to_records(idr, archetype, component_id, i)
if ECS_IS_PAIR(component_id) then if ECS_IS_PAIR(component_id) then
local relation = ecs_pair_first(world, component_id) local relation = ECS_PAIR_FIRST(component_id)
local object = ecs_pair_second(world, component_id) relation = entity_index_get_alive(entity_index, relation)
local object = ECS_PAIR_SECOND(component_id)
object = entity_index_get_alive(entity_index, object)
local r = ECS_PAIR(relation, EcsWildcard) local r = ECS_PAIR(relation, EcsWildcard)
local idr_r = id_record_ensure(world, r) local idr_r = id_record_ensure(world, r)
archetype_append_to_records(idr_r, archetype, r, i) archetype_append_to_records(idr_r, archetype, r, i)
@ -2481,7 +2500,7 @@ return {
Name = EcsName :: Entity<string>, Name = EcsName :: Entity<string>,
Rest = EcsRest :: Entity, Rest = EcsRest :: Entity,
pair = ECS_PAIR :: <P, O>(first: P, second: O) -> Pair<P, O>, pair = (ECS_PAIR :: any) :: <P, O>(first: P, second: O) -> Pair<P, O>,
-- Inwards facing API for testing -- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO, ECS_ID = ECS_ENTITY_T_LO,

View file

@ -126,23 +126,51 @@ TEST("#repro2", function()
local entity = world:entity() local entity = world:entity()
world:set(entity, pair(Lifetime, Particle), 1) world:set(entity, pair(Lifetime, Particle), 1)
world:set(entity, pair(Lifetime, Beam), 2) world:set(entity, pair(Lifetime, Beam), 2)
world:set(entity, pair(4, 5), 6) -- noise
local entity_visualizer = require("@tools/entity_visualiser")
entity_visualizer.components(world, entity)
for e in world:each(pair(Lifetime, __)) do for e in world:each(pair(Lifetime, __)) do
local i = 0 local i = 0
local nth = world:target(e, Lifetime, i) local nth = world:target(e, Lifetime, i)
while nth do while nth do
entity_visualizer.components(world, e)
local data = world:get(e, pair(Lifetime, nth)) local data = world:get(e, pair(Lifetime, nth))
if nth == Particle then data -= 1
CHECK(data == 1) if data <= 0 then
elseif nth == Beam then world:remove(e, pair(Lifetime, nth))
CHECK(data == 2) else
else world:set(e, pair(Lifetime, nth), data)
CHECK(false) end
end
i += 1 i += 1
nth = world:target(e, Lifetime, i) nth = world:target(e, Lifetime, i)
end end
end end
CHECK(not world:has(entity, pair(Lifetime, Particle)))
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
end)
local lifetime_tracker_add = require("@tools/lifetime_tracker")
TEST("another", function()
local world = world_new()
world = lifetime_tracker_add(world, {padding_enabled=false})
local e1 = world:entity()
local e2 = world:entity()
local e3 = world:entity()
world:delete(e2)
world:print_entity_index()
print(pair(e1, e2))
print(pair(e2, e3))
local e2_e3 = pair(e2, e3)
CHECK(jecs.pair_first(world, e2_e3) == 0)
CHECK(jecs.pair_second(world, e2_e3) == e3)
CHECK_EXPECT_ERR(function()
world:add(e1, pair(e2, e3))
end)
end) end)
TEST("#repro", function() TEST("#repro", function()
@ -851,8 +879,9 @@ TEST("world:query()", function()
local bob = world:entity() local bob = world:entity()
world:delete(Apples) world:delete(Apples)
CHECK_EXPECT_ERR(function()
world:set(bob, pair(Eats, Apples), "bob eats apples") world:set(bob, pair(Eats, Apples), "bob eats apples")
end)
end end
do do