2024-07-06 21:30:14 +00:00
|
|
|
local jecs = require("@jecs")
|
|
|
|
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
|
2024-07-15 18:29:06 +00:00
|
|
|
local pair = jecs.pair
|
|
|
|
local getAlive = jecs.entity_index_get_alive
|
|
|
|
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-26 02:36:30 +00:00
|
|
|
do CASE "should not error when removing a component that entity doesn't have"
|
|
|
|
local world = jecs.World.new() :: World
|
|
|
|
local A = world:component()
|
|
|
|
local e = world:entity()
|
|
|
|
world:remove(e, A)
|
|
|
|
CHECK(true)
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
do CASE("should find every component id")
|
2024-07-02 23:24:17 +00:00
|
|
|
local world = jecs.World.new() :: World
|
2024-05-27 01:39:20 +00:00
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
2024-07-06 14:36:00 +00:00
|
|
|
world:entity()
|
|
|
|
world:entity()
|
|
|
|
world:entity()
|
|
|
|
|
|
|
|
local count = 0
|
|
|
|
for componentId in world:query(jecs.Component) do
|
2024-07-06 21:30:14 +00:00
|
|
|
if componentId ~= A and componentId ~= B then
|
2024-07-06 14:36:00 +00:00
|
|
|
error("found entity")
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
count += 1
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
|
|
|
CHECK(count == 2)
|
2024-07-02 23:24:17 +00:00
|
|
|
end
|
|
|
|
|
2024-07-06 21:30:14 +00:00
|
|
|
do CASE("should remove its components")
|
2024-07-02 23:24:17 +00:00
|
|
|
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
|
|
|
|
|
2024-07-26 00:55:36 +00:00
|
|
|
do CASE("should drain query while iterating")
|
2024-07-02 23:24:17 +00:00
|
|
|
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)
|
2024-07-06 21:30:14 +00:00
|
|
|
world:set(eAB, B, true)
|
2024-07-02 23:24:17 +00:00
|
|
|
|
|
|
|
local q = world:query(A)
|
|
|
|
|
|
|
|
local i = 0
|
|
|
|
local j = 0
|
2024-07-06 21:30:14 +00:00
|
|
|
for _ in q do
|
2024-07-02 23:24:17 +00:00
|
|
|
i+=1
|
|
|
|
end
|
2024-07-06 21:30:14 +00:00
|
|
|
for _ in q do
|
2024-07-02 23:24:17 +00:00
|
|
|
j+=1
|
|
|
|
end
|
2024-07-26 00:55:36 +00:00
|
|
|
CHECK(i == 2)
|
|
|
|
CHECK(j == 0)
|
2024-07-02 23:24:17 +00:00
|
|
|
end
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-07-06 21:30:14 +00:00
|
|
|
do CASE("should be able to get next results")
|
2024-07-02 23:24:17 +00:00
|
|
|
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)
|
2024-07-06 21:30:14 +00:00
|
|
|
world:set(eAB, B, true)
|
2024-07-02 23:24:17 +00:00
|
|
|
|
|
|
|
local q = world:query(A)
|
|
|
|
|
|
|
|
local e, data = q:next()
|
2024-07-06 21:30:14 +00:00
|
|
|
while e do
|
2024-07-02 23:24:17 +00:00
|
|
|
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)
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
local pair = pair(e2, e3)
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(IS_PAIR(pair) == true)
|
2024-06-05 22:38:27 +00:00
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
CHECK(ecs_pair_relation(world.entityIndex, pair) == e2)
|
|
|
|
CHECK(ecs_pair_object(world.entityIndex, pair) == e3)
|
2024-05-27 01:39:20 +00:00
|
|
|
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()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), true)
|
|
|
|
for e, bool in world:query(pair(Eats, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(bool)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-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()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
CHECK(e == bob)
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-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()
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local w = jecs.Wildcard
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(Eats, w)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
if e == bob then
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
else
|
|
|
|
CHECK(data == "alice eats oranges")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 2)
|
|
|
|
count = 0
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
for e, data in world:query(pair(w, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
CHECK(data == "bob eats apples")
|
|
|
|
end
|
|
|
|
CHECK(count == 1)
|
|
|
|
end
|
|
|
|
|
2024-07-14 00:37:16 +00:00
|
|
|
do CASE "should only relate alive entities"
|
|
|
|
SKIP()
|
2024-05-27 01:39:20 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Eats = world:entity()
|
|
|
|
local Apples = world:entity()
|
|
|
|
local Oranges = world:entity()
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
|
|
|
|
world:set(bob, Apples, "apples")
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
|
|
|
world:set(alice, pair(Eats, Oranges), "alice eats oranges")
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
world:delete(Apples)
|
|
|
|
local Wildcard = jecs.Wildcard
|
|
|
|
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for _, data in world:query(pair(Wildcard, Apples)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:delete(pair(Eats, Apples))
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
CHECK(count == 0)
|
2024-07-15 18:29:06 +00:00
|
|
|
CHECK(world:get(bob, pair(Eats, Apples)) == nil)
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
2024-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)
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
world:set(bob, pair(Eats, Apples), "bob eats apples")
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
2024-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
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local Name = world:component()
|
|
|
|
|
|
|
|
local bob = world:entity()
|
|
|
|
local alice = world:entity()
|
|
|
|
local sara = world:entity()
|
|
|
|
|
2024-07-03 00:46:54 +00:00
|
|
|
world:add(bob, pair(ChildOf, alice))
|
2024-05-27 01:39:20 +00:00
|
|
|
world:set(bob, Name, "bob")
|
2024-07-03 00:46:54 +00:00
|
|
|
world:add(sara, pair(ChildOf, alice))
|
2024-05-27 01:39:20 +00:00
|
|
|
world:set(sara, Name, "sara")
|
2024-07-03 00:46:54 +00:00
|
|
|
CHECK(world:parent(bob) == alice) -- O(1)
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
local count = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for _, name in world:query(Name, pair(ChildOf, alice)) do
|
2024-05-27 01:39:20 +00:00
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-07-06 21:30:14 +00:00
|
|
|
do CASE "should be able to add/remove matching entity during iteration"
|
2024-07-06 14:36:00 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
local Name = world:component()
|
2024-07-06 21:30:14 +00:00
|
|
|
for i = 1, 5 do
|
2024-07-06 14:36:00 +00:00
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Name, tostring(e))
|
|
|
|
end
|
|
|
|
local count = 0
|
2024-07-06 21:30:14 +00:00
|
|
|
for id, name in world:query(Name) do
|
2024-07-06 14:36:00 +00:00
|
|
|
count += 1
|
|
|
|
CHECK(id == tonumber(name))
|
|
|
|
|
|
|
|
world:remove(id, Name)
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Name, tostring(e))
|
|
|
|
end
|
|
|
|
CHECK(count == 5)
|
|
|
|
end
|
2024-07-06 21:30:14 +00:00
|
|
|
|
2024-07-06 14:36:00 +00:00
|
|
|
do CASE "should allow adding a matching entity during iteration"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
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
|
2024-07-06 21:30:14 +00:00
|
|
|
for id in world:query(A) do
|
2024-07-06 14:36:00 +00:00
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, A)
|
|
|
|
world:add(e, B)
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 3)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
do CASE "should not iterate same entity when adding component"
|
2024-07-14 00:37:16 +00:00
|
|
|
SKIP()
|
2024-07-06 14:36:00 +00:00
|
|
|
local world = jecs.World.new()
|
|
|
|
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
|
2024-07-06 21:30:14 +00:00
|
|
|
for id in world:query(A) do
|
2024-07-06 14:36:00 +00:00
|
|
|
world:add(id, B)
|
|
|
|
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 2)
|
|
|
|
end
|
2024-07-07 02:53:17 +00:00
|
|
|
|
|
|
|
do CASE "should replace component data"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
local C = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, A, 1)
|
|
|
|
world:set(e, B, true)
|
|
|
|
world:set(e, C, "hello ")
|
|
|
|
|
|
|
|
world:query(A, B, C):replace(function(a, b, c)
|
|
|
|
return a * 2, not b, c.."world"
|
|
|
|
end)
|
|
|
|
|
|
|
|
CHECK(world:get(e, A) == 2)
|
|
|
|
CHECK(world:get(e, B) == false)
|
|
|
|
CHECK(world:get(e, C) == "hello world")
|
|
|
|
end
|
2024-07-08 13:51:34 +00:00
|
|
|
|
|
|
|
do CASE "should not iterate when nothing matches query"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
|
|
|
|
local count = 0
|
|
|
|
for id in world:query(B) do
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
CHECK(count == 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should return nothing for empty iteration"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
|
|
|
|
local query = world:query(B)
|
|
|
|
CHECK(query.next() == nil)
|
|
|
|
CHECK(query.replace() == nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should properly handle query:without for empty iteration"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local A = world:component()
|
|
|
|
local B = world:component()
|
|
|
|
|
|
|
|
local e1 = world:entity()
|
|
|
|
world:add(e1, A)
|
|
|
|
|
|
|
|
local query = world:query(B)
|
|
|
|
CHECK(query == query:without())
|
|
|
|
end
|
2024-07-13 02:32:31 +00:00
|
|
|
|
|
|
|
do CASE "should not find any entities"
|
2024-07-14 00:45:49 +00:00
|
|
|
local world = jecs.World.new()
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
local Hello = world:component()
|
|
|
|
local Bob = world:component()
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
local helloBob = world:entity()
|
2024-07-15 18:29:06 +00:00
|
|
|
world:add(helloBob, pair(Hello, Bob))
|
2024-07-14 00:45:49 +00:00
|
|
|
world:add(helloBob, Bob)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
local withoutCount = 0
|
2024-07-15 18:29:06 +00:00
|
|
|
for _ in world:query(pair(Hello, Bob)):without(Bob) do
|
2024-07-14 00:45:49 +00:00
|
|
|
withoutCount += 1
|
|
|
|
end
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
CHECK(withoutCount == 0)
|
|
|
|
end
|
2024-07-23 02:44:56 +00:00
|
|
|
|
|
|
|
do CASE "should find Tag on entity"
|
|
|
|
local world = jecs.World.new()
|
|
|
|
|
|
|
|
local Tag = world:component()
|
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:add(e, Tag)
|
|
|
|
|
|
|
|
CHECK(world:has(e, Tag))
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should return false when missing one tag"
|
|
|
|
local world = jecs.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, A)
|
|
|
|
world:add(e, C)
|
|
|
|
world:add(e, D)
|
|
|
|
|
|
|
|
CHECK(world:has(e, A, B, C, D) == false)
|
|
|
|
end
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
end)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
TEST("changetracker", function()
|
|
|
|
local world = jecs.World.new()
|
|
|
|
local Previous = world:component()
|
|
|
|
|
|
|
|
local function shallowEq(a, b)
|
|
|
|
for k, v in a do
|
|
|
|
if b[k] ~= v then
|
|
|
|
return false
|
2024-07-13 02:32:31 +00:00
|
|
|
end
|
|
|
|
end
|
2024-07-14 00:45:49 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function ChangeTracker(world, component)
|
|
|
|
local addedComponents = {}
|
|
|
|
local removedComponents = {}
|
|
|
|
local previous = jecs.pair(Previous, component)
|
|
|
|
local isTrivial = nil
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
local function track(fn)
|
2024-07-13 02:32:31 +00:00
|
|
|
local added = false
|
|
|
|
local removed = false
|
|
|
|
|
|
|
|
local changes = {}
|
2024-07-14 00:45:49 +00:00
|
|
|
function changes.added()
|
2024-07-13 02:32:31 +00:00
|
|
|
added = true
|
|
|
|
local q = world:query(component):without(previous)
|
|
|
|
return function()
|
|
|
|
local id, data = q:next()
|
|
|
|
if not id then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
if isTrivial == nil then
|
|
|
|
isTrivial = typeof(data) ~= "table"
|
|
|
|
end
|
|
|
|
|
|
|
|
if not isTrivial then
|
|
|
|
data = table.clone(data)
|
|
|
|
end
|
|
|
|
|
|
|
|
addedComponents[id] = data
|
|
|
|
return id, data
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
function changes.changed()
|
2024-07-13 02:32:31 +00:00
|
|
|
local q = world:query(component, previous)
|
|
|
|
|
|
|
|
return function()
|
|
|
|
local id, new, old = q:next()
|
|
|
|
while true do
|
|
|
|
if not id then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
if not isTrivial then
|
|
|
|
if not shallowEq(new, old) then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
elseif new ~= old then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
id, new, old = q:next()
|
|
|
|
end
|
|
|
|
|
|
|
|
addedComponents[id] = new
|
|
|
|
|
|
|
|
return id, old, new
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
function changes.removed()
|
2024-07-13 02:32:31 +00:00
|
|
|
removed = true
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
local q = world:query(previous):without(component)
|
2024-07-13 02:32:31 +00:00
|
|
|
return function()
|
|
|
|
local id = q:next()
|
|
|
|
if id then
|
2024-07-14 00:45:49 +00:00
|
|
|
table.insert(removedComponents, id)
|
2024-07-13 02:32:31 +00:00
|
|
|
end
|
|
|
|
return id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
fn(changes)
|
|
|
|
if not added then
|
2024-07-14 00:45:49 +00:00
|
|
|
for _ in changes.added() do
|
2024-07-13 02:32:31 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not removed then
|
2024-07-14 00:45:49 +00:00
|
|
|
for _ in changes.removed() do
|
2024-07-13 02:32:31 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for e, data in addedComponents do
|
|
|
|
world:set(e, previous, if isTrivial then data else table.clone(data))
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, e in removedComponents do
|
|
|
|
world:remove(e, previous)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
return {
|
|
|
|
track = track
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
do CASE "should allow change tracking"
|
2024-07-13 02:32:31 +00:00
|
|
|
local Test = world:component()
|
2024-07-14 00:45:49 +00:00
|
|
|
local TestTracker = ChangeTracker(world, Test)
|
2024-07-13 02:32:31 +00:00
|
|
|
|
|
|
|
local e = world:entity()
|
|
|
|
world:set(e, Test, { foo = 11 })
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
TestTracker.track(function(changes)
|
2024-07-13 02:32:31 +00:00
|
|
|
local added = 0
|
|
|
|
local changed = 0
|
|
|
|
local removed = 0
|
|
|
|
for e, data in changes.added() do
|
|
|
|
added+=1
|
|
|
|
end
|
|
|
|
for e, old, new in changes.changed() do
|
|
|
|
changed+=1
|
|
|
|
end
|
|
|
|
for e in changes.removed() do
|
|
|
|
removed+=1
|
|
|
|
end
|
|
|
|
CHECK(added == 1)
|
|
|
|
CHECK(changed == 0)
|
|
|
|
CHECK(removed == 0)
|
|
|
|
end)
|
|
|
|
|
|
|
|
for e, test in world:query(Test) do
|
2024-07-14 00:45:49 +00:00
|
|
|
test.foo = test.foo + 1
|
|
|
|
end
|
2024-07-13 02:32:31 +00:00
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
TestTracker.track(function(changes)
|
2024-07-13 02:32:31 +00:00
|
|
|
local added = 0
|
|
|
|
local changed = 0
|
|
|
|
local removed = 0
|
2024-07-14 00:45:49 +00:00
|
|
|
|
|
|
|
for e, data in changes.added() do
|
2024-07-13 02:32:31 +00:00
|
|
|
added+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
|
|
|
for e, old, new in changes.changed() do
|
2024-07-13 02:32:31 +00:00
|
|
|
changed+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
|
|
|
for e in changes.removed() do
|
2024-07-13 02:32:31 +00:00
|
|
|
removed+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-13 02:32:31 +00:00
|
|
|
CHECK(added == 0)
|
|
|
|
CHECK(changed == 1)
|
|
|
|
CHECK(removed == 0)
|
|
|
|
end)
|
|
|
|
|
|
|
|
world:remove(e, Test)
|
|
|
|
|
2024-07-14 00:45:49 +00:00
|
|
|
TestTracker.track(function(changes)
|
2024-07-13 02:32:31 +00:00
|
|
|
local added = 0
|
|
|
|
local changed = 0
|
|
|
|
local removed = 0
|
2024-07-14 00:45:49 +00:00
|
|
|
for e, data in changes.added() do
|
2024-07-13 02:32:31 +00:00
|
|
|
added+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
|
|
|
for e, old, new in changes.changed() do
|
2024-07-13 02:32:31 +00:00
|
|
|
changed+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
|
|
|
for e in changes.removed() do
|
2024-07-13 02:32:31 +00:00
|
|
|
removed+=1
|
2024-07-14 00:45:49 +00:00
|
|
|
end
|
2024-07-13 02:32:31 +00:00
|
|
|
CHECK(added == 0)
|
|
|
|
CHECK(changed == 0)
|
|
|
|
CHECK(removed == 1)
|
|
|
|
end)
|
|
|
|
end
|
2024-05-01 12:41:10 +00:00
|
|
|
end)
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
FINISH()
|