2024-11-16 17:07:12 +00:00
|
|
|
local jecs = require("@jecs")
|
2024-10-06 01:36:36 +00:00
|
|
|
|
2024-07-06 21:30:14 +00:00
|
|
|
local testkit = require("@testkit")
|
2024-08-13 18:08:58 +00:00
|
|
|
local BENCH, START = testkit.benchmark()
|
2024-05-16 22:17:53 +00:00
|
|
|
local __ = jecs.Wildcard
|
2024-05-10 15:59:57 +00:00
|
|
|
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
|
2024-07-15 18:29:06 +00:00
|
|
|
local pair = jecs.pair
|
2024-08-10 02:55:04 +00:00
|
|
|
local ecs_pair_first = jecs.pair_first
|
|
|
|
local ecs_pair_second = jecs.pair_second
|
2024-11-13 19:05:01 +00:00
|
|
|
local entity_index_try_get_any = jecs.entity_index_try_get_any
|
|
|
|
local entity_index_get_alive = jecs.entity_index_get_alive
|
|
|
|
local entity_index_is_alive = jecs.entity_index_is_alive
|
2024-12-20 12:08:50 +00:00
|
|
|
local ChildOf = jecs.ChildOf
|
2024-09-10 21:56:42 +00:00
|
|
|
local world_new = jecs.World.new
|
2024-05-01 12:41:10 +00:00
|
|
|
|
2024-09-17 01:18:52 +00:00
|
|
|
local TEST, CASE, CHECK, FINISH, SKIP, FOCUS = testkit.test()
|
2024-12-20 12:08:50 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local N = 2 ^ 8
|
2024-05-01 12:41:10 +00:00
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
type World = jecs.World
|
|
|
|
type Entity<T=nil> = jecs.Entity<T>
|
2024-07-02 23:24:17 +00:00
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
local function debug_world_inspect(world: World)
|
|
|
|
local function record(e): jecs.Record
|
|
|
|
return entity_index_try_get_any(world.entity_index, e) :: any
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
|
|
|
local function tbl(e)
|
|
|
|
return record(e).archetype
|
|
|
|
end
|
|
|
|
local function archetype(e)
|
|
|
|
return tbl(e).type
|
|
|
|
end
|
|
|
|
local function records(e)
|
|
|
|
return tbl(e).records
|
|
|
|
end
|
|
|
|
local function columns(e)
|
|
|
|
return tbl(e).columns
|
|
|
|
end
|
|
|
|
local function row(e)
|
|
|
|
return record(e).row
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Important to order them in the order of their columns
|
|
|
|
local function tuple(e, ...)
|
|
|
|
for i, column in columns(e) do
|
|
|
|
if select(i, ...) ~= column[row(e)] then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
return {
|
|
|
|
record = record,
|
|
|
|
tbl = tbl,
|
|
|
|
archetype = archetype,
|
|
|
|
records = records,
|
|
|
|
row = row,
|
|
|
|
tuple = tuple,
|
|
|
|
}
|
2024-07-29 23:11:22 +00:00
|
|
|
end
|
|
|
|
|
2024-10-06 01:36:36 +00:00
|
|
|
TEST("archetype", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
local archetype_append_to_records = jecs.archetype_append_to_records
|
|
|
|
local id_record_ensure = jecs.id_record_ensure
|
|
|
|
local archetype_create = jecs.archetype_create
|
|
|
|
local archetype_ensure = jecs.archetype_ensure
|
|
|
|
local find_insert = jecs.find_insert
|
|
|
|
local find_archetype_with = jecs.find_archetype_with
|
|
|
|
local find_archetype_without = jecs.find_archetype_without
|
|
|
|
local archetype_init_edge = jecs.archetype_init_edge
|
|
|
|
local archetype_ensure_edge = jecs.archetype_ensure_edge
|
|
|
|
local init_edge_for_add = jecs.init_edge_for_add
|
|
|
|
local init_edge_for_remove = jecs.init_edge_for_remove
|
|
|
|
local create_edge_for_add = jecs.create_edge_for_add
|
|
|
|
local create_edge_for_remove = jecs.create_edge_for_remove
|
|
|
|
local archetype_traverse_add = jecs.archetype_traverse_add
|
|
|
|
local archetype_traverse_remove = jecs.archetype_traverse_remove
|
|
|
|
|
|
|
|
local world = world_new()
|
|
|
|
local root = world.ROOT_ARCHETYPE
|
|
|
|
local c1 = world:component()
|
|
|
|
local c2 = world:component()
|
|
|
|
local c3 = world:component()
|
|
|
|
|
|
|
|
local a1 = archetype_traverse_add(world, c1, nil)
|
|
|
|
local a2 = archetype_traverse_remove(world, c1, a1)
|
2024-11-22 22:06:17 +00:00
|
|
|
CHECK(root.add[c1].to == a1)
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(root == a2)
|
2024-10-06 01:36:36 +00:00
|
|
|
end)
|
|
|
|
|
2024-10-01 15:30:51 +00:00
|
|
|
TEST("world:cleanup()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
|
|
|
|
world:set(e1, A, true)
|
|
|
|
|
|
|
|
world:set(e2, A, true)
|
|
|
|
world:set(e2, B, true)
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e3, A, true)
|
|
|
|
world:set(e3, B, true)
|
|
|
|
world:set(e3, C, true)
|
|
|
|
|
|
|
|
local archetypeIndex = world.archetypeIndex
|
|
|
|
|
|
|
|
CHECK(#archetypeIndex["1"].entities == 1)
|
|
|
|
CHECK(#archetypeIndex["1_2"].entities == 1)
|
|
|
|
CHECK(#archetypeIndex["1_2_3"].entities == 1)
|
|
|
|
|
|
|
|
world:delete(e1)
|
|
|
|
world:delete(e2)
|
|
|
|
world:delete(e3)
|
|
|
|
|
|
|
|
world:cleanup()
|
|
|
|
|
|
|
|
archetypeIndex = world.archetypeIndex
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK((archetypeIndex["1"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetypeIndex["1_2"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetypeIndex["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
local e4 = world:entity()
|
|
|
|
world:set(e4, A, true)
|
|
|
|
CHECK(#archetypeIndex["1"].entities == 1)
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK((archetypeIndex["1_2"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetypeIndex["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e4, B, true)
|
|
|
|
CHECK(#archetypeIndex["1"].entities == 0)
|
|
|
|
CHECK(#archetypeIndex["1_2"].entities == 1)
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK((archetypeIndex["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e4, C, true)
|
|
|
|
CHECK(#archetypeIndex["1"].entities == 0)
|
|
|
|
CHECK(#archetypeIndex["1_2"].entities == 0)
|
|
|
|
CHECK(#archetypeIndex["1_2_3"].entities == 1)
|
2024-10-01 15:30:51 +00:00
|
|
|
end)
|
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:entity()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("unique IDs")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local set = {}
|
|
|
|
for i = 1, N do
|
|
|
|
local e = world:entity()
|
|
|
|
CHECK(not set[e])
|
|
|
|
set[e] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
|
|
CASE("generations")
|
2024-07-29 23:11:22 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local e = world:entity()
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK(ECS_ID(e) == 1 + jecs.Rest :: number)
|
2024-07-29 23:11:22 +00:00
|
|
|
CHECK(ECS_GENERATION(e) == 0) -- 0
|
|
|
|
e = ECS_GENERATION_INC(e)
|
|
|
|
CHECK(ECS_GENERATION(e) == 1) -- 1
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("pairs")
|
2024-07-29 23:11:22 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local _e = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
|
|
|
|
-- Incomplete pair, must have a bit flag that notes it is a pair
|
|
|
|
CHECK(IS_PAIR(world:entity()) == false)
|
|
|
|
|
|
|
|
local pair = pair(e2, e3)
|
|
|
|
CHECK(IS_PAIR(pair) == true)
|
|
|
|
|
2024-08-10 02:55:04 +00:00
|
|
|
CHECK(ecs_pair_first(world, pair) == e2)
|
|
|
|
CHECK(ecs_pair_second(world, pair) == e3)
|
2024-07-29 23:11:22 +00:00
|
|
|
end
|
2024-11-14 02:38:27 +00:00
|
|
|
|
|
|
|
do CASE "Recycling"
|
|
|
|
local world = world_new()
|
|
|
|
local e = world:entity()
|
|
|
|
world:delete(e)
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:delete(e1)
|
|
|
|
local e2 = world:entity()
|
|
|
|
CHECK(ECS_ID(e2) == e)
|
|
|
|
CHECK(ECS_GENERATION(e2) == 2)
|
|
|
|
CHECK(world:contains(e2))
|
|
|
|
CHECK(not world:contains(e1))
|
|
|
|
CHECK(not world:contains(e))
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "Recycling max generation"
|
|
|
|
local world = world_new()
|
2024-12-20 12:08:50 +00:00
|
|
|
local pin = jecs.Rest::number + 1
|
2024-11-14 02:38:27 +00:00
|
|
|
for i = 1, 2^16-1 do
|
|
|
|
local e = world:entity()
|
|
|
|
world:delete(e)
|
|
|
|
end
|
|
|
|
local e = world:entity()
|
|
|
|
CHECK(ECS_ID(e) == pin)
|
|
|
|
CHECK(ECS_GENERATION(e) == 2^16-1)
|
|
|
|
world:delete(e)
|
|
|
|
e = world:entity()
|
|
|
|
CHECK(ECS_ID(e) == pin)
|
|
|
|
CHECK(ECS_GENERATION(e) == 0)
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
|
|
|
|
|
|
|
TEST("world:set()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("archetype move")
|
|
|
|
do
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local d = debug_world_inspect(world)
|
|
|
|
|
|
|
|
local _1 = world:component()
|
|
|
|
local _2 = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
-- An entity starts without an archetype or row
|
|
|
|
-- should therefore not need to copy over data
|
|
|
|
CHECK(d.tbl(e) == nil)
|
|
|
|
CHECK(d.row(e) == nil)
|
|
|
|
|
|
|
|
local archetypes = #world.archetypes
|
|
|
|
-- This should create a new archetype since it is the first
|
|
|
|
-- entity to have moved there
|
|
|
|
world:set(e, _1, 1)
|
|
|
|
local oldRow = d.row(e)
|
|
|
|
local oldArchetype = d.archetype(e)
|
|
|
|
CHECK(#world.archetypes == archetypes + 1)
|
|
|
|
CHECK(oldArchetype == "1")
|
|
|
|
CHECK(d.tbl(e))
|
|
|
|
CHECK(oldRow == 1)
|
|
|
|
|
|
|
|
world:set(e, _2, 2)
|
|
|
|
CHECK(d.archetype(e) == "1_2")
|
|
|
|
-- Should have tuple of fields to the next archetype and set the component data
|
|
|
|
CHECK(d.tuple(e, 1, 2))
|
|
|
|
-- Should have moved the data from the old archetype
|
|
|
|
CHECK(world.archetypeIndex[oldArchetype].columns[_1][oldRow] == nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("arbitrary order")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local Health = world:entity()
|
|
|
|
local Poison = world:component()
|
|
|
|
|
|
|
|
local id = world:entity()
|
|
|
|
world:set(id, Poison, 5)
|
|
|
|
world:set(id, Health, 50)
|
|
|
|
|
|
|
|
CHECK(world:get(id, Poison) == 5)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("pairs")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local C1 = world:component()
|
|
|
|
local C2 = world:component()
|
|
|
|
local T1 = world:entity()
|
|
|
|
local T2 = world:entity()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
world:set(e, pair(C1, C2), true)
|
|
|
|
world:set(e, pair(C1, T1), true)
|
|
|
|
world:set(e, pair(T1, C1), true)
|
|
|
|
world:set(e, pair(T1, T2), true)
|
|
|
|
|
|
|
|
CHECK(world:get(e, pair(C1, C2)))
|
|
|
|
CHECK(world:get(e, pair(C1, T1)))
|
|
|
|
CHECK(not world:get(e, pair(T1, C1)))
|
|
|
|
CHECK(not world:get(e, pair(T1, T2)))
|
|
|
|
|
|
|
|
local e2 = world:entity()
|
|
|
|
|
|
|
|
world:set(e2, pair(jecs.ChildOf, e), true)
|
|
|
|
CHECK(not world:get(e2, pair(jecs.ChildOf, e)))
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
|
|
|
|
|
|
|
TEST("world:remove()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should allow remove a component that doesn't exist on entity")
|
2024-07-26 02:45:07 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
|
2024-09-10 21:56:42 +00:00
|
|
|
local Health = world:component()
|
2024-07-26 02:45:07 +00:00
|
|
|
local Poison = world:component()
|
|
|
|
|
|
|
|
local id = world:entity()
|
|
|
|
do
|
2024-10-12 20:18:11 +00:00
|
|
|
world:remove(id, Poison)
|
2024-07-26 02:45:07 +00:00
|
|
|
CHECK(true) -- Didn't error
|
|
|
|
end
|
|
|
|
|
|
|
|
world:set(id, Health, 50)
|
|
|
|
world:remove(id, Poison)
|
|
|
|
|
|
|
|
CHECK(world:get(id, Poison) == nil)
|
|
|
|
CHECK(world:get(id, Health) == 50)
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:add()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("idempotent")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local d = debug_world_inspect(world)
|
|
|
|
local _1, _2 = world:component(), world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, _1)
|
|
|
|
world:add(e, _2)
|
|
|
|
world:add(e, _2) -- should have 0 effects
|
|
|
|
CHECK(d.archetype(e) == "1_2")
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("archetype move")
|
|
|
|
do
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local d = debug_world_inspect(world)
|
|
|
|
|
|
|
|
local _1 = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
-- An entity starts without an archetype or row
|
|
|
|
-- should therefore not need to copy over data
|
|
|
|
CHECK(d.tbl(e) == nil)
|
|
|
|
CHECK(d.row(e) == nil)
|
2024-08-11 02:03:18 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local archetypes = #world.archetypes
|
|
|
|
-- This should create a new archetype
|
|
|
|
world:add(e, _1)
|
|
|
|
CHECK(#world.archetypes == archetypes + 1)
|
|
|
|
|
|
|
|
CHECK(d.archetype(e) == "1")
|
|
|
|
CHECK(d.tbl(e))
|
|
|
|
end
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
2024-07-02 23:24:17 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:query()", function()
|
2024-12-20 12:08:50 +00:00
|
|
|
do CASE("multiple iter")
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local e = world:entity()
|
2024-12-20 12:08:50 +00:00
|
|
|
world:add(e, A)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:add(e, B)
|
|
|
|
local q = world:query(A, B)
|
|
|
|
local counter = 0
|
|
|
|
for x in q:iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
for x in q:iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 2)
|
|
|
|
end
|
|
|
|
do
|
|
|
|
CASE("tag")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:entity()
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, A, "test")
|
|
|
|
for id, a in world:query(A) do
|
|
|
|
CHECK(a == nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
|
|
CASE("pairs")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local C1 = world:component()
|
|
|
|
local C2 = world:component()
|
|
|
|
local T1 = world:entity()
|
|
|
|
local T2 = world:entity()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
world:set(e, pair(C1, C2), true)
|
|
|
|
world:set(e, pair(C1, T1), true)
|
|
|
|
world:set(e, pair(T1, C1), true)
|
|
|
|
world:set(e, pair(T1, T2), true)
|
|
|
|
|
|
|
|
for id, a, b, c, d in world:query(pair(C1, C2), pair(C1, T1), pair(T1, C1), pair(T1, T2)) do
|
|
|
|
CHECK(a == true)
|
|
|
|
CHECK(b == true)
|
|
|
|
CHECK(c == nil)
|
|
|
|
CHECK(d == nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
|
|
CASE("query single component")
|
|
|
|
do
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local entities = {}
|
|
|
|
for i = 1, N do
|
|
|
|
local id = world:entity()
|
|
|
|
|
|
|
|
world:set(id, A, true)
|
|
|
|
if i > 5 then
|
|
|
|
world:set(id, B, true)
|
|
|
|
end
|
|
|
|
entities[i] = id
|
|
|
|
end
|
|
|
|
|
|
|
|
for id in world:query(A) do
|
|
|
|
table.remove(entities, CHECK(table.find(entities, id)))
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(#entities == 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local eA = world:entity()
|
|
|
|
world:set(eA, A, true)
|
|
|
|
local eB = world:entity()
|
|
|
|
world:set(eB, B, true)
|
|
|
|
local eAB = world:entity()
|
|
|
|
world:set(eAB, A, true)
|
|
|
|
world:set(eAB, B, true)
|
|
|
|
|
|
|
|
-- Should drain the iterator
|
|
|
|
local q = world:query(A)
|
|
|
|
|
|
|
|
local i = 0
|
|
|
|
local j = 0
|
|
|
|
for _ in q do
|
|
|
|
i += 1
|
|
|
|
end
|
|
|
|
for _ in q do
|
|
|
|
j += 1
|
|
|
|
end
|
|
|
|
CHECK(i == 2)
|
|
|
|
CHECK(j == 0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("query missing component")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
|
|
|
|
world:set(e1, A, "abc")
|
|
|
|
world:set(e2, A, "def")
|
|
|
|
world:set(e1, B, 123)
|
|
|
|
world:set(e2, B, 457)
|
|
|
|
|
|
|
|
local counter = 0
|
|
|
|
for _ in world:query(B, C) do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("query more than 8 components")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local components = {}
|
|
|
|
|
|
|
|
for i = 1, 9 do
|
|
|
|
local id = world:component()
|
|
|
|
components[i] = id
|
|
|
|
end
|
|
|
|
local e = world:entity()
|
|
|
|
for i, id in components do
|
|
|
|
world:set(e, id, 13 ^ i)
|
|
|
|
end
|
|
|
|
|
|
|
|
for entity, a, b, c, d, e, f, g, h, i in world:query(unpack(components)) do
|
|
|
|
CHECK(a == 13 ^ 1)
|
|
|
|
CHECK(b == 13 ^ 2)
|
|
|
|
CHECK(c == 13 ^ 3)
|
|
|
|
CHECK(d == 13 ^ 4)
|
|
|
|
CHECK(e == 13 ^ 5)
|
|
|
|
CHECK(f == 13 ^ 6)
|
|
|
|
CHECK(g == 13 ^ 7)
|
|
|
|
CHECK(h == 13 ^ 8)
|
|
|
|
CHECK(i == 13 ^ 9)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("should be able to get next results")
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
world:component()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local eA = world:entity()
|
|
|
|
world:set(eA, A, true)
|
|
|
|
local eB = world:entity()
|
|
|
|
world:set(eB, B, true)
|
|
|
|
local eAB = world:entity()
|
|
|
|
world:set(eAB, A, true)
|
|
|
|
world:set(eAB, B, true)
|
|
|
|
|
|
|
|
local it = world:query(A):iter()
|
|
|
|
|
|
|
|
local e, data = it()
|
|
|
|
while e do
|
|
|
|
if e == eA then
|
|
|
|
CHECK(data)
|
|
|
|
elseif e == eAB then
|
|
|
|
CHECK(data)
|
|
|
|
else
|
|
|
|
CHECK(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
e, data = it()
|
|
|
|
end
|
|
|
|
CHECK(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("should query all matching entities when irrelevant component is removed")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
local entities = {}
|
|
|
|
for i = 1, N do
|
|
|
|
local id = world:entity()
|
|
|
|
|
|
|
|
-- specifically put them in disorder to track regression
|
|
|
|
-- https://github.com/Ukendio/jecs/pull/15
|
|
|
|
world:set(id, B, true)
|
|
|
|
world:set(id, A, true)
|
|
|
|
if i > 5 then
|
|
|
|
world:remove(id, B)
|
|
|
|
end
|
|
|
|
entities[i] = id
|
|
|
|
end
|
|
|
|
|
|
|
|
local added = 0
|
|
|
|
for id in world:query(A) do
|
|
|
|
added += 1
|
|
|
|
table.remove(entities, CHECK(table.find(entities, id)))
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(added == N)
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should query all entities without B")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local entities = {}
|
|
|
|
for i = 1, N do
|
|
|
|
local id = world:entity()
|
|
|
|
|
|
|
|
world:set(id, A, true)
|
|
|
|
if i < 5 then
|
|
|
|
entities[i] = id
|
|
|
|
else
|
|
|
|
world:set(id, B, true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id in world:query(A):without(B) do
|
|
|
|
table.remove(entities, CHECK(table.find(entities, id)))
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(#entities == 0)
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should allow querying for relations")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
2024-09-10 21:56:42 +00:00
|
|
|
local Eats = world:component()
|
|
|
|
local Apples = world:component()
|
2024-05-27 01:39:20 +00:00
|
|
|
local bob = world:entity()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), true)
|
|
|
|
for e, bool in world:query(pair(Eats, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(bool)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should allow wildcards in queries")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
2024-09-10 21:56:42 +00:00
|
|
|
local Eats = world:component()
|
2024-05-27 01:39:20 +00:00
|
|
|
local Apples = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should match against multiple pairs")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
2024-09-10 21:56:42 +00:00
|
|
|
local Eats = world:component()
|
2024-05-27 01:39:20 +00:00
|
|
|
local Apples = world:entity()
|
|
|
|
local Oranges = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
if e == bob then
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
else
|
|
|
|
CHECK(data == "alice eats oranges")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 2)
|
|
|
|
count = 0
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
CHECK(count == 1)
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should only relate alive entities")
|
|
|
|
SKIP()
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local Oranges = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
|
|
|
|
world:set(bob, Apples, "apples")
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for _, data in world:query(pair(Wildcard, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:delete(pair(Eats, Apples))
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
CHECK(count == 0)
|
2024-07-15 18:29:06 +00:00
|
|
|
CHECK(world:get(bob, pair(Eats, Apples)) == nil)
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should error when setting invalid pair")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
2024-09-16 19:33:12 +00:00
|
|
|
local Eats = world:component()
|
|
|
|
local Apples = world:component()
|
2024-05-27 01:39:20 +00:00
|
|
|
local bob = world:entity()
|
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should find target for ChildOf")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
2024-07-03 00:46:54 +00:00
|
|
|
local ChildOf = jecs.ChildOf
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local Name = world:component()
|
|
|
|
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
local sara = world:entity()
|
|
|
|
|
2024-07-03 00:46:54 +00:00
|
|
|
world:add(bob, pair(ChildOf, alice))
|
2024-05-27 01:39:20 +00:00
|
|
|
world:set(bob, Name, "bob")
|
2024-07-03 00:46:54 +00:00
|
|
|
world:add(sara, pair(ChildOf, alice))
|
2024-05-27 01:39:20 +00:00
|
|
|
world:set(sara, Name, "sara")
|
2024-07-03 00:46:54 +00:00
|
|
|
CHECK(world:parent(bob) == alice) -- O(1)
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for _, name in world:query(Name, pair(ChildOf, alice)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("despawning while iterating")
|
|
|
|
local world = jecs.World.new()
|
2024-07-31 16:48:34 +00:00
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
world:add(e2, A)
|
|
|
|
world:add(e2, B)
|
|
|
|
|
|
|
|
local count = 0
|
|
|
|
for id in world:query(A) do
|
2024-10-12 20:18:11 +00:00
|
|
|
world:clear(id)
|
2024-07-31 16:48:34 +00:00
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("iterator invalidation")
|
|
|
|
do
|
|
|
|
CASE("adding")
|
2024-07-29 23:11:22 +00:00
|
|
|
SKIP()
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
2024-07-29 23:11:22 +00:00
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
world:add(e2, A)
|
|
|
|
world:add(e2, B)
|
2024-07-06 21:30:14 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
local count = 0
|
|
|
|
for id in world:query(A) do
|
|
|
|
world:add(id, B)
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
count += 1
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
CHECK(count == 2)
|
2024-07-06 14:36:00 +00:00
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("spawning")
|
|
|
|
local world = jecs.World.new()
|
2024-07-29 23:11:22 +00:00
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
world:add(e2, A)
|
|
|
|
world:add(e2, B)
|
|
|
|
|
|
|
|
for id in world:query(A) do
|
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, A)
|
|
|
|
world:add(e, B)
|
|
|
|
end
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
CHECK(true)
|
2024-07-29 23:11:22 +00:00
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should not find any entities")
|
|
|
|
local world = jecs.World.new()
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local Hello = world:component()
|
|
|
|
local Bob = world:component()
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local helloBob = world:entity()
|
|
|
|
world:add(helloBob, pair(Hello, Bob))
|
|
|
|
world:add(helloBob, Bob)
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local withoutCount = 0
|
|
|
|
for _ in world:query(pair(Hello, Bob)):without(Bob) do
|
|
|
|
withoutCount += 1
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(withoutCount == 0)
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("Empty Query")
|
|
|
|
do
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
2024-07-07 02:53:17 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
2024-07-07 02:53:17 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local query = world:query(B)
|
|
|
|
CHECK(query:without() == query)
|
|
|
|
CHECK(query:with() == query)
|
|
|
|
-- They always return the same EMPTY_LIST
|
|
|
|
CHECK(query:archetypes() == world:query(B):archetypes())
|
|
|
|
end
|
2024-07-07 02:53:17 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
2024-07-07 02:53:17 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local count = 0
|
|
|
|
for id in world:query(B) do
|
|
|
|
count += 1
|
|
|
|
end
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(count == 0)
|
|
|
|
end
|
|
|
|
end
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("without")
|
|
|
|
do
|
|
|
|
-- REGRESSION TEST
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local _1, _2, _3 = world:component(), world:component(), world:component()
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local counter = 0
|
|
|
|
for e, a, b in world:query(_1, _2):without(_3) do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 0)
|
|
|
|
end
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
TEST("world:each", function()
|
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
|
|
|
|
world:set(e1, A, true)
|
|
|
|
|
|
|
|
world:set(e2, A, true)
|
|
|
|
world:set(e2, B, true)
|
|
|
|
|
|
|
|
world:set(e3, A, true)
|
|
|
|
world:set(e3, B, true)
|
|
|
|
world:set(e3, C, true)
|
|
|
|
|
|
|
|
for entity in world:each(A) do
|
|
|
|
if entity == e1 or entity == e2 or entity == e3 then
|
|
|
|
CHECK(true)
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
CHECK(false)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
TEST("world:children", function()
|
|
|
|
local world = world_new()
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
|
|
|
|
world:add(e2, pair(ChildOf, e1))
|
|
|
|
world:add(e3, pair(ChildOf, e1))
|
|
|
|
|
|
|
|
for entity in world:children(pair(ChildOf, e1)) do
|
|
|
|
if entity == e2 or entity == e3 then
|
|
|
|
CHECK(true)
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
CHECK(false)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:clear()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should remove its components")
|
2024-07-29 23:11:22 +00:00
|
|
|
local world = jecs.World.new() :: World
|
2024-07-08 13:51:34 +00:00
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
local e = world:entity()
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
world:set(e, A, true)
|
|
|
|
world:set(e, B, true)
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
CHECK(world:get(e, A))
|
|
|
|
CHECK(world:get(e, B))
|
2024-07-08 13:51:34 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
world:clear(e)
|
|
|
|
CHECK(world:get(e, A) == nil)
|
|
|
|
CHECK(world:get(e, B) == nil)
|
2024-07-08 13:51:34 +00:00
|
|
|
end
|
2024-10-12 19:55:24 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should move last record")
|
2024-10-12 19:55:24 +00:00
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
local e1 = world:entity()
|
|
|
|
|
|
|
|
world:add(e, A)
|
|
|
|
world:add(e1, A)
|
|
|
|
|
|
|
|
local archetype = world.archetypeIndex["1"]
|
|
|
|
local archetype_entities = archetype.entities
|
|
|
|
|
|
|
|
local _e = e :: number
|
|
|
|
local _e1 = e1 :: number
|
|
|
|
|
|
|
|
CHECK(archetype_entities[1] == _e)
|
|
|
|
CHECK(archetype_entities[2] == _e1)
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
local e_record: jecs.Record = entity_index_try_get_any(
|
|
|
|
world.entity_index, e) :: any
|
|
|
|
local e1_record: jecs.Record = entity_index_try_get_any(
|
|
|
|
world.entity_index, e1) :: any
|
2024-10-12 19:55:24 +00:00
|
|
|
CHECK(e_record.archetype == archetype)
|
|
|
|
CHECK(e1_record.archetype == archetype)
|
|
|
|
CHECK(e1_record.row == 2)
|
|
|
|
|
|
|
|
world:clear(e)
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK((e_record.archetype :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((e_record.row :: number?) == nil)
|
2024-10-12 19:55:24 +00:00
|
|
|
CHECK(e1_record.archetype == archetype)
|
|
|
|
CHECK(e1_record.row == 1)
|
|
|
|
|
|
|
|
CHECK(archetype_entities[1] == _e1)
|
|
|
|
CHECK(archetype_entities[2] == nil)
|
|
|
|
|
|
|
|
CHECK(world:contains(e) == true)
|
|
|
|
CHECK(world:has(e, A) == false)
|
|
|
|
CHECK(world:contains(e1) == true)
|
|
|
|
CHECK(world:has(e1, A) == true)
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:has()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should find Tag on entity")
|
|
|
|
local world = jecs.World.new()
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local Tag = world:entity()
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, Tag)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(world:has(e, Tag))
|
|
|
|
end
|
2024-08-11 13:03:05 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should return false when missing one tag")
|
|
|
|
local world = jecs.World.new()
|
2024-08-11 13:03:05 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local A = world:entity()
|
|
|
|
local B = world:entity()
|
|
|
|
local C = world:entity()
|
|
|
|
local D = world:entity()
|
2024-08-11 13:03:05 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, A)
|
|
|
|
world:add(e, C)
|
|
|
|
world:add(e, D)
|
2024-08-11 13:03:05 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(world:has(e, A, B, C, D) == false)
|
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
2024-07-23 02:44:56 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:component()", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("only components should have EcsComponent trait")
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
CHECK(world:has(A, jecs.Component))
|
|
|
|
CHECK(not world:has(e, jecs.Component))
|
|
|
|
end
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
do CASE("tag")
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:entity()
|
|
|
|
local C = world:entity()
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, A, "test")
|
2024-12-20 12:08:50 +00:00
|
|
|
world:add(e, B)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e, C, 11)
|
|
|
|
|
|
|
|
CHECK(world:has(e, A))
|
|
|
|
CHECK(world:get(e, A) == "test")
|
|
|
|
CHECK(world:get(e, B) == nil)
|
|
|
|
CHECK(world:get(e, C) == nil)
|
2024-08-28 17:26:57 +00:00
|
|
|
end
|
2024-07-29 23:11:22 +00:00
|
|
|
end)
|
2024-07-23 02:44:56 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:delete", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("bug: Empty entity does not respect cleanup policy")
|
|
|
|
local world = world_new()
|
|
|
|
local parent = world:entity()
|
|
|
|
local tag = world:entity()
|
|
|
|
|
|
|
|
local child = world:entity()
|
|
|
|
world:add(child, jecs.pair(jecs.ChildOf, parent))
|
|
|
|
world:delete(parent)
|
|
|
|
|
|
|
|
CHECK(not world:contains(parent))
|
|
|
|
CHECK(not world:contains(child))
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
world:add(entity, tag)
|
|
|
|
world:delete(tag)
|
|
|
|
CHECK(world:contains(entity))
|
|
|
|
CHECK(not world:contains(tag))
|
|
|
|
CHECK(not world:has(entity, tag)) -- => true
|
|
|
|
end
|
|
|
|
do
|
|
|
|
CASE("should allow deleting components")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local Health = world:component()
|
|
|
|
local Poison = world:component()
|
|
|
|
|
|
|
|
local id = world:entity()
|
|
|
|
world:set(id, Poison, 5)
|
|
|
|
world:set(id, Health, 50)
|
|
|
|
local id1 = world:entity()
|
|
|
|
world:set(id1, Poison, 500)
|
|
|
|
world:set(id1, Health, 50)
|
|
|
|
|
|
|
|
world:delete(id)
|
|
|
|
CHECK(not world:contains(id))
|
|
|
|
CHECK(world:get(id, Poison) == nil)
|
|
|
|
CHECK(world:get(id, Health) == nil)
|
|
|
|
|
|
|
|
CHECK(world:get(id1, Poison) == 500)
|
|
|
|
CHECK(world:get(id1, Health) == 50)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("delete entities using another Entity as component with Delete cleanup action")
|
|
|
|
local world = jecs.World.new()
|
2024-08-13 18:08:58 +00:00
|
|
|
|
|
|
|
local Health = world:entity()
|
2024-08-30 17:19:20 +00:00
|
|
|
world:add(Health, pair(jecs.OnDelete, jecs.Delete))
|
2024-08-13 18:08:58 +00:00
|
|
|
local Poison = world:component()
|
|
|
|
|
|
|
|
local id = world:entity()
|
|
|
|
world:set(id, Poison, 5)
|
|
|
|
world:set(id, Health, 50)
|
|
|
|
local id1 = world:entity()
|
|
|
|
world:set(id1, Poison, 500)
|
|
|
|
world:set(id1, Health, 50)
|
|
|
|
|
|
|
|
CHECK(world:has(id, Poison, Health))
|
|
|
|
CHECK(world:has(id1, Poison, Health))
|
|
|
|
world:delete(Poison)
|
|
|
|
|
2024-08-30 17:19:20 +00:00
|
|
|
CHECK(world:contains(id))
|
2024-08-13 18:08:58 +00:00
|
|
|
CHECK(not world:has(id, Poison))
|
|
|
|
CHECK(not world:has(id1, Poison))
|
2024-08-30 17:19:20 +00:00
|
|
|
|
|
|
|
world:delete(Health)
|
|
|
|
CHECK(not world:contains(id))
|
|
|
|
CHECK(not world:contains(id1))
|
|
|
|
CHECK(not world:has(id, Health))
|
|
|
|
CHECK(not world:has(id1, Health))
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("delete children")
|
|
|
|
local world = jecs.World.new()
|
2024-08-13 18:08:58 +00:00
|
|
|
|
|
|
|
local Health = world:component()
|
|
|
|
local Poison = world:component()
|
|
|
|
local FriendsWith = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Poison, 5)
|
|
|
|
world:set(e, Health, 50)
|
|
|
|
|
|
|
|
local children = {}
|
|
|
|
for i = 1, 10 do
|
2024-10-12 20:18:11 +00:00
|
|
|
local child = world:entity()
|
2024-08-13 18:08:58 +00:00
|
|
|
world:set(child, Poison, 9999)
|
|
|
|
world:set(child, Health, 100)
|
|
|
|
world:add(child, pair(jecs.ChildOf, e))
|
|
|
|
table.insert(children, child)
|
|
|
|
end
|
|
|
|
|
|
|
|
BENCH("delete children of entity", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
world:delete(e)
|
2024-08-13 18:08:58 +00:00
|
|
|
end)
|
|
|
|
|
|
|
|
for i, child in children do
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(not world:contains(child))
|
|
|
|
CHECK(not world:has(child, pair(jecs.ChildOf, e)))
|
2024-08-13 18:08:58 +00:00
|
|
|
CHECK(not world:has(child, Health))
|
|
|
|
end
|
|
|
|
|
|
|
|
e = world:entity()
|
|
|
|
|
|
|
|
local friends = {}
|
|
|
|
for i = 1, 10 do
|
2024-10-12 20:18:11 +00:00
|
|
|
local friend = world:entity()
|
2024-08-13 18:08:58 +00:00
|
|
|
world:set(friend, Poison, 9999)
|
|
|
|
world:set(friend, Health, 100)
|
|
|
|
world:add(friend, pair(FriendsWith, e))
|
|
|
|
table.insert(friends, friend)
|
|
|
|
end
|
|
|
|
|
|
|
|
BENCH("remove friends of entity", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
world:delete(e)
|
|
|
|
end)
|
2024-08-13 18:08:58 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
for i, friend in friends do
|
|
|
|
CHECK(not world:has(friend, pair(FriendsWith, e)))
|
|
|
|
CHECK(world:has(friend, Health))
|
2024-11-13 19:05:01 +00:00
|
|
|
CHECK(world:contains(friend))
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
|
|
|
end
|
2024-08-13 18:08:58 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("fast delete")
|
|
|
|
local world = jecs.World.new()
|
2024-08-13 18:08:58 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
local entities = {}
|
2024-08-13 18:08:58 +00:00
|
|
|
local Health = world:component()
|
|
|
|
local Poison = world:component()
|
|
|
|
|
2024-08-13 18:58:07 +00:00
|
|
|
for i = 1, 100 do
|
2024-10-12 20:18:11 +00:00
|
|
|
local child = world:entity()
|
2024-08-13 18:08:58 +00:00
|
|
|
world:set(child, Poison, 9999)
|
|
|
|
world:set(child, Health, 100)
|
|
|
|
table.insert(entities, child)
|
|
|
|
end
|
|
|
|
|
|
|
|
BENCH("simple deletion of entity", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
for i = 1, START(100) do
|
2024-08-13 18:08:58 +00:00
|
|
|
local e = entities[i]
|
|
|
|
world:delete(e)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
for _, entity in entities do
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(not world:contains(entity))
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
|
|
|
end
|
2024-08-13 23:15:04 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("cycle")
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local Likes = world:component()
|
|
|
|
world:add(Likes, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
2024-08-13 23:15:04 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
world:add(bob, pair(Likes, alice))
|
|
|
|
world:add(alice, pair(Likes, bob))
|
2024-08-13 23:15:04 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
world:delete(bob)
|
|
|
|
CHECK(not world:contains(bob))
|
|
|
|
CHECK(not world:contains(alice))
|
2024-08-13 23:15:04 +00:00
|
|
|
end
|
2024-07-14 00:45:49 +00:00
|
|
|
end)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-09-10 21:56:42 +00:00
|
|
|
TEST("world:target", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("nth index")
|
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
local D = world:component()
|
|
|
|
local e = world:entity()
|
2024-09-10 21:56:42 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
world:add(e, pair(A, B))
|
|
|
|
world:add(e, pair(A, C))
|
|
|
|
world:add(e, pair(B, C))
|
|
|
|
world:add(e, pair(B, D))
|
|
|
|
world:add(e, pair(C, D))
|
|
|
|
|
|
|
|
CHECK(pair(A, B) < pair(A, C))
|
|
|
|
|
|
|
|
CHECK(world:target(e, A, 0) == B)
|
|
|
|
CHECK(world:target(e, A, 1) == C)
|
|
|
|
CHECK(world:target(e, B, 0) == C)
|
|
|
|
CHECK(world:target(e, B, 1) == D)
|
|
|
|
CHECK(world:target(e, C, 0) == D)
|
|
|
|
CHECK(world:target(e, C, 1) == nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("infer index when unspecified")
|
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
local D = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
world:add(e, pair(A, B))
|
|
|
|
world:add(e, pair(A, C))
|
|
|
|
world:add(e, pair(B, C))
|
|
|
|
world:add(e, pair(B, D))
|
|
|
|
world:add(e, pair(C, D))
|
|
|
|
|
|
|
|
CHECK(world:target(e, A) == world:target(e, A, 0))
|
|
|
|
CHECK(world:target(e, B) == world:target(e, B, 0))
|
|
|
|
CHECK(world:target(e, C) == world:target(e, C, 0))
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
CASE("loop until no target")
|
|
|
|
local world = world_new()
|
|
|
|
|
|
|
|
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
|
|
|
|
local target = world:target(e1, ROOT, 0)
|
|
|
|
while target do
|
|
|
|
i += 1
|
|
|
|
CHECK(targets[i] == target)
|
|
|
|
target = world:target(e1, ROOT, i)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(i == 10)
|
|
|
|
end
|
2024-09-10 21:56:42 +00:00
|
|
|
end)
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
TEST("world:contains", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
2024-08-13 18:08:58 +00:00
|
|
|
local id = world:entity()
|
|
|
|
CHECK(world:contains(id))
|
2024-09-10 21:56:42 +00:00
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
CASE("should not exist after delete")
|
|
|
|
world:delete(id)
|
|
|
|
CHECK(not world:contains(id))
|
|
|
|
end
|
2024-08-13 18:08:58 +00:00
|
|
|
end)
|
2024-10-12 20:01:08 +00:00
|
|
|
|
2024-08-11 02:03:18 +00:00
|
|
|
TEST("Hooks", function()
|
2024-12-20 12:08:50 +00:00
|
|
|
do CASE "OnAdd"
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Transform = world:component()
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:set(Transform, jecs.OnAdd, function(entity)
|
|
|
|
CHECK(e1 == entity)
|
|
|
|
end)
|
|
|
|
world:add(e1, Transform)
|
|
|
|
end
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
do CASE "OnSet"
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Number = world:component()
|
|
|
|
local e1 = world:entity()
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
world:set(Number, jecs.OnSet, function(entity, data)
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(e1 == entity)
|
|
|
|
CHECK(data == world:get(entity, Number))
|
|
|
|
CHECK(data == 1)
|
|
|
|
end)
|
|
|
|
world:set(e1, Number, 1)
|
|
|
|
end
|
2024-08-16 23:10:35 +00:00
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
do CASE("OnRemove")
|
2024-10-12 20:18:11 +00:00
|
|
|
do
|
|
|
|
-- basic
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
world:set(A, jecs.OnRemove, function(entity)
|
|
|
|
CHECK(e1 == entity)
|
|
|
|
CHECK(not world:has(e1, A))
|
|
|
|
end)
|
|
|
|
world:remove(e1, A)
|
|
|
|
CHECK(not world:has(e1, A))
|
|
|
|
end
|
|
|
|
do
|
|
|
|
-- [BUG] https://github.com/Ukendio/jecs/issues/118
|
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
world:set(A, jecs.OnRemove, function(entity)
|
|
|
|
world:set(entity, B, true)
|
|
|
|
CHECK(world:get(entity, A))
|
|
|
|
CHECK(world:get(entity, B))
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:set(e, A, true)
|
|
|
|
world:remove(e, A)
|
|
|
|
CHECK(not world:get(e, A))
|
|
|
|
CHECK(not world:get(e, B))
|
|
|
|
end
|
|
|
|
end
|
2024-12-20 12:08:50 +00:00
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
TEST("repro", function()
|
|
|
|
do CASE "#1"
|
|
|
|
local world = world_new()
|
|
|
|
local reproEntity = world:component()
|
|
|
|
local components = { Cooldown = world:component() :: jecs.Id<number> }
|
|
|
|
world:set(reproEntity, components.Cooldown, 2)
|
|
|
|
|
|
|
|
local function updateCooldowns(dt: number)
|
|
|
|
local toRemove = {}
|
|
|
|
|
|
|
|
for id, cooldown in world:query(components.Cooldown):iter() do
|
|
|
|
cooldown -= dt
|
|
|
|
|
|
|
|
if cooldown <= 0 then
|
|
|
|
table.insert(toRemove, id)
|
|
|
|
print("removing")
|
|
|
|
-- world:remove(id, components.Cooldown)
|
|
|
|
else
|
|
|
|
world:set(id, components.Cooldown, cooldown)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, id in toRemove do
|
|
|
|
world:remove(id, components.Cooldown)
|
|
|
|
CHECK(not world:get(id, components.Cooldown))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
updateCooldowns(1.5)
|
|
|
|
updateCooldowns(1.5)
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "#2"
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
export type Iterator<T> = () -> (Entity, T?, T?)
|
|
|
|
export type Destructor = () -> ()
|
|
|
|
|
|
|
|
-- Helpers
|
|
|
|
|
|
|
|
type ValuesMap<T> = { [Entity]: T? }
|
|
|
|
type ChangeSet = { [Entity]: true? }
|
|
|
|
type ChangeSets = { [ChangeSet]: true? }
|
|
|
|
type ChangeSetsCache = {
|
|
|
|
Added: ChangeSets,
|
|
|
|
Changed: ChangeSets,
|
|
|
|
Removed: ChangeSets,
|
|
|
|
}
|
|
|
|
|
|
|
|
local cachedChangeSets = {}
|
|
|
|
local function getChangeSets(component): ChangeSetsCache
|
|
|
|
if cachedChangeSets[component] == nil then
|
|
|
|
local changeSetsAdded: ChangeSets = {}
|
|
|
|
local changeSetsChanged: ChangeSets = {}
|
|
|
|
local changeSetsRemoved: ChangeSets = {}
|
|
|
|
world:set(component, jecs.OnAdd, function(id)
|
|
|
|
for set in changeSetsAdded do
|
|
|
|
set[id] = true
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
world:set(component, jecs.OnSet, function(id)
|
|
|
|
for set in changeSetsChanged do
|
|
|
|
set[id] = true
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
world:set(component, jecs.OnRemove, function(id)
|
|
|
|
for set in changeSetsRemoved do
|
|
|
|
set[id] = true
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
cachedChangeSets[component] = {
|
|
|
|
Added = changeSetsAdded,
|
|
|
|
Changed = changeSetsChanged,
|
|
|
|
Removed = changeSetsRemoved,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
return cachedChangeSets[component]
|
|
|
|
end
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
local function ChangeTracker<T>(component: jecs.Id): (Iterator<T>, Destructor)
|
2024-10-12 20:18:11 +00:00
|
|
|
local values: ValuesMap<T> = {}
|
|
|
|
local changeSet: ChangeSet = {}
|
|
|
|
|
|
|
|
for id in world:query(component) do
|
|
|
|
changeSet[id] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
local changeSets = getChangeSets(component)
|
|
|
|
changeSets.Added[changeSet] = true
|
|
|
|
changeSets.Changed[changeSet] = true
|
|
|
|
changeSets.Removed[changeSet] = true
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
local id: jecs.Id? = nil
|
2024-10-12 20:18:11 +00:00
|
|
|
local iter: Iterator<T> = function()
|
|
|
|
id = next(changeSet)
|
|
|
|
if id then
|
|
|
|
changeSet[id] = nil
|
|
|
|
local old: T? = values[id]
|
|
|
|
local new: T? = world:get(id, component)
|
|
|
|
if old ~= nil and new == nil then
|
|
|
|
-- Old value but no new value = removed
|
|
|
|
values[id] = nil
|
|
|
|
else
|
|
|
|
-- Old+new value or just new value = new becomes old
|
|
|
|
values[id] = new
|
|
|
|
end
|
|
|
|
return id, old, new
|
|
|
|
end
|
|
|
|
return nil :: any, nil, nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local destroy: Destructor = function()
|
|
|
|
changeSets.Added[changeSet] = nil
|
|
|
|
changeSets.Changed[changeSet] = nil
|
|
|
|
changeSets.Removed[changeSet] = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return iter, destroy
|
|
|
|
end
|
|
|
|
|
|
|
|
local Transform = world:component()
|
|
|
|
local iter, destroy = ChangeTracker(Transform)
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:set(e1, Transform, { 1, 1 })
|
|
|
|
local counter = 0
|
|
|
|
for _ in iter do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 1)
|
|
|
|
end
|
2024-08-11 02:03:18 +00:00
|
|
|
end)
|
2024-05-27 01:39:20 +00:00
|
|
|
FINISH()
|