2024-05-01 12:41:10 +00:00
|
|
|
local jecs = require("../lib/init")
|
2024-05-27 01:39:20 +00:00
|
|
|
local testkit = require("../testkit")
|
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...)
|
2024-05-27 01:39:20 +00:00
|
|
|
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
|
2024-05-16 22:17:53 +00:00
|
|
|
end
|
2024-05-01 12:41:10 +00:00
|
|
|
local N = 10
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
type World = jecs.WorldShim
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
TEST("world", function()
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should be iterable")
|
|
|
|
local world = jecs.World.new() :: World
|
2024-05-27 01:39:20 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
for id, data in world do
|
|
|
|
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-07-02 23:24:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
do CASE("should remove its components")
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
|
|
|
|
world:set(e, A, true)
|
|
|
|
world:set(e, B, true)
|
|
|
|
|
|
|
|
CHECK(world:get(e, A))
|
|
|
|
CHECK(world:get(e, B))
|
|
|
|
|
|
|
|
world:clear(e)
|
|
|
|
CHECK(world:get(e, A) == nil)
|
|
|
|
CHECK(world:get(e, B) == nil)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE("iterator should not drain the query")
|
|
|
|
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)
|
|
|
|
|
|
|
|
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 == j)
|
|
|
|
end
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
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 q = world:query(A)
|
|
|
|
|
|
|
|
local e, data = q:next()
|
|
|
|
while e do
|
|
|
|
CHECK(
|
|
|
|
if e == eA then data == true
|
|
|
|
elseif e == eAB then data == true
|
|
|
|
else false
|
|
|
|
)
|
|
|
|
e, data = q:next()
|
|
|
|
end
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should query all matching entities")
|
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
|
|
|
|
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
|
|
|
|
|
2024-07-02 23:24:17 +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
|
|
|
|
for id in world:query(A) do
|
|
|
|
added += 1
|
|
|
|
table.remove(entities, CHECK(table.find(entities, id)))
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(added == N)
|
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +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-07-02 23:24:17 +00:00
|
|
|
do CASE("should allow setting components in arbitrary order")
|
2024-05-27 01:39:20 +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)
|
|
|
|
|
|
|
|
CHECK(world:get(id, Poison) == 5)
|
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should allow deleting components")
|
2024-05-27 01:39:20 +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)
|
|
|
|
local id1 = world:entity()
|
|
|
|
world:set(id1, Poison, 500)
|
|
|
|
world:set(id1, Health, 50)
|
|
|
|
|
|
|
|
world:delete(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
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should allow remove that doesn't exist on entity")
|
2024-05-27 01:39:20 +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-07-02 23:24:17 +00:00
|
|
|
do CASE("should increment generation")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local e = world:entity()
|
|
|
|
CHECK(ECS_ID(e) == 1 + jecs.Rest)
|
|
|
|
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-07-02 23:24:17 +00:00
|
|
|
do CASE("should get alive from index in the dense array")
|
2024-05-27 01:39:20 +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-06-05 22:38:27 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2)
|
|
|
|
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
|
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should allow querying for relations")
|
2024-05-27 01:39:20 +00:00
|
|
|
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
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should allow wildcards in queries")
|
2024-05-27 01:39:20 +00:00
|
|
|
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
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should match against multiple pairs")
|
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, 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-07-02 23:24:17 +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()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local Oranges = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
|
|
|
|
world:set(bob, Apples, "apples")
|
|
|
|
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
|
|
|
|
for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
world:delete(ECS_PAIR(Eats, Apples))
|
|
|
|
|
|
|
|
CHECK(count == 0)
|
|
|
|
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
|
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
do CASE("should error when setting invalid pair")
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
|
|
|
|
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
|
|
|
end
|
|
|
|
|
2024-07-02 23:24:17 +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
|
|
|
|
local pair = ECS_PAIR
|
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
|
|
|
|
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
2024-05-01 12:41:10 +00:00
|
|
|
end)
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
FINISH()
|