Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/jecs.luau
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ type world = {
entity: (self: world, id: i53?) -> i53,
component: (self: world) -> i53,
target: (self: world, id: i53, relation: i53, index: number?) -> i53?,
targets: (self: world, id: i53, relation: i53) -> () -> i53?,
delete: (self: world, id: i53) -> (),
add: (self: world, id: i53, component: i53) -> (),
set: (self: world, id: i53, component: i53, data: any) -> (),
Expand Down Expand Up @@ -253,6 +254,13 @@ export type World = {
index: number?
) -> Entity<unknown>?,

-- Gets an iterator for all viable targets of a relationship
targets: <T>(
self: World,
id: Entity<T> | number,
relation: ecs_entity_t<Component>
) -> () -> Id,

--- Deletes an entity and all it's related components and relationships.
delete: <T>(self: World, id: Entity<T>) -> (),

Expand Down Expand Up @@ -3302,6 +3310,50 @@ local function world_new(DEBUG: boolean?)
ECS_PAIR_SECOND(nth))
end

local NOOP = NOOP :: () -> i53

local function world_targets(world: world, entity: i53, relation: i53): () -> i53?
local record = entity_index_try_get_unsafe(entity)
if not record then
return NOOP
end

local archetype = record.archetype
if not archetype then
return NOOP
end

local r = ECS_PAIR(relation, EcsWildcard)
local ct_idx = world.component_index
local idr = ct_idx[r]

if not idr then
return NOOP
end

local archetype_id = archetype.id
local count = idr.counts[archetype_id]
if not count then
return NOOP
end

local nth = idr.records[archetype_id]
local end_count = nth + count

local archetype_types = archetype.types
local sparse_array = entity_index.sparse_array
local dense_array = entity_index.dense_array

return function(): i53?
if nth == end_count then
return nil
end
local target = dense_array[sparse_array[ECS_PAIR_SECOND(archetype_types[nth])].dense]
Comment thread
kurokuukyo marked this conversation as resolved.
nth += 1
return target
end
end

local function world_parent(world: world, entity: i53): i53?
return world_target(world, entity, EcsChildOf, 0)
end
Expand Down Expand Up @@ -3730,6 +3782,7 @@ local function world_new(DEBUG: boolean?)
world.get = world_get :: any
world.has = world_has :: any
world.target = world_target
world.targets = world_targets
world.parent = world_parent
world.contains = world_contains
world.exists = world_exists
Expand Down
126 changes: 125 additions & 1 deletion test/tests.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,7 @@ TEST("world:set()", function()
end
end)

TEST("world:target", function()
TEST("world:target()", function()
do CASE "nth index"
local world = jecs.world()
local A = world:component()
Expand Down Expand Up @@ -3178,6 +3178,130 @@ TEST("world:target", function()
end
end)

TEST("world:targets()", function()
do CASE "should find single relation"
local world = jecs.world()

local Alice = world:entity()
local Bob = world:entity()
local Likes = world:entity()

world:add(Alice, jecs.pair(Likes, Bob))

local i = 0
for target in world:targets(Alice, Likes) do
i += 1
CHECK(target == Bob)
end
CHECK(i == 1)
end
do CASE "basic iteration"
local world = jecs.world()

local ROOT = world:entity()
local e1 = world:entity()
local targets = {}

for i = 1, 10 do
local target = world:entity()
targets[i] = target
world:add(e1, pair(ROOT, target))
end

local i = 0
for target in world:targets(e1, ROOT) do
i += 1
CHECK(targets[i] == target)
end

CHECK(i == 10)
end
do CASE "multiple iterations"
local world = jecs.world()

local ROOT = world:entity()
local OTHER = world:entity()

local e = world:entity()

local root_targets = {}
local other_targets = {}

for i = 1, 5 do
local t = world:entity()
root_targets[i] = t
world:add(e, pair(ROOT, t))
end

for i = 1, 3 do
local t = world:entity()
other_targets[i] = t
world:add(e, pair(OTHER, t))
end

local i = 0
for target in world:targets(e, ROOT) do
i += 1
CHECK(root_targets[i] == target)
end
CHECK(i == 5)

local j = 0
for target in world:targets(e, OTHER) do
j += 1
CHECK(other_targets[j] == target)
end
CHECK(j == 3)
end
do CASE "empty iterator"
local world = jecs.world()

local ROOT = world:entity()
local OTHER = world:entity()

local e = world:entity()

world:add(e, pair(ROOT, world:entity()))

local count = 0
for _ in world:targets(e, OTHER) do
count += 1
end

CHECK(count == 0)
end
do CASE "should ignore deleted targets"
local world = jecs.world()

local ROOT = world:entity()
local e = world:entity()

local alive = {}
local dead = {}

for i = 1, 3 do
local t = world:entity()
alive[#alive + 1] = t
world:add(e, pair(ROOT, t))
end

for i = 1, 2 do
local t = world:entity()
dead[#dead + 1] = t
world:add(e, pair(ROOT, t))
world:delete(t)
end

local count = 0
for t in world:targets(e, ROOT) do
count += 1
CHECK(t ~= nil)
end

CHECK(count == #alive)
end
end)

TEST("#adding a recycled target", function()
local world = jecs.world()
local R = world:component()
Expand Down
Loading