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
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local it = testkit.test()
|
|
|
|
local TEST, CASE = it.TEST, it.CASE
|
|
|
|
local CHECK, FINISH = it.CHECK, it.FINISH
|
|
|
|
local SKIP, FOCUS = it.SKIP, it.FOCUS
|
|
|
|
local CHECK_EXPECT_ERR = it.CHECK_EXPECT_ERR
|
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
|
|
|
|
2025-03-12 14:29:24 +00:00
|
|
|
local c = {
|
|
|
|
white_underline = function(s: any)
|
|
|
|
return `\27[1;4m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
white = function(s: any)
|
|
|
|
return `\27[37;1m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
green = function(s: any)
|
|
|
|
return `\27[32;1m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
red = function(s: any)
|
|
|
|
return `\27[31;1m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
yellow = function(s: any)
|
|
|
|
return `\27[33;1m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
red_highlight = function(s: any)
|
|
|
|
return `\27[41;1;30m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
green_highlight = function(s: any)
|
|
|
|
return `\27[42;1;30m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
|
|
|
|
gray = function(s: any)
|
|
|
|
return `\27[30;1m{s}\27[0m`
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
|
|
|
local function pe(e)
|
|
|
|
local gen = ECS_GENERATION(e)
|
|
|
|
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function pp(e)
|
|
|
|
local gen = ECS_GENERATION(e)
|
|
|
|
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{jecs.ECS_ENTITY_T_HI(e)}`)
|
|
|
|
end
|
|
|
|
|
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-12-26 00:06:14 +00:00
|
|
|
columns = columns
|
2024-10-12 20:18:11 +00:00
|
|
|
}
|
2024-07-29 23:11:22 +00:00
|
|
|
end
|
|
|
|
|
2025-03-12 14:29:24 +00:00
|
|
|
local dwi = debug_world_inspect
|
|
|
|
|
2024-12-26 05:15:41 +00:00
|
|
|
local function name(world, e)
|
|
|
|
return world:get(e, jecs.Name)
|
|
|
|
end
|
|
|
|
|
2025-04-04 22:41:38 +00:00
|
|
|
|
|
|
|
local function worldReset(world)
|
|
|
|
local entity_index = world.entity_index
|
|
|
|
for i = jecs.Rest, entity_index.max_id do
|
|
|
|
local entity = entity_index.dense_array[i]
|
|
|
|
world:delete(entity)
|
|
|
|
end
|
|
|
|
for i = jecs.Rest, entity_index.max_id do
|
|
|
|
local sparse = entity_index.dense_array[i]
|
|
|
|
entity_index.sparse_array[sparse] = nil
|
|
|
|
entity_index.dense_array[i] = nil
|
|
|
|
end
|
|
|
|
entity_index.alive_count = jecs.Rest
|
|
|
|
entity_index.max_id = jecs.Rest
|
|
|
|
end
|
|
|
|
|
|
|
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
|
|
|
TEST("the great reset", function()
|
|
|
|
local world = world_new()
|
|
|
|
lifetime_tracker_add(world, {padding_enabled=false})
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
for i = 1, 10 do
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, A, true)
|
|
|
|
world:set(e, B, true)
|
|
|
|
end
|
|
|
|
world:print_entity_index()
|
|
|
|
worldReset(world)
|
|
|
|
CHECK(world:contains(A))
|
|
|
|
CHECK(world:contains(B))
|
|
|
|
world:print_entity_index()
|
|
|
|
end)
|
|
|
|
|
|
|
|
TEST("#repro3", function()
|
|
|
|
local world = world_new()
|
|
|
|
local Model = world:component()
|
|
|
|
local ModelBase = world:component()
|
|
|
|
|
|
|
|
local systems = {}
|
|
|
|
|
|
|
|
local function progress()
|
|
|
|
for _, system in systems do
|
|
|
|
system()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local newQuery = nil
|
|
|
|
local oldQuery = nil
|
|
|
|
|
|
|
|
local function modelBase()
|
|
|
|
if not newQuery then
|
|
|
|
newQuery = world:query(Model):without(ModelBase):cached()
|
|
|
|
end
|
|
|
|
if not oldQuery then
|
|
|
|
oldQuery = world:query(ModelBase):without(Model):cached()
|
|
|
|
end
|
|
|
|
for e, model in newQuery do
|
|
|
|
world:set(e, ModelBase, { "part base" })
|
|
|
|
end
|
|
|
|
for e, model in oldQuery do
|
|
|
|
world:remove(e, ModelBase)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
table.insert(systems, modelBase)
|
|
|
|
|
|
|
|
do CASE("should add the correct ModelBase for parts")
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Model, { instance = "Model" })
|
|
|
|
progress()
|
|
|
|
CHECK(world:get(e, ModelBase)[1] == "part base" )
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE("should add the correct ModelBase for parts")
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Model, { instance = "Model "})
|
|
|
|
progress()
|
|
|
|
CHECK(world:get(e, ModelBase)[1] == "part base")
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
do CASE("")
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Model, { instance = "Model "})
|
|
|
|
progress()
|
|
|
|
CHECK(world:get(e, ModelBase)[1] == "part base")
|
|
|
|
world:remove(e, Model)
|
|
|
|
progress()
|
|
|
|
CHECK(world:get(e, ModelBase) == nil)
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2025-03-26 13:23:54 +00:00
|
|
|
TEST("#adding a recycled target", function()
|
|
|
|
local world = world_new()
|
|
|
|
local R = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
local T = world:entity()
|
|
|
|
world:add(e, pair(R, T))
|
|
|
|
world:delete(T)
|
|
|
|
CHECK(not world:has(e, pair(R, T)))
|
|
|
|
local T2 = world:entity()
|
|
|
|
world:add(e, pair(R, T2))
|
|
|
|
CHECK(world:target(e, R) ~= T)
|
|
|
|
CHECK(world:target(e, R) ~= 0)
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
2025-03-30 20:29:43 +00:00
|
|
|
local entity_visualizer = require("@tools/entity_visualiser")
|
2025-03-30 20:14:22 +00:00
|
|
|
|
2025-03-25 22:13:53 +00:00
|
|
|
TEST("#repro2", function()
|
|
|
|
local world = world_new()
|
|
|
|
local Lifetime = world:component() :: jecs.Id<number>
|
|
|
|
local Particle = world:entity()
|
|
|
|
local Beam = world:entity()
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
world:set(entity, pair(Lifetime, Particle), 1)
|
|
|
|
world:set(entity, pair(Lifetime, Beam), 2)
|
2025-03-26 02:39:04 +00:00
|
|
|
world:set(entity, pair(4, 5), 6) -- noise
|
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
-- entity_visualizer.components(world, entity)
|
2025-03-25 22:13:53 +00:00
|
|
|
|
|
|
|
for e in world:each(pair(Lifetime, __)) do
|
|
|
|
local i = 0
|
|
|
|
local nth = world:target(e, Lifetime, i)
|
|
|
|
while nth do
|
2025-03-27 16:37:36 +00:00
|
|
|
-- entity_visualizer.components(world, e)
|
2025-03-26 02:39:04 +00:00
|
|
|
|
2025-03-25 22:13:53 +00:00
|
|
|
local data = world:get(e, pair(Lifetime, nth))
|
2025-03-26 02:39:04 +00:00
|
|
|
data -= 1
|
|
|
|
if data <= 0 then
|
|
|
|
world:remove(e, pair(Lifetime, nth))
|
|
|
|
else
|
|
|
|
world:set(e, pair(Lifetime, nth), data)
|
|
|
|
end
|
2025-03-25 22:13:53 +00:00
|
|
|
i += 1
|
|
|
|
nth = world:target(e, Lifetime, i)
|
|
|
|
end
|
|
|
|
end
|
2025-03-26 02:39:04 +00:00
|
|
|
|
|
|
|
CHECK(not world:has(entity, pair(Lifetime, Particle)))
|
|
|
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
|
|
|
|
end)
|
|
|
|
|
2025-03-30 20:29:43 +00:00
|
|
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
2025-03-26 02:39:04 +00:00
|
|
|
|
|
|
|
TEST("another", function()
|
|
|
|
local world = world_new()
|
2025-03-27 16:37:36 +00:00
|
|
|
-- world = lifetime_tracker_add(world, {padding_enabled=false})
|
2025-03-26 02:39:04 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
world:delete(e2)
|
|
|
|
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)
|
2025-03-25 22:13:53 +00:00
|
|
|
end)
|
|
|
|
|
2025-03-12 14:29:24 +00:00
|
|
|
TEST("#repro", function()
|
|
|
|
local world = world_new()
|
|
|
|
|
|
|
|
local function getTargets(relation)
|
|
|
|
local tgts = {}
|
|
|
|
local pairwildcard = pair(relation, jecs.Wildcard)
|
|
|
|
for _, archetype in world:query(pairwildcard):archetypes() do
|
2025-03-24 13:31:56 +00:00
|
|
|
local tr = archetype.records[pairwildcard]
|
|
|
|
local count = archetype.counts[pairwildcard]
|
|
|
|
local types = archetype.types
|
|
|
|
for _, entity in archetype.entities do
|
|
|
|
for i = 0, count - 1 do
|
|
|
|
local tgt = jecs.pair_second(world, types[i + tr])
|
|
|
|
table.insert(tgts, tgt)
|
|
|
|
end
|
|
|
|
end
|
2025-03-12 14:29:24 +00:00
|
|
|
end
|
|
|
|
return tgts
|
|
|
|
end
|
|
|
|
|
|
|
|
local Attacks = world:component()
|
|
|
|
local Eats = world:component()
|
|
|
|
|
|
|
|
local function setAttacksAndEats(entity1, entity2)
|
|
|
|
world:add(entity1, pair(Attacks, entity2))
|
|
|
|
world:add(entity1, pair(Eats, entity2))
|
|
|
|
end
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
setAttacksAndEats(e3, e1)
|
|
|
|
setAttacksAndEats(e3, e2)
|
|
|
|
setAttacksAndEats(e1, e2)
|
|
|
|
local d = dwi(world)
|
|
|
|
world:delete(e2)
|
2025-03-24 13:31:56 +00:00
|
|
|
local types1 = { pair(Attacks, e1), pair(Eats, e1) }
|
|
|
|
table.sort(types1)
|
|
|
|
|
|
|
|
|
|
|
|
CHECK(d.tbl(e1).type == "")
|
|
|
|
CHECK(d.tbl(e3).type == table.concat(types1, "_"))
|
|
|
|
|
|
|
|
for _, entity in getTargets(Attacks) do
|
|
|
|
CHECK(entity == e1)
|
|
|
|
end
|
|
|
|
for _, entity in getTargets(Eats) do
|
|
|
|
CHECK(entity == e1)
|
|
|
|
end
|
2025-03-12 14:29:24 +00:00
|
|
|
end)
|
|
|
|
|
2024-10-06 01:36:36 +00:00
|
|
|
TEST("archetype", function()
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local a1 = archetype_traverse_add(world, c1, nil :: any)
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
2025-03-26 03:59:11 +00:00
|
|
|
local A = world:component() :: jecs.Id<boolean>
|
|
|
|
local B = world:component() :: jecs.Id<boolean>
|
|
|
|
local C = world:component() :: jecs.Id<boolean>
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local archetype_index = world.archetype_index
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(#archetype_index["1"].entities == 1)
|
|
|
|
CHECK(#archetype_index["1_2"].entities == 1)
|
|
|
|
CHECK(#archetype_index["1_2_3"].entities == 1)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
world:delete(e1)
|
|
|
|
world:delete(e2)
|
|
|
|
world:delete(e3)
|
|
|
|
|
|
|
|
world:cleanup()
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
archetype_index = world.archetype_index
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK((archetype_index["1"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetype_index["1_2"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
local e4 = world:entity()
|
|
|
|
world:set(e4, A, true)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(#archetype_index["1"].entities == 1)
|
|
|
|
CHECK((archetype_index["1_2"] :: jecs.Archetype?) == nil)
|
|
|
|
CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e4, B, true)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(#archetype_index["1"].entities == 0)
|
|
|
|
CHECK(#archetype_index["1_2"].entities == 1)
|
|
|
|
CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e4, C, true)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(#archetype_index["1"].entities == 0)
|
|
|
|
CHECK(#archetype_index["1_2"].entities == 0)
|
|
|
|
CHECK(#archetype_index["1_2_3"].entities == 1)
|
2024-10-01 15:30:51 +00:00
|
|
|
end)
|
|
|
|
|
2025-03-30 20:29:43 +00:00
|
|
|
local pe = require("@tools/entity_visualiser").prettify
|
|
|
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
2025-03-24 13:31:56 +00:00
|
|
|
|
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()
|
2025-01-29 07:28:08 +00:00
|
|
|
local e = world:entity() :: number
|
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
|
|
|
|
|
2025-01-29 07:28:08 +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)
|
|
|
|
|
2024-12-24 21:38:25 +00:00
|
|
|
local p = pair(e2, e3)
|
|
|
|
CHECK(IS_PAIR(p) == true)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(ecs_pair_first(world, p) == e2 :: number)
|
|
|
|
CHECK(ecs_pair_second(world, p) == e3 :: number)
|
2024-12-24 21:38:25 +00:00
|
|
|
|
|
|
|
world:delete(e2)
|
|
|
|
local e2v2 = world:entity()
|
|
|
|
CHECK(IS_PAIR(e2v2) == false)
|
2024-07-29 23:11:22 +00:00
|
|
|
|
2024-12-24 21:38:25 +00:00
|
|
|
CHECK(IS_PAIR(pair(e2v2, e3)) == true)
|
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()
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(ECS_ID(e2) == e :: number)
|
2024-11-14 02:38:27 +00:00
|
|
|
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()
|
2025-03-26 03:59:11 +00:00
|
|
|
local pin = (jecs.Rest :: any) :: 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()
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "archetype move"
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(world.archetype_index[oldArchetype].columns[_1][oldRow] == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "pairs"
|
2024-10-12 20:18:11 +00:00
|
|
|
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)
|
2025-01-29 07:28:08 +00:00
|
|
|
|
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(e, pair(T1, T2), true :: any)
|
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
CHECK(world:get(e, pair(C1, C2)))
|
|
|
|
CHECK(world:get(e, pair(C1, T1)))
|
2024-12-24 21:38:25 +00:00
|
|
|
CHECK(world:get(e, pair(T1, C1)))
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(not world:get(e, pair(T1, T2)))
|
|
|
|
|
|
|
|
local e2 = world:entity()
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(e2, pair(jecs.ChildOf, e), true :: any)
|
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
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-26 05:15:41 +00:00
|
|
|
do CASE "cached"
|
|
|
|
local world = world_new()
|
|
|
|
local Foo = world:component()
|
|
|
|
local Bar = world:component()
|
|
|
|
local Baz = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
local q = world:query(Foo, Bar):without(Baz):cached()
|
|
|
|
world:set(e, Foo, true)
|
|
|
|
world:set(e, Bar, false)
|
|
|
|
local i = 0
|
|
|
|
|
2025-01-14 10:09:18 +00:00
|
|
|
local iter = 0
|
2024-12-26 05:15:41 +00:00
|
|
|
for _, e in q:iter() do
|
2025-01-14 10:09:18 +00:00
|
|
|
iter += 1
|
2024-12-26 05:15:41 +00:00
|
|
|
i=1
|
|
|
|
end
|
2025-01-14 10:09:18 +00:00
|
|
|
CHECK (iter == 1)
|
2024-12-26 05:15:41 +00:00
|
|
|
CHECK(i == 1)
|
|
|
|
for _, e in q:iter() do
|
|
|
|
i=2
|
|
|
|
end
|
|
|
|
CHECK(i == 2)
|
2025-01-29 07:28:08 +00:00
|
|
|
for _, e in q :: any do
|
2024-12-26 05:15:41 +00:00
|
|
|
i=3
|
|
|
|
end
|
|
|
|
CHECK(i == 3)
|
2025-01-29 07:28:08 +00:00
|
|
|
for _, e in q :: any do
|
2024-12-26 05:15:41 +00:00
|
|
|
i=4
|
|
|
|
end
|
|
|
|
CHECK(i == 4)
|
|
|
|
|
|
|
|
CHECK(#q:archetypes() == 1)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(not table.find(q:archetypes(), world.archetype_index[table.concat({Foo, Bar, Baz}, "_")]))
|
2024-12-26 05:15:41 +00:00
|
|
|
world:delete(Foo)
|
|
|
|
CHECK(#q:archetypes() == 0)
|
|
|
|
end
|
2025-01-29 07:28:08 +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
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "tag"
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:entity()
|
|
|
|
local e = world:entity()
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(e, A, "test" :: any)
|
|
|
|
end)
|
|
|
|
local count = 0
|
|
|
|
for id, a in world:query(A) :: any do
|
|
|
|
count += 1
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(a == nil)
|
|
|
|
end
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(count == 1)
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "pairs"
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
|
2025-03-26 03:59:11 +00:00
|
|
|
local C1 = world:component() :: jecs.Id<boolean>
|
|
|
|
local C2 = world:component() :: jecs.Id<boolean>
|
2024-10-12 20:18:11 +00:00
|
|
|
local T1 = world:entity()
|
|
|
|
local T2 = world:entity()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
|
2025-03-26 03:59:11 +00:00
|
|
|
local C1_C2 = pair(C1, C2)
|
|
|
|
world:set(e, C1_C2, true)
|
2024-10-12 20:18:11 +00:00
|
|
|
world:set(e, pair(C1, T1), true)
|
|
|
|
world:set(e, pair(T1, C1), true)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(e, pair(T1, T2), true :: any)
|
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2025-03-26 03:59:11 +00:00
|
|
|
for id, a, b, c, d in world:query(pair(C1, C2), pair(C1, T1), pair(T1, C1), pair(T1, T2)):iter() do
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(a == true)
|
|
|
|
CHECK(b == true)
|
2024-12-24 21:38:25 +00:00
|
|
|
CHECK(c == true)
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A) :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
2025-01-29 07:28:08 +00:00
|
|
|
for _ in q :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
i += 1
|
|
|
|
end
|
2025-01-29 07:28:08 +00:00
|
|
|
for _ in q :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
2025-01-29 07:28:08 +00:00
|
|
|
for _ in world:query(B, C) :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for entity, a, b, c, d, e, f, g, h, i in world:query(unpack(components)) :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local e: number, data = it()
|
2024-10-12 20:18:11 +00:00
|
|
|
while e do
|
2025-01-29 07:28:08 +00:00
|
|
|
if e == eA :: number then
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(data)
|
2025-01-29 07:28:08 +00:00
|
|
|
elseif e == eAB :: number then
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(data)
|
|
|
|
else
|
|
|
|
CHECK(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
e, data = it()
|
|
|
|
end
|
|
|
|
CHECK(true)
|
|
|
|
end
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
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
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A) :: any do
|
2024-05-27 01:39:20 +00:00
|
|
|
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
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A):without(B) :: any do
|
2024-05-27 01:39:20 +00:00
|
|
|
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)
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, bool in world:query(pair(Eats, Apples)) :: any 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
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) :: any do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) :: any 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
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) :: any 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
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) :: any do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
CHECK(count == 1)
|
|
|
|
end
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "should only relate alive entities"
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
2025-01-29 07:28:08 +00:00
|
|
|
local Apples = world:component()
|
|
|
|
local Oranges = world:component()
|
2024-05-27 01:39:20 +00:00
|
|
|
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")
|
2025-01-29 07:28:08 +00:00
|
|
|
world:set(alice, pair(Eats, Oranges) :: Entity<string>, "alice eats oranges")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
|
|
|
|
local count = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for _, data in world:query(pair(Wildcard, Apples)) :: any 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)
|
2025-01-29 07:28:08 +00:00
|
|
|
|
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)
|
2025-03-26 02:39:04 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
|
|
|
end)
|
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")
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK(world:parent(bob) :: number == alice :: number) -- O(1)
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local count = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for _, name in world:query(Name, pair(ChildOf, alice)) :: any 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
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A) :: any 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
|
|
|
|
|
2025-03-27 16:37:36 +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
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A) :: any do
|
2024-07-29 23:11:22 +00:00
|
|
|
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
|
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("spawning")
|
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()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
world:add(e2, A)
|
|
|
|
world:add(e2, B)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for id in world:query(A) :: any do
|
2024-07-29 23:11:22 +00:00
|
|
|
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
|
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("should not find any entities")
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
2025-01-29 07:28:08 +00:00
|
|
|
for _ in world:query(pair(Hello, Bob)):without(Bob) :: any do
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("without")
|
|
|
|
-- 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
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
local counter = 0
|
|
|
|
for e, a, b in world:query(_1, _2):without(_3) :: any do
|
|
|
|
counter += 1
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
2025-03-27 16:37:36 +00:00
|
|
|
CHECK(counter == 0)
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local e3 = world:entity()
|
2024-12-20 12:08:50 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = 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)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for entity: number in world:each(A) do
|
|
|
|
if entity == e1 :: number or entity == e2 :: number or entity == e3 :: number then
|
2024-12-20 12:08:50 +00:00
|
|
|
CHECK(true)
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
CHECK(false)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
TEST("world:children", function()
|
|
|
|
local world = world_new()
|
2025-01-15 12:03:28 +00:00
|
|
|
local C = world:component()
|
|
|
|
local T = world:entity()
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
local e1 = world:entity()
|
2025-01-15 12:03:28 +00:00
|
|
|
world:set(e1, C, true)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local e2 = world:entity() :: number
|
2024-12-20 12:08:50 +00:00
|
|
|
|
2025-01-15 12:03:28 +00:00
|
|
|
world:add(e2, T)
|
2024-12-20 12:08:50 +00:00
|
|
|
world:add(e2, pair(ChildOf, e1))
|
2025-01-15 12:03:28 +00:00
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
local e3 = world:entity() :: number
|
2024-12-20 12:08:50 +00:00
|
|
|
world:add(e3, pair(ChildOf, e1))
|
|
|
|
|
2025-01-15 12:03:28 +00:00
|
|
|
local count = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for entity: number in world:children(e1) do
|
2025-01-15 12:03:28 +00:00
|
|
|
count += 1
|
2024-12-20 12:08:50 +00:00
|
|
|
if entity == e2 or entity == e3 then
|
|
|
|
CHECK(true)
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
CHECK(false)
|
|
|
|
end
|
2025-01-15 12:03:28 +00:00
|
|
|
CHECK(count == 2)
|
|
|
|
|
|
|
|
world:remove(e2, pair(ChildOf, e1))
|
|
|
|
|
|
|
|
count = 0
|
|
|
|
for entity in world:children(e1) do
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 1)
|
2024-12-20 12:08:50 +00:00
|
|
|
end)
|
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
TEST("world:clear()", function()
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("should remove its components")
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
local D = world:component()
|
2025-03-27 04:08:38 +00:00
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
local e = world:entity()
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
2025-03-27 04:08:38 +00:00
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
world:set(e, A, true)
|
|
|
|
world:set(e, B, true)
|
2025-03-27 04:08:38 +00:00
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
world:set(e1, A, true)
|
|
|
|
world:set(e1, B, true)
|
2025-03-27 04:08:38 +00:00
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
CHECK(world:get(e, A))
|
|
|
|
CHECK(world:get(e, B))
|
2025-03-27 04:08:38 +00:00
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
world:clear(A)
|
|
|
|
CHECK(world:get(e, A) == nil)
|
|
|
|
CHECK(world:get(e, B))
|
|
|
|
CHECK(world:get(e1, A) == nil)
|
|
|
|
CHECK(world:get(e1, B))
|
|
|
|
end
|
2025-03-28 00:53:19 +00:00
|
|
|
|
|
|
|
do CASE("remove cleared ID from entities")
|
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
do
|
|
|
|
local id1 = world:entity()
|
|
|
|
local id2 = world:entity()
|
|
|
|
local id3 = world:entity()
|
|
|
|
|
|
|
|
world:set(id1, A, true)
|
|
|
|
|
|
|
|
world:set(id2, A, true)
|
|
|
|
world:set(id2, B, true)
|
|
|
|
|
|
|
|
world:set(id3, A, true)
|
|
|
|
world:set(id3, B, true)
|
|
|
|
world:set(id3, C, true)
|
|
|
|
|
|
|
|
world:clear(A)
|
|
|
|
|
|
|
|
CHECK(not world:has(id1, A))
|
|
|
|
CHECK(not world:has(id2, A))
|
|
|
|
CHECK(not world:has(id3, A))
|
|
|
|
|
|
|
|
CHECK(world:has(id2, B))
|
|
|
|
CHECK(world:has(id3, B, C))
|
|
|
|
|
|
|
|
world:clear(C)
|
|
|
|
|
|
|
|
CHECK(world:has(id2, B))
|
|
|
|
CHECK(world:has(id3, B))
|
|
|
|
|
|
|
|
CHECK(world:contains(A))
|
|
|
|
CHECK(world:contains(C))
|
|
|
|
CHECK(world:has(A, jecs.Component))
|
|
|
|
CHECK(world:has(B, jecs.Component))
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
local id1 = world:entity()
|
|
|
|
local id2 = world:entity()
|
|
|
|
local id3 = world:entity()
|
|
|
|
|
|
|
|
local tgt = world:entity()
|
|
|
|
|
|
|
|
world:add(id1, pair(A, tgt))
|
|
|
|
world:add(id1, pair(B, tgt))
|
|
|
|
world:add(id1, pair(C, tgt))
|
|
|
|
|
|
|
|
world:add(id2, pair(A, tgt))
|
|
|
|
world:add(id2, pair(B, tgt))
|
|
|
|
world:add(id2, pair(C, tgt))
|
|
|
|
|
|
|
|
world:add(id3, pair(A, tgt))
|
|
|
|
world:add(id3, pair(B, tgt))
|
|
|
|
world:add(id3, pair(C, tgt))
|
|
|
|
|
|
|
|
world:clear(B)
|
|
|
|
CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
|
|
|
|
CHECK(not world:has(id1, pair(B, tgt)))
|
|
|
|
CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
|
|
|
|
CHECK(not world:has(id1, pair(B, tgt)))
|
|
|
|
CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
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()
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("should find Tag on entity")
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("should return false when missing one tag")
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
2025-03-27 16:37:36 +00:00
|
|
|
do CASE("only components should have EcsComponent trait")
|
2024-10-12 20:18:11 +00:00
|
|
|
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)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(e, C, 11 :: any)
|
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
|
|
|
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()
|
2025-03-01 19:15:52 +00:00
|
|
|
do CASE "invoke OnRemove hooks"
|
|
|
|
local world = world_new()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
|
|
|
|
local Stable = world:component()
|
|
|
|
world:set(Stable, jecs.OnRemove, function(e)
|
|
|
|
CHECK(e == e1)
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:set(e1, Stable, true)
|
|
|
|
world:set(e2, Stable, true)
|
|
|
|
|
|
|
|
world:delete(e1)
|
|
|
|
end
|
2025-02-01 12:07:55 +00:00
|
|
|
do CASE "delete recycled entity id used as component"
|
|
|
|
local world = world_new()
|
|
|
|
local id = world:entity()
|
|
|
|
world:add(id, jecs.Component)
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, id, 1)
|
|
|
|
CHECK(world:get(e, id) == 1)
|
|
|
|
world:delete(id)
|
|
|
|
local recycled = world:entity()
|
|
|
|
world:add(recycled, jecs.Component)
|
|
|
|
world:set(e, recycled, 1)
|
|
|
|
CHECK(world:has(recycled, jecs.Component))
|
|
|
|
CHECK(world:get(e, recycled) == 1)
|
|
|
|
end
|
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
|
2025-03-28 00:53:19 +00:00
|
|
|
do CASE("should allow deleting components")
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
|
2025-03-28 00:53:19 +00:00
|
|
|
do CASE("delete entities using another Entity as component with Delete cleanup action")
|
2024-10-12 20:18:11 +00:00
|
|
|
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)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(id, Health, 50 :: any)
|
|
|
|
end)
|
2024-08-13 18:08:58 +00:00
|
|
|
local id1 = world:entity()
|
|
|
|
world:set(id1, Poison, 500)
|
2025-01-29 07:28:08 +00:00
|
|
|
CHECK_EXPECT_ERR(function()
|
|
|
|
world:set(id1, Health, 50 :: any)
|
|
|
|
end)
|
2024-08-13 18:08:58 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2025-03-28 00:53:19 +00:00
|
|
|
|
|
|
|
do CASE("delete children")
|
2024-10-12 20:18:11 +00:00
|
|
|
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
|
|
|
|
2025-03-12 14:29:24 +00:00
|
|
|
local d = debug_world_inspect(world)
|
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
|
|
|
|
2025-03-28 00:53:19 +00:00
|
|
|
do CASE("remove deleted ID from entities")
|
|
|
|
local world = world_new()
|
|
|
|
do
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
local id1 = world:entity()
|
|
|
|
local id2 = world:entity()
|
|
|
|
local id3 = world:entity()
|
|
|
|
|
|
|
|
world:set(id1, A, true)
|
|
|
|
|
|
|
|
world:set(id2, A, true)
|
|
|
|
world:set(id2, B, true)
|
|
|
|
|
|
|
|
world:set(id3, A, true)
|
|
|
|
world:set(id3, B, true)
|
|
|
|
world:set(id3, C, true)
|
|
|
|
|
|
|
|
world:delete(A)
|
|
|
|
|
|
|
|
CHECK(not world:has(id1, A))
|
|
|
|
CHECK(not world:has(id2, A))
|
|
|
|
CHECK(not world:has(id3, A))
|
|
|
|
|
|
|
|
CHECK(world:has(id2, B))
|
|
|
|
CHECK(world:has(id3, B, C))
|
|
|
|
|
|
|
|
world:delete(C)
|
|
|
|
|
|
|
|
CHECK(world:has(id2, B))
|
|
|
|
CHECK(world:has(id3, B))
|
|
|
|
|
|
|
|
CHECK(not world:contains(A))
|
|
|
|
CHECK(not world:contains(C))
|
|
|
|
end
|
|
|
|
|
|
|
|
do
|
|
|
|
local A = world:component()
|
|
|
|
world:add(A, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
world:add(C, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
|
|
|
|
|
|
local id1 = world:entity()
|
|
|
|
local id2 = world:entity()
|
|
|
|
local id3 = world:entity()
|
|
|
|
|
|
|
|
world:set(id1, C, true)
|
|
|
|
|
|
|
|
world:set(id2, pair(A, id1), true)
|
|
|
|
world:set(id2, B, true)
|
|
|
|
|
|
|
|
world:set(id3, B, true)
|
|
|
|
world:set(id3, pair(C, id2), true)
|
|
|
|
|
|
|
|
world:delete(id1)
|
|
|
|
|
|
|
|
CHECK(not world:contains(id1))
|
|
|
|
CHECK(not world:contains(id2))
|
|
|
|
CHECK(not world:contains(id3))
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
local id1 = world:entity()
|
|
|
|
local id2 = world:entity()
|
|
|
|
local id3 = world:entity()
|
|
|
|
|
|
|
|
|
|
|
|
world:set(id2, A, true)
|
|
|
|
world:set(id2, pair(B, id1), true)
|
|
|
|
|
|
|
|
world:set(id3, A, true)
|
|
|
|
world:set(id3, pair(B, id1), true)
|
|
|
|
world:set(id3, C, true)
|
|
|
|
|
|
|
|
world:delete(id1)
|
|
|
|
|
|
|
|
CHECK(not world:contains(id1))
|
|
|
|
CHECK(world:contains(id2))
|
|
|
|
CHECK(world:contains(id3))
|
|
|
|
|
|
|
|
CHECK(world:has(id2, A))
|
|
|
|
CHECK(world:has(id3, A, C))
|
|
|
|
|
|
|
|
CHECK(not world:target(id2, B))
|
|
|
|
CHECK(not world:target(id3, B))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
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-12-26 00:06:14 +00:00
|
|
|
do CASE("nth index")
|
2024-10-12 20:18:11 +00:00
|
|
|
local world = world_new()
|
|
|
|
local A = world:component()
|
2024-12-26 00:06:14 +00:00
|
|
|
world:set(A, jecs.Name, "A")
|
2024-10-12 20:18:11 +00:00
|
|
|
local B = world:component()
|
2024-12-26 00:06:14 +00:00
|
|
|
world:set(B, jecs.Name, "B")
|
2024-10-12 20:18:11 +00:00
|
|
|
local C = world:component()
|
2024-12-26 00:06:14 +00:00
|
|
|
world:set(C, jecs.Name, "C")
|
2024-10-12 20:18:11 +00:00
|
|
|
local D = world:component()
|
2024-12-26 00:06:14 +00:00
|
|
|
world:set(D, jecs.Name, "D")
|
|
|
|
local E = world:component()
|
|
|
|
world:set(E, jecs.Name, "E")
|
2024-10-12 20:18:11 +00:00
|
|
|
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))
|
2024-12-26 00:06:14 +00:00
|
|
|
world:add(e, pair(A, D))
|
|
|
|
world:add(e, pair(A, E))
|
2024-10-12 20:18:11 +00:00
|
|
|
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))
|
2025-03-24 16:36:13 +00:00
|
|
|
CHECK(pair(A, C) < pair(A, D))
|
|
|
|
CHECK(pair(C, A) < pair(C, D))
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2024-12-26 00:06:14 +00:00
|
|
|
local records = debug_world_inspect(world).records(e)
|
|
|
|
CHECK(jecs.pair_first(world, pair(B, C)) == B)
|
2025-03-24 16:36:13 +00:00
|
|
|
local r = jecs.entity_index_try_get(world.entity_index, e)
|
|
|
|
local archetype = r.archetype
|
|
|
|
local counts = archetype.counts
|
|
|
|
CHECK(counts[pair(A, __)] == 4)
|
2025-02-26 16:04:17 +00:00
|
|
|
CHECK(records[pair(B, C)] > records[pair(A, E)])
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(world:target(e, A, 0) == B)
|
|
|
|
CHECK(world:target(e, A, 1) == C)
|
2024-12-26 00:06:14 +00:00
|
|
|
CHECK(world:target(e, A, 2) == D)
|
|
|
|
CHECK(world:target(e, A, 3) == E)
|
2024-10-12 20:18:11 +00:00
|
|
|
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)
|
2025-03-24 16:36:13 +00:00
|
|
|
|
|
|
|
CHECK(archetype.records[pair(A, B)] == 1)
|
|
|
|
CHECK(archetype.records[pair(A, C)] == 2)
|
|
|
|
CHECK(archetype.records[pair(A, D)] == 3)
|
|
|
|
CHECK(archetype.records[pair(A, E)] == 4)
|
|
|
|
|
|
|
|
CHECK(world:target(e, C, 0) == D)
|
|
|
|
CHECK(world:target(e, C, 1) == nil)
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
|
|
|
|
2025-03-30 20:49:11 +00:00
|
|
|
local call = 0
|
2025-03-30 19:31:18 +00:00
|
|
|
world:set(Number, jecs.OnChange, function(entity, data)
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(e1 == entity)
|
2025-03-30 20:49:11 +00:00
|
|
|
if call == 1 then
|
|
|
|
CHECK(false)
|
|
|
|
elseif call == 2 then
|
|
|
|
CHECK(world:get(entity, Number) == data)
|
|
|
|
end
|
2024-10-12 20:18:11 +00:00
|
|
|
CHECK(data == 1)
|
|
|
|
end)
|
2025-03-30 20:49:11 +00:00
|
|
|
|
|
|
|
call = 1
|
|
|
|
world:set(e1, Number, 1)
|
|
|
|
call = 2
|
2024-10-12 20:18:11 +00:00
|
|
|
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()
|
2025-01-29 07:28:08 +00:00
|
|
|
local A = world:component() :: Entity<boolean>
|
2024-10-12 20:18:11 +00:00
|
|
|
local e1 = world:entity()
|
|
|
|
world:set(A, jecs.OnRemove, function(entity)
|
|
|
|
CHECK(e1 == entity)
|
2025-03-13 15:56:02 +00:00
|
|
|
CHECK(world:has(e1, A))
|
2024-10-12 20:18:11 +00:00
|
|
|
end)
|
2025-03-13 15:56:02 +00:00
|
|
|
world:add(e1, A)
|
|
|
|
|
2024-10-12 20:18:11 +00:00
|
|
|
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))
|
2025-03-24 16:36:13 +00:00
|
|
|
CHECK(world:get(e, B))
|
2024-10-12 20:18:11 +00:00
|
|
|
end
|
|
|
|
end
|
2024-12-20 12:08:50 +00:00
|
|
|
end)
|
2024-10-12 20:18:11 +00:00
|
|
|
|
2025-01-14 10:09:18 +00:00
|
|
|
TEST("change tracking", function()
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "#1"
|
2025-01-14 10:09:18 +00:00
|
|
|
local world = world_new()
|
2025-01-29 07:28:08 +00:00
|
|
|
local Foo = world:component() :: Entity<number>
|
2025-01-14 10:09:18 +00:00
|
|
|
local Previous = jecs.Rest
|
|
|
|
|
|
|
|
local q1 = world
|
|
|
|
:query(Foo)
|
|
|
|
:without(pair(Previous, Foo))
|
|
|
|
:cached()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:set(e1, Foo, 1)
|
|
|
|
local e2 = world:entity()
|
|
|
|
world:set(e2, Foo, 2)
|
|
|
|
|
|
|
|
local i = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, new in q1 :: any do
|
2025-01-14 10:09:18 +00:00
|
|
|
i += 1
|
|
|
|
world:set(e, pair(Previous, Foo), new)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(i == 2)
|
|
|
|
local j = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, new in q1 :: any do
|
2025-01-14 10:09:18 +00:00
|
|
|
j += 1
|
|
|
|
world:set(e, pair(Previous, Foo), new)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(j == 0)
|
|
|
|
end
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "#2"
|
2025-01-14 10:09:18 +00:00
|
|
|
local world = world_new()
|
2025-01-29 07:28:08 +00:00
|
|
|
local component = world:component() :: Entity<number>
|
2025-01-14 10:09:18 +00:00
|
|
|
local tag = world:entity()
|
|
|
|
local previous = jecs.Rest
|
|
|
|
|
|
|
|
local q1 = world:query(component):without(pair(previous, component), tag):cached()
|
|
|
|
|
|
|
|
local testEntity = world:entity()
|
|
|
|
|
|
|
|
world:set(testEntity, component, 10)
|
|
|
|
|
|
|
|
local i = 0
|
2025-01-29 07:28:08 +00:00
|
|
|
for entity, number in q1 :: any do
|
2025-01-14 10:09:18 +00:00
|
|
|
i += 1
|
2025-01-16 08:02:21 +00:00
|
|
|
world:add(testEntity, tag)
|
2025-01-14 10:09:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(i == 1)
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
for e, n in q1 :: any do
|
2025-01-16 08:02:21 +00:00
|
|
|
world:set(e, pair(previous, component), n)
|
2025-01-14 10:09:18 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
2024-12-20 12:08:50 +00:00
|
|
|
TEST("repro", function()
|
|
|
|
do CASE "#1"
|
|
|
|
local world = world_new()
|
|
|
|
local reproEntity = world:component()
|
2025-03-24 13:31:56 +00:00
|
|
|
local components = { Cooldown = world:component() :: jecs.Entity<number> }
|
2024-12-20 12:08:50 +00:00
|
|
|
world:set(reproEntity, components.Cooldown, 2)
|
|
|
|
|
|
|
|
local function updateCooldowns(dt: number)
|
|
|
|
local toRemove = {}
|
|
|
|
|
2025-03-24 13:31:56 +00:00
|
|
|
local it = world:query(components.Cooldown):iter()
|
|
|
|
for id, cooldown in it do
|
2024-12-20 12:08:50 +00:00
|
|
|
cooldown -= dt
|
|
|
|
|
|
|
|
if cooldown <= 0 then
|
|
|
|
table.insert(toRemove, id)
|
|
|
|
-- 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
|
|
|
|
|
2025-01-29 07:28:08 +00:00
|
|
|
do CASE "#2" -- ISSUE #171
|
2025-01-05 05:41:52 +00:00
|
|
|
local world = world_new()
|
|
|
|
local component1 = world:component()
|
|
|
|
local tag1 = world:entity()
|
2025-01-15 12:03:28 +00:00
|
|
|
|
2025-01-05 05:41:52 +00:00
|
|
|
local query = world:query(component1):with(tag1):cached()
|
2025-01-15 12:03:28 +00:00
|
|
|
|
2025-01-05 05:41:52 +00:00
|
|
|
local entity = world:entity()
|
|
|
|
world:set(entity, component1, "some data")
|
|
|
|
|
|
|
|
local counter = 0
|
|
|
|
for x in query:iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 0)
|
|
|
|
end
|
2024-08-11 02:03:18 +00:00
|
|
|
end)
|
2025-01-16 08:02:21 +00:00
|
|
|
|
|
|
|
TEST("wildcard query", function()
|
|
|
|
do CASE "#1"
|
|
|
|
local world = world_new()
|
|
|
|
local pair = jecs.pair
|
|
|
|
|
|
|
|
local Relation = world:entity()
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
local A = world:entity()
|
|
|
|
|
|
|
|
local relationship = pair(Relation, Wildcard)
|
|
|
|
local query = world:query(relationship):cached()
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
|
2025-03-24 16:36:13 +00:00
|
|
|
local p = pair(Relation, A)
|
|
|
|
CHECK(jecs.pair_first(world, p) == Relation)
|
|
|
|
CHECK(jecs.pair_second(world, p) == A)
|
|
|
|
local w = dwi(world)
|
2025-01-16 08:02:21 +00:00
|
|
|
world:add(entity, pair(Relation, A))
|
|
|
|
|
|
|
|
local counter = 0
|
|
|
|
for e in query:iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 1)
|
|
|
|
end
|
|
|
|
do CASE "#2"
|
|
|
|
local world = world_new()
|
|
|
|
local pair = jecs.pair
|
|
|
|
|
|
|
|
local Relation = world:entity()
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
local A = world:entity()
|
|
|
|
|
|
|
|
local relationship = pair(Relation, Wildcard)
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
|
|
|
|
world:add(entity, pair(Relation, A))
|
|
|
|
|
|
|
|
local counter = 0
|
|
|
|
for e in world:query(relationship):iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 1)
|
|
|
|
end
|
|
|
|
do CASE "#3"
|
|
|
|
local world = world_new()
|
|
|
|
local pair = jecs.pair
|
|
|
|
|
|
|
|
local Relation = world:entity()
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
local A = world:entity()
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
|
|
|
|
world:add(entity, pair(Relation, A))
|
|
|
|
|
|
|
|
local relationship = pair(Relation, Wildcard)
|
|
|
|
local query = world:query(relationship):cached()
|
|
|
|
|
|
|
|
local counter = 0
|
|
|
|
for e in query:iter() do
|
|
|
|
counter += 1
|
|
|
|
end
|
|
|
|
CHECK(counter == 1)
|
|
|
|
end
|
|
|
|
end)
|
2025-01-16 23:45:29 +00:00
|
|
|
|
|
|
|
TEST("world:delete() invokes OnRemove hook", function()
|
|
|
|
do CASE "#1"
|
|
|
|
local world = world_new()
|
2025-01-29 07:28:08 +00:00
|
|
|
|
2025-01-16 23:45:29 +00:00
|
|
|
local A = world:entity()
|
|
|
|
local entity = world:entity()
|
|
|
|
|
|
|
|
local called = false
|
|
|
|
world:set(A, jecs.OnRemove, function(e)
|
|
|
|
called = true
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:add(entity, A)
|
|
|
|
world:delete(entity)
|
|
|
|
|
|
|
|
CHECK(called)
|
|
|
|
end
|
|
|
|
do CASE "#2"
|
|
|
|
local world = world_new()
|
|
|
|
local pair = jecs.pair
|
|
|
|
|
|
|
|
local Relation = world:entity()
|
|
|
|
local A = world:entity()
|
|
|
|
local B = world:entity()
|
|
|
|
|
|
|
|
world:add(Relation, pair(jecs.OnDelete, jecs.Delete))
|
|
|
|
|
|
|
|
local entity = world:entity()
|
|
|
|
|
|
|
|
local called = false
|
|
|
|
world:set(A, jecs.OnRemove, function(e)
|
|
|
|
called = true
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:add(entity, A)
|
|
|
|
world:add(entity, pair(Relation, B))
|
2025-01-29 07:28:08 +00:00
|
|
|
|
2025-01-16 23:45:29 +00:00
|
|
|
world:delete(B)
|
|
|
|
|
2025-01-17 00:46:14 +00:00
|
|
|
CHECK(called)
|
|
|
|
end
|
|
|
|
do CASE "#3"
|
|
|
|
local world = world_new()
|
|
|
|
local pair = jecs.pair
|
|
|
|
|
|
|
|
local viewingContainer = world:entity()
|
|
|
|
local character = world:entity()
|
|
|
|
local container = world:entity()
|
|
|
|
|
|
|
|
local called = false
|
|
|
|
world:set(viewingContainer, jecs.OnRemove, function(e)
|
|
|
|
called = true
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:add(character, pair(viewingContainer, container))
|
|
|
|
|
|
|
|
world:delete(container)
|
|
|
|
|
2025-01-16 23:45:29 +00:00
|
|
|
CHECK(called)
|
|
|
|
end
|
|
|
|
end)
|
2024-05-27 01:39:20 +00:00
|
|
|
FINISH()
|