2024-05-01 12:41:10 +00:00
|
|
|
local testkit = require("../testkit")
|
|
|
|
local jecs = require("../lib/init")
|
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
|
|
|
|
local ECS_PAIR = jecs.ECS_PAIR
|
|
|
|
local getAlive = jecs.getAlive
|
2024-05-14 15:52:41 +00:00
|
|
|
local ECS_PAIR_RELATION = jecs.ECS_PAIR_RELATION
|
|
|
|
local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT
|
2024-05-01 12:41:10 +00:00
|
|
|
|
|
|
|
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
2024-05-16 22:17:53 +00:00
|
|
|
local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
|
|
|
|
local ok, err: string? = pcall(fn, ...)
|
|
|
|
|
|
|
|
if not CHECK(not ok, 2) then
|
|
|
|
local i = string.find(err :: string, " ")
|
|
|
|
assert(i)
|
|
|
|
local msg = string.sub(err :: string, i+1)
|
|
|
|
CHECK(msg == s, 2)
|
|
|
|
end
|
|
|
|
end
|
2024-05-01 12:41:10 +00:00
|
|
|
local N = 10
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
TEST("world", function()
|
|
|
|
do CASE "should be iterable"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
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 count = 0
|
|
|
|
for id, data in world do
|
|
|
|
count += 1
|
|
|
|
if id == eA then
|
|
|
|
CHECK(data[A] == true)
|
|
|
|
CHECK(data[B] == nil)
|
|
|
|
elseif id == eB then
|
|
|
|
CHECK(data[A] == nil)
|
|
|
|
CHECK(data[B] == true)
|
|
|
|
elseif id == eAB then
|
|
|
|
CHECK(data[A] == true)
|
|
|
|
CHECK(data[B] == true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-10 18:35:41 +00:00
|
|
|
-- components are registered in the entity index as well
|
|
|
|
-- so this test has to add 2 to account for them
|
|
|
|
CHECK(count == 3 + 2)
|
2024-05-10 15:59:57 +00:00
|
|
|
end
|
|
|
|
|
2024-05-01 12:41:10 +00:00
|
|
|
do CASE "should query all matching entities"
|
|
|
|
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 CASE "should query all matching entities when irrelevant component is removed"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
2024-05-07 22:57:22 +00:00
|
|
|
local C = world:component()
|
2024-05-01 12:41:10 +00:00
|
|
|
|
|
|
|
local entities = {}
|
|
|
|
for i = 1, N do
|
|
|
|
local id = world:entity()
|
|
|
|
|
2024-05-07 22:57:22 +00:00
|
|
|
-- specifically put them in disorder to track regression
|
|
|
|
-- https://github.com/Ukendio/jecs/pull/15
|
2024-05-01 12:41:10 +00:00
|
|
|
world:set(id, B, true)
|
2024-05-07 22:57:22 +00:00
|
|
|
world:set(id, A, true)
|
|
|
|
if i > 5 then world:remove(id, B) end
|
2024-05-01 12:41:10 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
do CASE "should query all entities without B"
|
|
|
|
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-05-03 00:39:59 +00:00
|
|
|
do CASE "should allow setting components in 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
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
do CASE "should allow deleting components"
|
2024-05-03 00:39:59 +00:00
|
|
|
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)
|
2024-05-10 15:59:57 +00:00
|
|
|
local id1 = world:entity()
|
|
|
|
world:set(id1, Poison, 500)
|
|
|
|
world:set(id1, Health, 50)
|
|
|
|
|
2024-05-03 00:39:59 +00:00
|
|
|
world:delete(id)
|
|
|
|
|
|
|
|
CHECK(world:get(id, Poison) == nil)
|
|
|
|
CHECK(world:get(id, Health) == nil)
|
2024-05-10 15:59:57 +00:00
|
|
|
CHECK(world:get(id1, Poison) == 500)
|
|
|
|
CHECK(world:get(id1, Health) == 50)
|
|
|
|
|
2024-05-03 00:39:59 +00:00
|
|
|
end
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
do CASE "should allow remove that doesn't exist on entity"
|
2024-05-07 22:57:22 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local Health = world:entity()
|
|
|
|
local Poison = world:component()
|
|
|
|
|
|
|
|
local id = world:entity()
|
|
|
|
world:set(id, Health, 50)
|
|
|
|
world:remove(id, Poison)
|
|
|
|
|
|
|
|
CHECK(world:get(id, Poison) == nil)
|
|
|
|
CHECK(world:get(id, Health) == 50)
|
|
|
|
end
|
2024-05-10 15:59:57 +00:00
|
|
|
|
|
|
|
do CASE "should increment generation"
|
2024-05-05 13:22:02 +00:00
|
|
|
local world = jecs.World.new()
|
2024-05-10 15:59:57 +00:00
|
|
|
local e = world:entity()
|
2024-05-12 22:53:51 +00:00
|
|
|
CHECK(ECS_ID(e) == 1 + jecs.Rest)
|
2024-05-10 15:59:57 +00:00
|
|
|
CHECK(getAlive(world.entityIndex, ECS_ID(e)) == e)
|
|
|
|
CHECK(ECS_GENERATION(e) == 0) -- 0
|
|
|
|
e = ECS_GENERATION_INC(e)
|
|
|
|
CHECK(ECS_GENERATION(e) == 1) -- 1
|
|
|
|
end
|
2024-05-05 13:22:02 +00:00
|
|
|
|
2024-05-12 22:53:51 +00:00
|
|
|
do CASE "should get alive from index in the dense array"
|
2024-05-10 15:59:57 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local _e = world:entity()
|
|
|
|
local e2 = world:entity()
|
|
|
|
local e3 = world:entity()
|
|
|
|
|
|
|
|
CHECK(IS_PAIR(world:entity()) == false)
|
|
|
|
|
|
|
|
local pair = ECS_PAIR(e2, e3)
|
|
|
|
CHECK(IS_PAIR(pair) == true)
|
2024-05-14 15:52:41 +00:00
|
|
|
CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2)
|
|
|
|
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
|
2024-05-05 13:22:02 +00:00
|
|
|
end
|
|
|
|
|
2024-05-12 22:53:51 +00:00
|
|
|
do CASE "should allow querying for relations"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
|
|
|
|
world:set(bob, ECS_PAIR(Eats, Apples), true)
|
|
|
|
for e, bool in world:query(ECS_PAIR(Eats, Apples)) do
|
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(bool)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should allow wildcards in queries"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
|
|
|
|
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
|
|
|
for e, data in world:query(ECS_PAIR(Eats, w)) do
|
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
for e, data in world:query(ECS_PAIR(w, Apples)) do
|
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should match against multiple pairs"
|
|
|
|
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, ECS_PAIR(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges")
|
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
|
|
|
local count = 0
|
|
|
|
for e, data in world:query(ECS_PAIR(Eats, w)) do
|
|
|
|
count += 1
|
|
|
|
if e == bob then
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
else
|
|
|
|
CHECK(data == "alice eats oranges")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 2)
|
|
|
|
count = 0
|
|
|
|
|
|
|
|
for e, data in world:query(ECS_PAIR(w, Apples)) do
|
|
|
|
count += 1
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
CHECK(count == 1)
|
|
|
|
end
|
2024-05-16 22:17:53 +00:00
|
|
|
|
|
|
|
do CASE "should only relate alive entities"
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
2024-05-16 23:05:40 +00:00
|
|
|
world:set(bob, Apples, "apples")
|
2024-05-16 22:17:53 +00:00
|
|
|
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges")
|
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
|
|
|
|
local count = 0
|
2024-05-16 23:05:40 +00:00
|
|
|
for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do
|
2024-05-16 22:17:53 +00:00
|
|
|
count += 1
|
|
|
|
end
|
2024-05-16 23:05:40 +00:00
|
|
|
|
|
|
|
world:delete(ECS_PAIR(Eats, Apples))
|
2024-05-16 22:17:53 +00:00
|
|
|
|
|
|
|
CHECK(count == 0)
|
2024-05-16 23:05:40 +00:00
|
|
|
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
|
2024-05-16 22:17:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should error when setting invalid pair"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
|
|
|
|
CHECK_NO_ERR("Apples should be dead", function()
|
|
|
|
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should find target for ChildOf"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local ChildOf = world:component()
|
|
|
|
local Name = world:component()
|
|
|
|
|
|
|
|
local function parent(entity)
|
|
|
|
return world:target(entity, ChildOf)
|
|
|
|
end
|
|
|
|
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
local sara = world:entity()
|
|
|
|
|
|
|
|
world:add(bob, ECS_PAIR(ChildOf, alice))
|
|
|
|
world:set(bob, Name, "bob")
|
|
|
|
world:add(sara, ECS_PAIR(ChildOf, alice))
|
|
|
|
world:set(sara, Name, "sara")
|
|
|
|
CHECK(parent(bob) == alice) -- O(1)
|
|
|
|
|
|
|
|
local count = 0
|
|
|
|
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
|
|
|
|
print(name)
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
2024-05-01 12:41:10 +00:00
|
|
|
end)
|
|
|
|
|
|
|
|
FINISH()
|