diff --git a/addons/ob.luau b/addons/ob.luau index 6b20c58..6661c47 100755 --- a/addons/ob.luau +++ b/addons/ob.luau @@ -12,6 +12,10 @@ export type Iter = (Observer) -> () -> (jecs.Entity, T...) export type Observer = { disconnect: (Observer) -> (), +} + +export type Monitor = { + disconnect: (Observer) -> (), added: ((jecs.Entity) -> ()) -> (), removed: ((jecs.Entity) -> ()) -> () } @@ -77,11 +81,11 @@ local function observers_new( if without then for _, term in without do if jecs.IS_PAIR(term) then - local rel = jecs.ECS_PAIR_SECOND(term) + local rel = jecs.ECS_PAIR_FIRST(term) local tgt = jecs.ECS_PAIR_SECOND(term) local wc = tgt == jecs.w local onremoved = world:removed(rel, function(entity, id) - if not wc and term ~= tgt then + if not wc and id ~= term then return end local r = jecs.record(world, entity) @@ -137,7 +141,7 @@ local function observers_new( return (observer :: any) :: Observer end -local function monitors_new(query: Query): Observer +local function monitors_new(query: Query): Monitor query:cached() local world = (query :: Query & { world: World }).world @@ -313,7 +317,7 @@ local function monitors_new(query: Query): Observer removed = monitor_removed } - return (observer :: any) :: Observer + return (observer :: any) :: Monitor end return { diff --git a/test/addons/ob.luau b/test/addons/ob.luau index 00d2e1d..fbe0ccd 100755 --- a/test/addons/ob.luau +++ b/test/addons/ob.luau @@ -5,7 +5,203 @@ local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK local FOCUS = test.FOCUS local ob = require("@addons/ob") -TEST("addons/ob", function() +TEST("addons/ob::observer", function() + local world = jecs.world() + do CASE "should match against archetypes that were already created" + local A = world:component() + + local e1 = world:entity() + world:add(e1, A) + + local c = 1 + ob.observer(world:query(A), function() + c+=1 + end) + + world:remove(e1, A) + world:add(e1, A) + CHECK(c==2) + end + + do CASE "Should enter observer at query:without(pair(R, t1)) when adding pair(R, t2)" + 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, jecs.pair(B, D)) + + local c = 1 + + ob.observer(world:query(A):without(jecs.pair(B, C)), function() + c += 1 + end) + + local child = world:entity() + world:add(child, A) + CHECK(c==2) + world:add(child, jecs.pair(B, D)) + CHECK(c==2) + world:add(child, jecs.pair(B, C)) + CHECK(c==2) + end + + do CASE "Should enter observer at query:without(pair(R, t1)) when removing pair(R, t1)" + 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, jecs.pair(B, D)) + + local c = 1 + ob.observer(world:query(A):without(jecs.pair(B, D)), function() + c += 1 + end) + + local child = world:entity() + world:add(child, jecs.pair(B, C)) + world:add(child, jecs.pair(B, D)) + CHECK(c==1) + world:add(child, A) + CHECK(c==1) + world:remove(child, jecs.pair(B, C)) + CHECK(c==1) + world:add(child, jecs.pair(B, C)) + CHECK(c==1) + world:remove(child, jecs.pair(B, D)) + CHECK(c==2) + world:add(child, jecs.pair(B, D)) + CHECK(c==2) + world:remove(child, jecs.pair(B, C)) + CHECK(c==2) + world:remove(child, jecs.pair(B, D)) + CHECK(c==3) + end + + do CASE "Should enter observer at query:without(pair(R, *)) when adding pair(R, t1)" + local A = world:component() + local B = world:component() + local C = world:component() + local D = world:component() + + local c = 1 + ob.observer(world:query(A):without(jecs.pair(B, jecs.w)), function() c+= 1 end) + + local child = world:entity() + world:add(child, A) + CHECK(c==2) + world:add(child, jecs.pair(B, D)) + CHECK(c==2) + world:add(child, jecs.pair(B, C)) + CHECK(c==2) + end + + do CASE "Should enter observer at query:without(t1) when removing t1" + local A = world:component() + local B = world:component() + + local c = 1 + ob.observer(world:query(A):without(B), function() c+= 1 end) + + local child = world:entity() + world:add(child, B) + CHECK(c==1) + world:add(child, A) + CHECK(c==1) + world:remove(child, B) + CHECK(c==2) + world:remove(child, A) + CHECK(c==2) + end + + do CASE "observers should accept pairs" + local A = world:component() + local B = world:component() + + local c = 1 + ob.observer(world:query(jecs.pair(A, B)), function() c+= 1 end) + + local child = world:entity() + world:add(child, jecs.pair(A, B)) + CHECK(c == 2) + + world:remove(child, jecs.pair(A, B)) + CHECK(c == 2) + end + + do CASE "Ensure ordering between signals and observers" + local A = world:component() + local B = world:component() + + local count = 1 + local function counter() + count += 1 + end + + ob.observer(world:query(A, B), counter) + + world:added(A, counter) + world:added(A, counter) + + for _ in world:query(A) do + + end + + local e = world:entity() + world:add(e, A) + CHECK(count == 3) + + world:add(e, B) + CHECK(count == 4) + end + + do CASE "Rematch entities in observers" + local A = world:component() + + local count = 1 + local function counter() + count += 1 + end + + ob.observer(world:query(A), counter) + + local e = world:entity() + world:set(e, A, false) + CHECK(count == 2) + world:remove(e, A) + CHECK(count == 2) + world:set(e, A, false) + CHECK(count == 3) + world:set(e, A, false) + CHECK(count == 4) + end + + do CASE "Call off pairs" + local A = world:component() + + local callcount = 1 + world:added(A, function(entity) + callcount += 1 + end) + world:added(A, function(entity) + callcount += 1 + end) + + local e = world:entity() + local e2 = world:entity() + + world:add(e2, jecs.pair(A, e)) + world:add(e, jecs.pair(A, e2)) + CHECK(callcount == 1 + 2 * 2) + end +end) + +TEST("addons/ob::monitor", function() local world = jecs.world() do CASE "should match against archetypes that were already created" local A = world:component() @@ -61,8 +257,6 @@ TEST("addons/ob", function() local C = world:component() local D = world:component() - print("B", B) - local e = world:entity() world:add(e, A) world:add(e, jecs.pair(B, D)) @@ -172,53 +366,6 @@ TEST("addons/ob", function() CHECK(c == 3) end - do CASE "Ensure ordering between signals and observers" - local A = world:component() - local B = world:component() - - local count = 1 - local function counter() - count += 1 - end - - ob.observer(world:query(A, B), counter) - - world:added(A, counter) - world:added(A, counter) - - for _ in world:query(A) do - - end - - local e = world:entity() - world:add(e, A) - CHECK(count == 3) - - world:add(e, B) - CHECK(count == 4) - end - - do CASE "Rematch entities in observers" - local A = world:component() - - local count = 1 - local function counter() - count += 1 - end - - ob.observer(world:query(A), counter) - - local e = world:entity() - world:set(e, A, false) - CHECK(count == 2) - world:remove(e, A) - CHECK(count == 2) - world:set(e, A, false) - CHECK(count == 3) - world:set(e, A, false) - CHECK(count == 4) - end - do CASE "Don't report changed components in monitor" local A = world:component() local count = 1 @@ -240,25 +387,6 @@ TEST("addons/ob", function() world:set(e, A, false) CHECK(count == 4) end - - do CASE "Call off pairs" - local A = world:component() - - local callcount = 1 - world:added(A, function(entity) - callcount += 1 - end) - world:added(A, function(entity) - callcount += 1 - end) - - local e = world:entity() - local e2 = world:entity() - - world:add(e2, jecs.pair(A, e)) - world:add(e, jecs.pair(A, e2)) - CHECK(callcount == 1 + 2 * 2) - end end) return FINISH()