mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
feat: update alien-signals
This commit is contained in:
parent
32788b19e1
commit
7a88c784fa
14 changed files with 397 additions and 343 deletions
|
|
@ -1,13 +1,13 @@
|
||||||
local core = require("../../core")
|
local core = require("../../core")
|
||||||
local suite = require("../../../../../test/suite")
|
local suite = require("../../../../../test/suite")
|
||||||
|
|
||||||
local atom = core.atom
|
|
||||||
local computed = core.computed
|
local computed = core.computed
|
||||||
local effect = core.effect
|
local effect = core.effect
|
||||||
|
local signal = core.signal
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should correctly propagate changes through computed core", function()
|
test("should correctly propagate changes through computed core", function()
|
||||||
local src = atom(0)
|
local src, setSrc = signal(0)
|
||||||
local c1 = computed(function()
|
local c1 = computed(function()
|
||||||
return src() % 2
|
return src() % 2
|
||||||
end)
|
end)
|
||||||
|
|
@ -19,15 +19,15 @@ test("should correctly propagate changes through computed core", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
c3()
|
c3()
|
||||||
src(1) -- c1 -> dirty, c2 -> toCheckDirty, c3 -> toCheckDirty
|
setSrc(1) -- c1 -> dirty, c2 -> toCheckDirty, c3 -> toCheckDirty
|
||||||
c2() -- c1 -> none, c2 -> none
|
c2() -- c1 -> none, c2 -> none
|
||||||
src(3) -- c1 -> dirty, c2 -> toCheckDirty
|
setSrc(3) -- c1 -> dirty, c2 -> toCheckDirty
|
||||||
|
|
||||||
assert(c3() == 1, "bad final value")
|
assert(c3() == 1, "bad final value")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should propagate updated source value through chained computations", function()
|
test("should propagate updated source value through chained computations", function()
|
||||||
local src = atom(0)
|
local src, setSrc = signal(0)
|
||||||
local a = computed(function()
|
local a = computed(function()
|
||||||
return src()
|
return src()
|
||||||
end)
|
end)
|
||||||
|
|
@ -42,12 +42,12 @@ test("should propagate updated source value through chained computations", funct
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(d() == 0, "bad initial value")
|
assert(d() == 0, "bad initial value")
|
||||||
src(2)
|
setSrc(2)
|
||||||
assert(d() == 2, "bad updated value")
|
assert(d() == 2, "bad updated value")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should handle flags are indirectly updated during checkDirty", function()
|
test("should handle flags are indirectly updated during checkDirty", function()
|
||||||
local a = atom(false)
|
local a, setA = signal(false)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -61,29 +61,29 @@ test("should handle flags are indirectly updated during checkDirty", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(d() == false, "bad initial value")
|
assert(d() == false, "bad initial value")
|
||||||
a(true)
|
setA(true)
|
||||||
assert(d() == true, "bad updated value")
|
assert(d() == true, "bad updated value")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not update if the atom value is reverted", function()
|
test("should not update if the signal value is reverted", function()
|
||||||
local times = 0
|
local times = 0
|
||||||
|
|
||||||
local src = atom(0)
|
local src, setSrc = signal(0)
|
||||||
local c1 = computed(function()
|
local c1 = computed(function()
|
||||||
times += 1
|
times += 1
|
||||||
return src()
|
return src()
|
||||||
end)
|
end)
|
||||||
c1()
|
c1()
|
||||||
assert(times == 1, "computed did not run")
|
assert(times == 1, "computed did not run")
|
||||||
src(1)
|
setSrc(1)
|
||||||
src(0)
|
setSrc(0)
|
||||||
c1()
|
c1()
|
||||||
assert(times == 1, "computed ran when it should not")
|
assert(times == 1, "computed ran when it should not")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should receive previous value", function()
|
test("should receive previous value", function()
|
||||||
local calls = {}
|
local calls = {}
|
||||||
local src = atom(0)
|
local src, setSrc = signal(0)
|
||||||
local c1 = computed(function(current): number
|
local c1 = computed(function(current): number
|
||||||
local incoming = src()
|
local incoming = src()
|
||||||
return if current == incoming or incoming % 2 == 1 then current or incoming else incoming
|
return if current == incoming or incoming % 2 == 1 then current or incoming else incoming
|
||||||
|
|
@ -93,12 +93,12 @@ test("should receive previous value", function()
|
||||||
table.insert(calls, c1())
|
table.insert(calls, c1())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
src(2)
|
setSrc(2)
|
||||||
src(3)
|
setSrc(3)
|
||||||
assert(c1() == 2, "should skip odd value")
|
assert(c1() == 2, "should skip odd value")
|
||||||
|
|
||||||
src(4)
|
setSrc(4)
|
||||||
src(5)
|
setSrc(5)
|
||||||
assert(c1() == 4, "should skip odd value")
|
assert(c1() == 4, "should skip odd value")
|
||||||
assert(table.concat(calls) == "024", `should only call on even updates, got {table.concat(calls)}`)
|
assert(table.concat(calls) == "024", `should only call on even updates, got {table.concat(calls)}`)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ local computed = core.computed
|
||||||
local effect = core.effect
|
local effect = core.effect
|
||||||
local effectScope = core.effectScope
|
local effectScope = core.effectScope
|
||||||
local endBatch = core.endBatch
|
local endBatch = core.endBatch
|
||||||
local setActiveSub = core.setActiveSub
|
|
||||||
local onCleanup = core.onCleanup
|
local onCleanup = core.onCleanup
|
||||||
local atom = core.atom
|
local setActiveSub = core.setActiveSub
|
||||||
|
local signal = core.signal
|
||||||
local startBatch = core.startBatch
|
local startBatch = core.startBatch
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should clear subscriptions when untracked by all subscribers", function()
|
test("should clear subscriptions when untracked by all subscribers", function()
|
||||||
local bRunTimes = 0
|
local bRunTimes = 0
|
||||||
|
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
bRunTimes += 1
|
bRunTimes += 1
|
||||||
return a() * 2
|
return a() * 2
|
||||||
|
|
@ -24,15 +24,15 @@ test("should clear subscriptions when untracked by all subscribers", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(bRunTimes == 1, "should run initially")
|
assert(bRunTimes == 1, "should run initially")
|
||||||
a(2)
|
setA(2)
|
||||||
assert(bRunTimes == 2, "should run after change")
|
assert(bRunTimes == 2, "should run after change")
|
||||||
stopEffect()
|
stopEffect()
|
||||||
a(3)
|
setA(3)
|
||||||
assert(bRunTimes == 2, "should not run after stop")
|
assert(bRunTimes == 2, "should not run after stop")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not run untracked inner effect", function()
|
test("should not run untracked inner effect", function()
|
||||||
local a = atom(3)
|
local a, setA = signal(3)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a() > 0
|
return a() > 0
|
||||||
end)
|
end)
|
||||||
|
|
@ -47,14 +47,14 @@ test("should not run untracked inner effect", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
a(1)
|
setA(1)
|
||||||
a(0)
|
setA(0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run outer effect first", function()
|
test("should run outer effect first", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(1)
|
local b, setB = signal(1)
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
if a() > 0 then
|
if a() > 0 then
|
||||||
|
|
@ -68,13 +68,13 @@ test("should run outer effect first", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
startBatch()
|
startBatch()
|
||||||
b(0)
|
setB(0)
|
||||||
a(0)
|
setA(0)
|
||||||
endBatch()
|
endBatch()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not trigger inner effect when resolve maybe dirty", function()
|
test("should not trigger inner effect when resolve maybe dirty", function()
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a() % 2
|
return a() % 2
|
||||||
end)
|
end)
|
||||||
|
|
@ -91,68 +91,66 @@ test("should not trigger inner effect when resolve maybe dirty", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should trigger inner effects in sequence", function()
|
test("should notify inner effects in the same order as non-inner effects", function()
|
||||||
local a = atom(0)
|
local getA, setA = signal(0)
|
||||||
local b = atom(0)
|
local getB, setB = signal(0)
|
||||||
local c = computed(function()
|
local c = computed(function()
|
||||||
return a() - b()
|
return getA() - getB()
|
||||||
|
end)
|
||||||
|
local order1 = {}
|
||||||
|
local order2 = {}
|
||||||
|
local order3 = {}
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
table.insert(order1, "effect1")
|
||||||
|
getA()
|
||||||
|
end)
|
||||||
|
effect(function()
|
||||||
|
table.insert(order1, "effect2")
|
||||||
|
getA()
|
||||||
|
getB()
|
||||||
end)
|
end)
|
||||||
local order: { string } = {}
|
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
c()
|
c()
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
table.insert(order, "first inner")
|
table.insert(order2, "effect1")
|
||||||
a()
|
getA()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
table.insert(order, "last inner")
|
table.insert(order2, "effect2")
|
||||||
a()
|
getA()
|
||||||
b()
|
getB()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.clear(order)
|
|
||||||
|
|
||||||
startBatch()
|
|
||||||
b(1)
|
|
||||||
a(1)
|
|
||||||
endBatch()
|
|
||||||
|
|
||||||
assert(table.concat(order, ", ") == "first inner, last inner", `wrong order: {table.concat(order, ", ")}`)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("should trigger inner effects in sequence in effect scope", function()
|
|
||||||
local a = atom(0)
|
|
||||||
local b = atom(0)
|
|
||||||
local order: { string } = {}
|
|
||||||
|
|
||||||
effectScope(function()
|
effectScope(function()
|
||||||
effect(function()
|
effect(function()
|
||||||
table.insert(order, "first inner")
|
table.insert(order3, "effect1")
|
||||||
a()
|
getA()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
table.insert(order, "last inner")
|
table.insert(order3, "effect2")
|
||||||
a()
|
getA()
|
||||||
b()
|
getB()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order1)
|
||||||
|
table.clear(order2)
|
||||||
|
table.clear(order3)
|
||||||
|
|
||||||
startBatch()
|
startBatch()
|
||||||
b(1)
|
setB(1)
|
||||||
a(1)
|
setA(1)
|
||||||
endBatch()
|
endBatch()
|
||||||
|
|
||||||
assert(table.concat(order, ", ") == "first inner, last inner", `wrong order: {table.concat(order, ", ")}`)
|
assert(table.concat(order1, ", ") == "effect2, effect1", "wrong order 1")
|
||||||
|
assert(table.concat(order2, ", ") == "effect2, effect1", "wrong order 2")
|
||||||
|
assert(table.concat(order3, ", ") == "effect2, effect1", "wrong order 3")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should custom effect support batch", function()
|
test("should custom effect support batch", function()
|
||||||
|
|
@ -168,13 +166,13 @@ test("should custom effect support batch", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
local logs: { string } = {}
|
local logs: { string } = {}
|
||||||
local a = atom(0)
|
local a = signal(0)
|
||||||
local b = atom(0)
|
local b, setB = signal(0)
|
||||||
|
|
||||||
local aa = computed(function()
|
local aa = computed(function()
|
||||||
table.insert(logs, "aa-0")
|
table.insert(logs, "aa-0")
|
||||||
if a() == 0 then
|
if a() == 0 then
|
||||||
b(1)
|
setB(1)
|
||||||
end
|
end
|
||||||
table.insert(logs, "aa-1")
|
table.insert(logs, "aa-1")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -196,8 +194,8 @@ test("should custom effect support batch", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should duplicate subscribers do not affect the notify order", function()
|
test("should duplicate subscribers do not affect the notify order", function()
|
||||||
local src1 = atom(0)
|
local src1, setSrc1 = signal(0)
|
||||||
local src2 = atom(0)
|
local src2, setSrc2 = signal(0)
|
||||||
local order: { string } = {}
|
local order: { string } = {}
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -215,17 +213,17 @@ test("should duplicate subscribers do not affect the notify order", function()
|
||||||
table.insert(order, "b")
|
table.insert(order, "b")
|
||||||
src1()
|
src1()
|
||||||
end)
|
end)
|
||||||
src2(1) -- src1.subs: a -> b -> a
|
setSrc2(1) -- src1.subs: a -> b -> a
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
src1(src1() + 1)
|
setSrc1(src1() + 1)
|
||||||
|
|
||||||
assert(table.concat(order) == "ab", `wrong order: {table.concat(order)}`)
|
assert(table.concat(order) == "ab", `wrong order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should handle side effect with inner effects", function()
|
test("should handle side effect with inner effects", function()
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = atom(0)
|
local b, setB = signal(0)
|
||||||
local order: { string } = {}
|
local order: { string } = {}
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -240,14 +238,14 @@ test("should handle side effect with inner effects", function()
|
||||||
assert(table.concat(order) == "ab", `wrong order: {table.concat(order)}`)
|
assert(table.concat(order) == "ab", `wrong order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
b(1)
|
setB(1)
|
||||||
a(1)
|
setA(1)
|
||||||
assert(table.concat(order) == "ba", `wrong order: {table.concat(order)}`)
|
assert(table.concat(order) == "ba", `wrong order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should handle flags are indirectly updated during checkDirty", function()
|
test("should handle flags are indirectly updated during checkDirty", function()
|
||||||
local a = atom(false)
|
local a, setA = signal(false)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -267,14 +265,14 @@ test("should handle flags are indirectly updated during checkDirty", function()
|
||||||
triggers += 1
|
triggers += 1
|
||||||
end)
|
end)
|
||||||
assert(triggers == 1, "did not run initially")
|
assert(triggers == 1, "did not run initially")
|
||||||
a(true)
|
setA(true)
|
||||||
assert(triggers == 2, "did not update indirectly")
|
assert(triggers == 2, "did not update indirectly")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run cleanups in sequence", function()
|
test("should run cleanups in sequence", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
|
|
||||||
local a = atom(false)
|
local a, setA = signal(false)
|
||||||
local stopEffect = effect(function()
|
local stopEffect = effect(function()
|
||||||
a()
|
a()
|
||||||
onCleanup(function()
|
onCleanup(function()
|
||||||
|
|
@ -287,7 +285,7 @@ test("should run cleanups in sequence", function()
|
||||||
|
|
||||||
assert(table.concat(order) == "", "should not cleanup immediately")
|
assert(table.concat(order) == "", "should not cleanup immediately")
|
||||||
|
|
||||||
a(true)
|
setA(true)
|
||||||
assert(table.concat(order) == "ab", `bad update: {table.concat(order)}`)
|
assert(table.concat(order) == "ab", `bad update: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
|
|
@ -295,14 +293,14 @@ test("should run cleanups in sequence", function()
|
||||||
assert(table.concat(order) == "ab", `bad dispose: {table.concat(order)}`)
|
assert(table.concat(order) == "ab", `bad dispose: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
a(false)
|
setA(false)
|
||||||
assert(table.concat(order) == "", "should not cleanup after update")
|
assert(table.concat(order) == "", "should not cleanup after update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run inner effect cleanup", function()
|
test("should run inner effect cleanup", function()
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
|
|
||||||
local src = atom(false)
|
local src, setSrc = signal(false)
|
||||||
local stopEffect = effect(function()
|
local stopEffect = effect(function()
|
||||||
src()
|
src()
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -318,18 +316,18 @@ test("should run inner effect cleanup", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(cleanups == 0, "should not cleanup immediately")
|
assert(cleanups == 0, "should not cleanup immediately")
|
||||||
src(true)
|
setSrc(true)
|
||||||
assert(cleanups == 2, "should cleanup after outer update")
|
assert(cleanups == 2, "should cleanup after outer update")
|
||||||
stopEffect()
|
stopEffect()
|
||||||
assert(cleanups == 4, "should cleanup after outer dispose")
|
assert(cleanups == 4, "should cleanup after outer dispose")
|
||||||
src(false)
|
setSrc(false)
|
||||||
assert(cleanups == 4, "should not cleanup after update")
|
assert(cleanups == 4, "should not cleanup after update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run cleanups outside any evaluation context", function()
|
test("should run cleanups outside any evaluation context", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = atom(0)
|
local b, setB = signal(0)
|
||||||
local c = computed(function()
|
local c = computed(function()
|
||||||
if a() == 0 then
|
if a() == 0 then
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -348,18 +346,18 @@ test("should run cleanups outside any evaluation context", function()
|
||||||
assert(calls == 1, "should run once")
|
assert(calls == 1, "should run once")
|
||||||
|
|
||||||
calls = 0
|
calls = 0
|
||||||
a(1)
|
setA(1)
|
||||||
assert(calls == 1, "should run once after a update")
|
assert(calls == 1, "should run once after a update")
|
||||||
|
|
||||||
calls = 0
|
calls = 0
|
||||||
b(1)
|
setB(1)
|
||||||
assert(calls == 0, "should not run after b update")
|
assert(calls == 0, "should not run after b update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should allow self cleanup", function()
|
test("should allow self cleanup", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
|
|
||||||
effect(function(stopEffect)
|
effect(function(stopEffect)
|
||||||
onCleanup(function()
|
onCleanup(function()
|
||||||
|
|
@ -372,7 +370,7 @@ test("should allow self cleanup", function()
|
||||||
|
|
||||||
assert(calls == 1, "should call effect once")
|
assert(calls == 1, "should call effect once")
|
||||||
assert(cleanups == 1, "should call cleanup once")
|
assert(cleanups == 1, "should call cleanup once")
|
||||||
a(1)
|
setA(1)
|
||||||
assert(calls == 1, "should not call effect after a update")
|
assert(calls == 1, "should not call effect after a update")
|
||||||
assert(cleanups == 1, "should not call cleanup after a update")
|
assert(cleanups == 1, "should not call cleanup after a update")
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ local suite = require("../../../../../test/suite")
|
||||||
|
|
||||||
local effect = core.effect
|
local effect = core.effect
|
||||||
local effectScope = core.effectScope
|
local effectScope = core.effectScope
|
||||||
local atom = core.atom
|
local signal = core.signal
|
||||||
local onCleanup = core.onCleanup
|
local onCleanup = core.onCleanup
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should not trigger after stop", function()
|
test("should not trigger after stop", function()
|
||||||
local count = atom(1)
|
local count, setCount = signal(1)
|
||||||
|
|
||||||
local triggers = 0
|
local triggers = 0
|
||||||
|
|
||||||
|
|
@ -19,14 +19,14 @@ test("should not trigger after stop", function()
|
||||||
end)
|
end)
|
||||||
assert(triggers == 1, "did not run initially")
|
assert(triggers == 1, "did not run initially")
|
||||||
|
|
||||||
count(2)
|
setCount(2)
|
||||||
assert(triggers == 2, "did not run after update")
|
assert(triggers == 2, "did not run after update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
count(3)
|
setCount(3)
|
||||||
assert(triggers == 3, "did not run after update outside scope")
|
assert(triggers == 3, "did not run after update outside scope")
|
||||||
stopScope()
|
stopScope()
|
||||||
count(4)
|
setCount(4)
|
||||||
assert(triggers == 3, "ran after stop")
|
assert(triggers == 3, "ran after stop")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ end)
|
||||||
test("should run indirect effect cleanup", function()
|
test("should run indirect effect cleanup", function()
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
|
|
||||||
local a = atom(false)
|
local a, setA = signal(false)
|
||||||
local stopScope = effectScope(function()
|
local stopScope = effectScope(function()
|
||||||
effect(function()
|
effect(function()
|
||||||
a()
|
a()
|
||||||
|
|
@ -63,11 +63,11 @@ test("should run indirect effect cleanup", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(cleanups == 0, "should not cleanup immediately")
|
assert(cleanups == 0, "should not cleanup immediately")
|
||||||
a(true)
|
setA(true)
|
||||||
assert(cleanups == 1, "should cleanup once")
|
assert(cleanups == 1, "should cleanup once")
|
||||||
stopScope()
|
stopScope()
|
||||||
assert(cleanups == 2, "should cleanup after outer dispose")
|
assert(cleanups == 2, "should cleanup after outer dispose")
|
||||||
a(false)
|
setA(false)
|
||||||
assert(cleanups == 2, "should not cleanup after update")
|
assert(cleanups == 2, "should not cleanup after update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ local suite = require("../../../../../test/suite")
|
||||||
|
|
||||||
local computed = core.computed
|
local computed = core.computed
|
||||||
local effect = core.effect
|
local effect = core.effect
|
||||||
local atom = core.atom
|
local signal = core.signal
|
||||||
local describe = suite.describe
|
local describe = suite.describe
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Tests adopted with thanks from preact-core implementation at
|
Tests adopted with thanks from preact-core implementation at
|
||||||
https://github.com/preactjs/core/blob/main/packages/core/test/atom.test.tsx
|
https://github.com/preactjs/signals/blob/main/packages/core/test/signal.test.tsx
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ describe("graph updates", function()
|
||||||
-- C
|
-- C
|
||||||
-- |
|
-- |
|
||||||
-- D
|
-- D
|
||||||
local a = atom(2)
|
local a, setA = signal(2)
|
||||||
|
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a() - 1
|
return a() - 1
|
||||||
|
|
@ -64,12 +64,12 @@ describe("graph updates", function()
|
||||||
assert(computeCalls == 1, "compute should be called once")
|
assert(computeCalls == 1, "compute should be called once")
|
||||||
computeCalls = 0
|
computeCalls = 0
|
||||||
|
|
||||||
a(4)
|
setA(4)
|
||||||
d()
|
d()
|
||||||
assert(computeCalls == 1, "compute should drop update")
|
assert(computeCalls == 1, "compute should drop update")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should only update every atom once (diamond graph)", function()
|
test("should only update every signal once (diamond graph)", function()
|
||||||
-- In this scenario "D" should only update once when "A" receives
|
-- In this scenario "D" should only update once when "A" receives
|
||||||
-- an update. This is sometimes referred to as the "diamond" scenario.
|
-- an update. This is sometimes referred to as the "diamond" scenario.
|
||||||
-- A
|
-- A
|
||||||
|
|
@ -77,8 +77,7 @@ describe("graph updates", function()
|
||||||
-- B C
|
-- B C
|
||||||
-- \ /
|
-- \ /
|
||||||
-- D
|
-- D
|
||||||
|
local a, setA = signal("a")
|
||||||
local a = atom("a")
|
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -96,12 +95,12 @@ describe("graph updates", function()
|
||||||
assert(d() == "a a", "d should be 'a a'")
|
assert(d() == "a a", "d should be 'a a'")
|
||||||
assert(spyCalls == 1, "compute should be called once")
|
assert(spyCalls == 1, "compute should be called once")
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
assert(d() == "aa aa", "d should be 'aa aa'")
|
assert(d() == "aa aa", "d should be 'aa aa'")
|
||||||
assert(spyCalls == 2, "compute should be called only twice")
|
assert(spyCalls == 2, "compute should be called only twice")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should only update every atom once (diamond graph + tail)", function()
|
test("should only update every signal once (diamond graph + tail)", function()
|
||||||
-- "E" will be likely updated twice if our mark+sweep logic is buggy.
|
-- "E" will be likely updated twice if our mark+sweep logic is buggy.
|
||||||
-- A
|
-- A
|
||||||
-- / \
|
-- / \
|
||||||
|
|
@ -110,8 +109,7 @@ describe("graph updates", function()
|
||||||
-- D
|
-- D
|
||||||
-- |
|
-- |
|
||||||
-- E
|
-- E
|
||||||
|
local a, setA = signal("a")
|
||||||
local a = atom("a")
|
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -133,7 +131,7 @@ describe("graph updates", function()
|
||||||
assert(e() == "a a", "e should be 'a a'")
|
assert(e() == "a a", "e should be 'a a'")
|
||||||
assert(spyCalls == 1, "compute should be called once")
|
assert(spyCalls == 1, "compute should be called once")
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
assert(e() == "aa aa", "e should be 'aa aa'")
|
assert(e() == "aa aa", "e should be 'aa aa'")
|
||||||
assert(spyCalls == 2, "compute should be called only twice")
|
assert(spyCalls == 2, "compute should be called only twice")
|
||||||
end)
|
end)
|
||||||
|
|
@ -141,7 +139,7 @@ describe("graph updates", function()
|
||||||
test("should bail out if result is the same", function()
|
test("should bail out if result is the same", function()
|
||||||
-- Bail out if value of "B" never changes
|
-- Bail out if value of "B" never changes
|
||||||
-- A->B->C
|
-- A->B->C
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
a()
|
a()
|
||||||
return "foo"
|
return "foo"
|
||||||
|
|
@ -157,12 +155,12 @@ describe("graph updates", function()
|
||||||
assert(c() == "foo", "c should be 'foo'")
|
assert(c() == "foo", "c should be 'foo'")
|
||||||
assert(spyCalls == 1, "compute should be called once")
|
assert(spyCalls == 1, "compute should be called once")
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
assert(c() == "foo", "c should be 'foo'")
|
assert(c() == "foo", "c should be 'foo'")
|
||||||
assert(spyCalls == 1, "compute should bail out")
|
assert(spyCalls == 1, "compute should bail out")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should only update every atom once (jagged diamond graph + tails)", function()
|
test("should only update every signal once (jagged diamond graph + tails)", function()
|
||||||
-- "F" and "G" will be likely updated twice if our mark+sweep logic is buggy.
|
-- "F" and "G" will be likely updated twice if our mark+sweep logic is buggy.
|
||||||
-- A
|
-- A
|
||||||
-- / \
|
-- / \
|
||||||
|
|
@ -173,7 +171,7 @@ describe("graph updates", function()
|
||||||
-- E
|
-- E
|
||||||
-- / \
|
-- / \
|
||||||
-- F G
|
-- F G
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
|
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
|
|
@ -222,7 +220,7 @@ describe("graph updates", function()
|
||||||
fSpyCalls, fSpyTime = 0, 0
|
fSpyCalls, fSpyTime = 0, 0
|
||||||
gSpyCalls, gSpyTime = 0, 0
|
gSpyCalls, gSpyTime = 0, 0
|
||||||
|
|
||||||
a("b")
|
setA("b")
|
||||||
|
|
||||||
assert(e() == "b b", "e should be 'b b'")
|
assert(e() == "b b", "e should be 'b b'")
|
||||||
assert(eSpyCalls == 1, "e should run once after update")
|
assert(eSpyCalls == 1, "e should run once after update")
|
||||||
|
|
@ -237,7 +235,7 @@ describe("graph updates", function()
|
||||||
fSpyCalls, fSpyTime = 0, 0
|
fSpyCalls, fSpyTime = 0, 0
|
||||||
gSpyCalls, gSpyTime = 0, 0
|
gSpyCalls, gSpyTime = 0, 0
|
||||||
|
|
||||||
a("c")
|
setA("c")
|
||||||
|
|
||||||
assert(e() == "c c", "e should be 'c c'")
|
assert(e() == "c c", "e should be 'c c'")
|
||||||
assert(eSpyCalls == 1, "e should run once after update")
|
assert(eSpyCalls == 1, "e should run once after update")
|
||||||
|
|
@ -258,7 +256,7 @@ describe("graph updates", function()
|
||||||
-- *A
|
-- *A
|
||||||
-- / \
|
-- / \
|
||||||
-- *B C <- we don't listen to C
|
-- *B C <- we don't listen to C
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
|
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
|
|
@ -273,7 +271,7 @@ describe("graph updates", function()
|
||||||
assert(b() == "a", "b should be 'a'")
|
assert(b() == "a", "b should be 'a'")
|
||||||
assert(spyCalls == 0, "compute should not run")
|
assert(spyCalls == 0, "compute should not run")
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
assert(b() == "aa", "b should be 'aa'")
|
assert(b() == "aa", "b should be 'aa'")
|
||||||
assert(spyCalls == 0, "compute should not run")
|
assert(spyCalls == 0, "compute should not run")
|
||||||
end)
|
end)
|
||||||
|
|
@ -287,7 +285,7 @@ describe("graph updates", function()
|
||||||
-- *B D <- we don't listen to C
|
-- *B D <- we don't listen to C
|
||||||
-- |
|
-- |
|
||||||
-- *C
|
-- *C
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
local spyBCalls = 0
|
local spyBCalls = 0
|
||||||
local spyB = function()
|
local spyB = function()
|
||||||
spyBCalls += 1
|
spyBCalls += 1
|
||||||
|
|
@ -318,7 +316,7 @@ describe("graph updates", function()
|
||||||
spyCCalls = 0
|
spyCCalls = 0
|
||||||
unsub()
|
unsub()
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
|
|
||||||
assert(spyBCalls == 0, "b should not run")
|
assert(spyBCalls == 0, "b should not run")
|
||||||
assert(spyCCalls == 0, "c should not run")
|
assert(spyCCalls == 0, "c should not run")
|
||||||
|
|
@ -335,7 +333,7 @@ describe("graph updates", function()
|
||||||
-- B *C <- returns same value every time
|
-- B *C <- returns same value every time
|
||||||
-- \ /
|
-- \ /
|
||||||
-- D
|
-- D
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -353,7 +351,7 @@ describe("graph updates", function()
|
||||||
assert(d() == "a c", "d should be 'a c'")
|
assert(d() == "a c", "d should be 'a c'")
|
||||||
spyResult = nil
|
spyResult = nil
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
d()
|
d()
|
||||||
assert(spyResult == "aa c", "d should have returned 'aa c'")
|
assert(spyResult == "aa c", "d should have returned 'aa c'")
|
||||||
end)
|
end)
|
||||||
|
|
@ -367,7 +365,7 @@ describe("graph updates", function()
|
||||||
-- B *C *D
|
-- B *C *D
|
||||||
-- \ | /
|
-- \ | /
|
||||||
-- E
|
-- E
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -389,13 +387,13 @@ describe("graph updates", function()
|
||||||
assert(e() == "a c d", "e should be 'a c d'")
|
assert(e() == "a c d", "e should be 'a c d'")
|
||||||
spyResult = nil
|
spyResult = nil
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
e()
|
e()
|
||||||
assert(spyResult == "aa c d", "e should have returned 'aa c d'")
|
assert(spyResult == "aa c d", "e should have returned 'aa c d'")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should support lazy branches", function()
|
test("should support lazy branches", function()
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
@ -404,10 +402,10 @@ describe("graph updates", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(c() == 0, "c should be 0")
|
assert(c() == 0, "c should be 0")
|
||||||
a(1)
|
setA(1)
|
||||||
assert(c() == 1, "c should be 1")
|
assert(c() == 1, "c should be 1")
|
||||||
|
|
||||||
a(0)
|
setA(0)
|
||||||
assert(c() == 0, "c should be 0 again")
|
assert(c() == 0, "c should be 0 again")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -419,7 +417,7 @@ describe("graph updates", function()
|
||||||
-- *B *C
|
-- *B *C
|
||||||
-- \ /
|
-- \ /
|
||||||
-- D
|
-- D
|
||||||
local a = atom("a")
|
local a, setA = signal("a")
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
a()
|
a()
|
||||||
return "b"
|
return "b"
|
||||||
|
|
@ -438,14 +436,14 @@ describe("graph updates", function()
|
||||||
assert(d() == "b c", "d should be 'b c'")
|
assert(d() == "b c", "d should be 'b c'")
|
||||||
spyCalls = 0
|
spyCalls = 0
|
||||||
|
|
||||||
a("aa")
|
setA("aa")
|
||||||
assert(spyCalls == 0, "d should not run")
|
assert(spyCalls == 0, "d should not run")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("error handling", function()
|
describe("error handling", function()
|
||||||
test("should keep graph consistent on errors during activation", function()
|
test("should keep graph consistent on errors during activation", function()
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
error("fail")
|
error("fail")
|
||||||
end)
|
end)
|
||||||
|
|
@ -459,12 +457,12 @@ describe("error handling", function()
|
||||||
assert(not success, "should throw")
|
assert(not success, "should throw")
|
||||||
assert(string.match(result, "fail"), "should throw fail")
|
assert(string.match(result, "fail"), "should throw fail")
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
assert(c() == 1, "c should recover and return 1")
|
assert(c() == 1, "c should recover and return 1")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should keep graph consistent on errors in computeds", function()
|
test("should keep graph consistent on errors in computeds", function()
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = computed(function()
|
local b = computed(function()
|
||||||
if a() == 1 then
|
if a() == 1 then
|
||||||
error("fail")
|
error("fail")
|
||||||
|
|
@ -477,14 +475,14 @@ describe("error handling", function()
|
||||||
|
|
||||||
assert(c() == 0, "c should be 0")
|
assert(c() == 0, "c should be 0")
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
local success, result = pcall(function()
|
local success, result = pcall(function()
|
||||||
b()
|
b()
|
||||||
end)
|
end)
|
||||||
assert(not success, "should throw")
|
assert(not success, "should throw")
|
||||||
assert(string.match(result, "fail"), "should throw fail")
|
assert(string.match(result, "fail"), "should throw fail")
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
assert(c() == 2, "c should recover and return 2")
|
assert(c() == 2, "c should recover and return 2")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
local atom = Charm.atom
|
local signal = Charm.signal
|
||||||
local listen = Charm.listen
|
local listen = Charm.listen
|
||||||
local effect = Charm.effect
|
local effect = Charm.effect
|
||||||
local onCleanup = Charm.onCleanup
|
local onCleanup = Charm.onCleanup
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should listen to selector", function()
|
test("should listen to selector", function()
|
||||||
local source = atom(0)
|
local source, setSource = signal(0)
|
||||||
local current, previous
|
local current, previous
|
||||||
|
|
||||||
local unsub = listen(function()
|
local unsub = listen(function()
|
||||||
|
|
@ -19,20 +19,20 @@ test("should listen to selector", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(current == 1 and previous == nil, "should run initially")
|
assert(current == 1 and previous == nil, "should run initially")
|
||||||
source(0)
|
setSource(0)
|
||||||
assert(current == 1 and previous == nil, "should skip redundant update")
|
assert(current == 1 and previous == nil, "should skip redundant update")
|
||||||
source(1)
|
setSource(1)
|
||||||
assert(current == 2 and previous == 1, "should run with new value")
|
assert(current == 2 and previous == 1, "should run with new value")
|
||||||
unsub()
|
unsub()
|
||||||
source(2)
|
setSource(2)
|
||||||
assert(current == 2 and previous == 1, "should not run after unsub")
|
assert(current == 2 and previous == 1, "should not run after unsub")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run in peek", function()
|
test("should run in peek", function()
|
||||||
local runs = 0
|
local runs = 0
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = atom(0)
|
local b, setB = signal(0)
|
||||||
|
|
||||||
listen(a, function()
|
listen(a, function()
|
||||||
runs += 1
|
runs += 1
|
||||||
|
|
@ -47,31 +47,31 @@ test("should run in peek", function()
|
||||||
assert(runs == 1, "should run once on init")
|
assert(runs == 1, "should run once on init")
|
||||||
assert(cleanups == 0, "should not cleanup yet")
|
assert(cleanups == 0, "should not cleanup yet")
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
assert(runs == 2, "should run on a change")
|
assert(runs == 2, "should run on a change")
|
||||||
assert(cleanups == 1, "should cleanup first call")
|
assert(cleanups == 1, "should cleanup first call")
|
||||||
|
|
||||||
b(1)
|
setB(1)
|
||||||
assert(runs == 2, "should not run on b change")
|
assert(runs == 2, "should not run on b change")
|
||||||
assert(cleanups == 1, "should not cleanup second call")
|
assert(cleanups == 1, "should not cleanup second call")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run if first value is nil", function()
|
test("should run if first value is nil", function()
|
||||||
local runs = 0
|
local runs = 0
|
||||||
local source = atom(nil :: number?)
|
local source, setSource = signal(nil :: number?)
|
||||||
|
|
||||||
listen(source, function()
|
listen(source, function()
|
||||||
runs += 1
|
runs += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(runs == 1, "should run once on init")
|
assert(runs == 1, "should run once on init")
|
||||||
source(1)
|
setSource(1)
|
||||||
assert(runs == 2, "should run once on change")
|
assert(runs == 2, "should run once on change")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should track inner subs", function()
|
test("should track inner subs", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
|
|
||||||
local unsub = listen(a, function()
|
local unsub = listen(a, function()
|
||||||
table.insert(order, 1)
|
table.insert(order, 1)
|
||||||
|
|
@ -86,22 +86,22 @@ test("should track inner subs", function()
|
||||||
assert(table.concat(order) == "123", `bad mount order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad mount order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
unsub()
|
unsub()
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should skip redundant updates", function()
|
test("should skip redundant updates", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
|
|
||||||
listen(function(): "even" | "odd"
|
listen(function(): "even" | "odd"
|
||||||
return if a() % 2 == 0 then "even" else "odd"
|
return if a() % 2 == 0 then "even" else "odd"
|
||||||
|
|
@ -109,17 +109,17 @@ test("should skip redundant updates", function()
|
||||||
table.insert(order, value)
|
table.insert(order, value)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
a(3)
|
setA(3)
|
||||||
a(4)
|
setA(4)
|
||||||
a(6)
|
setA(6)
|
||||||
assert(table.concat(order) == "evenoddeven", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "evenoddeven", `bad order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should allow self cleanup", function()
|
test("should allow self cleanup", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
|
|
||||||
listen(a, function(_, _, stopEffect)
|
listen(a, function(_, _, stopEffect)
|
||||||
onCleanup(function()
|
onCleanup(function()
|
||||||
|
|
@ -131,7 +131,7 @@ test("should allow self cleanup", function()
|
||||||
|
|
||||||
assert(calls == 1, "should call effect once")
|
assert(calls == 1, "should call effect once")
|
||||||
assert(cleanups == 1, "should call cleanup once")
|
assert(cleanups == 1, "should call cleanup once")
|
||||||
a(1)
|
setA(1)
|
||||||
assert(calls == 1, "should not call effect after a update")
|
assert(calls == 1, "should not call effect after a update")
|
||||||
assert(cleanups == 1, "should not call cleanup after a update")
|
assert(cleanups == 1, "should not call cleanup after a update")
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
local atom = Charm.atom
|
local signal = Charm.signal
|
||||||
local mapped = Charm.mapped
|
local mapped = Charm.mapped
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should map atom", function()
|
test("should map state", function()
|
||||||
local src = atom({ 1, 2, 3 })
|
local src, setSrc = signal({ 1, 2, 3 })
|
||||||
local map = mapped(src, function(value: number)
|
local map = mapped(src, function(value: number)
|
||||||
return value + 1
|
return value + 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(table.concat(map()) == "234", table.concat(map()))
|
assert(table.concat(map()) == "234", table.concat(map()))
|
||||||
src({ 4, 5, 6 })
|
setSrc({ 4, 5, 6 })
|
||||||
assert(table.concat(map()) == "567", table.concat(map()))
|
assert(table.concat(map()) == "567", table.concat(map()))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not map unchanged values", function()
|
test("should not map unchanged values", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
|
|
||||||
local src = atom({ 1, 2, 3 })
|
local src, setSrc = signal({ 1, 2, 3 })
|
||||||
local map = mapped(src, function(value: number)
|
local map = mapped(src, function(value: number)
|
||||||
calls += 1
|
calls += 1
|
||||||
return value + 1
|
return value + 1
|
||||||
|
|
@ -28,11 +28,11 @@ test("should not map unchanged values", function()
|
||||||
assert(table.concat(map()) == "234", table.concat(map()))
|
assert(table.concat(map()) == "234", table.concat(map()))
|
||||||
assert(calls == 3, "should map 3 values")
|
assert(calls == 3, "should map 3 values")
|
||||||
|
|
||||||
src({ 1, 3, 3 })
|
setSrc({ 1, 3, 3 })
|
||||||
assert(table.concat(map()) == "244", table.concat(map()))
|
assert(table.concat(map()) == "244", table.concat(map()))
|
||||||
assert(calls == 4, "should map 1 change")
|
assert(calls == 4, "should map 1 change")
|
||||||
|
|
||||||
src({})
|
setSrc({})
|
||||||
assert(#map() == 0, "should be empty")
|
assert(#map() == 0, "should be empty")
|
||||||
assert(calls == 4, "should not map for removed values")
|
assert(calls == 4, "should not map for removed values")
|
||||||
end)
|
end)
|
||||||
|
|
@ -40,7 +40,7 @@ end)
|
||||||
test("should not update if no values change", function()
|
test("should not update if no values change", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
|
|
||||||
local src = atom({ 1, 2, 3 })
|
local src, setSrc = signal({ 1, 2, 3 })
|
||||||
local map = mapped(src, function(value: number)
|
local map = mapped(src, function(value: number)
|
||||||
calls += 1
|
calls += 1
|
||||||
return value + 1
|
return value + 1
|
||||||
|
|
@ -50,8 +50,8 @@ test("should not update if no values change", function()
|
||||||
assert(calls == 3, "should map 3 values")
|
assert(calls == 3, "should map 3 values")
|
||||||
assert(table.concat(map()) == "234", table.concat(map()))
|
assert(table.concat(map()) == "234", table.concat(map()))
|
||||||
|
|
||||||
src({ 4, 5, 6, 7 })
|
setSrc({ 4, 5, 6, 7 })
|
||||||
src({ 1, 2, 3 })
|
setSrc({ 1, 2, 3 })
|
||||||
map()
|
map()
|
||||||
assert(calls == 3, "should not map unchanged values")
|
assert(calls == 3, "should not map unchanged values")
|
||||||
assert(table.concat(map()) == "234", table.concat(map()))
|
assert(table.concat(map()) == "234", table.concat(map()))
|
||||||
|
|
@ -61,7 +61,7 @@ end)
|
||||||
test("should filter values", function()
|
test("should filter values", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
|
|
||||||
local src = atom({ a = 1, b = 2, c = 3 })
|
local src, setSrc = signal({ a = 1, b = 2, c = 3 })
|
||||||
local map = mapped(src, function(value: number)
|
local map = mapped(src, function(value: number)
|
||||||
calls += 1
|
calls += 1
|
||||||
return if value < 0 then nil else value
|
return if value < 0 then nil else value
|
||||||
|
|
@ -72,7 +72,7 @@ test("should filter values", function()
|
||||||
assert(state.a and state.b and state.c, "should map all values")
|
assert(state.a and state.b and state.c, "should map all values")
|
||||||
|
|
||||||
calls = 0
|
calls = 0
|
||||||
src({ a = -1, b = 2, c = 3, d = 4 })
|
setSrc({ a = -1, b = 2, c = 3, d = 4 })
|
||||||
state = map()
|
state = map()
|
||||||
assert(calls == 2, "should map 2 values")
|
assert(calls == 2, "should map 2 values")
|
||||||
assert(state.a == nil, "should remove a")
|
assert(state.a == nil, "should remove a")
|
||||||
|
|
@ -83,7 +83,7 @@ end)
|
||||||
test("should map keys", function()
|
test("should map keys", function()
|
||||||
local calls = 0
|
local calls = 0
|
||||||
|
|
||||||
local src = atom({ a = 1, b = 2, c = 3 })
|
local src, setSrc = signal({ a = 1, b = 2, c = 3 })
|
||||||
local map = mapped(src, function(value: number, key: string)
|
local map = mapped(src, function(value: number, key: string)
|
||||||
calls += 1
|
calls += 1
|
||||||
return if value < 0 then nil else value, string.upper(key)
|
return if value < 0 then nil else value, string.upper(key)
|
||||||
|
|
@ -97,7 +97,7 @@ test("should map keys", function()
|
||||||
assert(state.C == 3, "should map c")
|
assert(state.C == 3, "should map c")
|
||||||
|
|
||||||
calls = 0
|
calls = 0
|
||||||
src({ a = -1, b = 2, c = 3, d = 4 })
|
setSrc({ a = -1, b = 2, c = 3, d = 4 })
|
||||||
state = map()
|
state = map()
|
||||||
assert(calls == 2, "should map 2 values")
|
assert(calls == 2, "should map 2 values")
|
||||||
assert(state.A == nil and state.a == nil, "should remove a")
|
assert(state.A == nil and state.a == nil, "should remove a")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
local atom = Charm.atom
|
local signal = Charm.signal
|
||||||
local computed = Charm.computed
|
local computed = Charm.computed
|
||||||
local effect = Charm.effect
|
local effect = Charm.effect
|
||||||
local observe = Charm.observe
|
local observe = Charm.observe
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should observe items", function()
|
test("should observe items", function()
|
||||||
local items = atom({ 0, 0, 0 })
|
local items, setItems = signal({ 0, 0, 0 })
|
||||||
local updates = {}
|
local updates = {}
|
||||||
local cleanups = {}
|
local cleanups = {}
|
||||||
|
|
||||||
|
|
@ -29,18 +29,18 @@ test("should observe items", function()
|
||||||
assert(table.concat(updates) == "000", `bad mount: {table.concat(updates)}`)
|
assert(table.concat(updates) == "000", `bad mount: {table.concat(updates)}`)
|
||||||
|
|
||||||
table.clear(updates)
|
table.clear(updates)
|
||||||
items({ 1, 2, 3 })
|
setItems({ 1, 2, 3 })
|
||||||
assert(table.concat(updates) == "123", `bad update: {table.concat(updates)}`)
|
assert(table.concat(updates) == "123", `bad update: {table.concat(updates)}`)
|
||||||
|
|
||||||
table.clear(updates)
|
table.clear(updates)
|
||||||
items({ 1, 2, nil :: any, 4 })
|
setItems({ 1, 2, nil :: any, 4 })
|
||||||
assert(table.concat(updates) == "4", `should add 4: {table.concat(updates)}`)
|
assert(table.concat(updates) == "4", `should add 4: {table.concat(updates)}`)
|
||||||
assert(table.concat(cleanups) == "3", `bad cleanup: {table.concat(cleanups)}`)
|
assert(table.concat(cleanups) == "3", `bad cleanup: {table.concat(cleanups)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should dispose scopes", function()
|
test("should dispose scopes", function()
|
||||||
local items = atom({ 1, 2, 3 })
|
local items, setItems = signal({ 1, 2, 3 })
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local runs = {}
|
local runs = {}
|
||||||
|
|
||||||
local dispose = observe(items, function(item)
|
local dispose = observe(items, function(item)
|
||||||
|
|
@ -51,23 +51,23 @@ test("should dispose scopes", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.clear(runs)
|
table.clear(runs)
|
||||||
items({ 1, 2 })
|
setItems({ 1, 2 })
|
||||||
a(1)
|
setA(1)
|
||||||
assert(table.concat(runs) == "12", `bad reruns: {table.concat(runs)}`)
|
assert(table.concat(runs) == "12", `bad reruns: {table.concat(runs)}`)
|
||||||
|
|
||||||
table.clear(runs)
|
table.clear(runs)
|
||||||
dispose()
|
dispose()
|
||||||
a(2)
|
setA(2)
|
||||||
assert(table.concat(runs) == "", `should not rerun: {table.concat(runs)}`)
|
assert(table.concat(runs) == "", `should not rerun: {table.concat(runs)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should queue addition", function()
|
test("should queue addition", function()
|
||||||
local items = atom({ 1 })
|
local items, setItems = signal({ 1 })
|
||||||
local edits = {}
|
local edits = {}
|
||||||
|
|
||||||
observe(items, function(item)
|
observe(items, function(item)
|
||||||
items({ 1, 2 })
|
setItems({ 1, 2 })
|
||||||
items({ 1, 2, 3, 4 })
|
setItems({ 1, 2, 3, 4 })
|
||||||
table.insert(edits, `+{item}`)
|
table.insert(edits, `+{item}`)
|
||||||
return function()
|
return function()
|
||||||
table.insert(edits, `-{item}`)
|
table.insert(edits, `-{item}`)
|
||||||
|
|
@ -78,12 +78,12 @@ test("should queue addition", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should queue removal", function()
|
test("should queue removal", function()
|
||||||
local items = atom({ 1, 2, 3, 4 })
|
local items, setItems = signal({ 1, 2, 3, 4 })
|
||||||
local edits = {}
|
local edits = {}
|
||||||
|
|
||||||
observe(items, function(item)
|
observe(items, function(item)
|
||||||
items({ 1, 2, 3 })
|
setItems({ 1, 2, 3 })
|
||||||
items({ 1 })
|
setItems({ 1 })
|
||||||
table.insert(edits, `+{item}`)
|
table.insert(edits, `+{item}`)
|
||||||
return function()
|
return function()
|
||||||
table.insert(edits, `-{item}`)
|
table.insert(edits, `-{item}`)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
local atom = Charm.atom
|
local signal = Charm.signal
|
||||||
local computed = Charm.computed
|
local computed = Charm.computed
|
||||||
local effect = Charm.effect
|
local effect = Charm.effect
|
||||||
local peek = Charm.peek
|
local peek = Charm.peek
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should not run detached", function()
|
test("should not run detached", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -23,15 +23,15 @@ test("should not run detached", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(spyCalls == 0, "should not be called yet")
|
assert(spyCalls == 0, "should not be called yet")
|
||||||
a(2)
|
setA(2)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
a(3)
|
setA(3)
|
||||||
assert(spyCalls == 2, "should be called twice")
|
assert(spyCalls == 2, "should be called twice")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking inside effects", function()
|
test("should block tracking inside effects", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -42,13 +42,13 @@ test("should block tracking inside effects", function()
|
||||||
end)
|
end)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
|
|
||||||
a(10)
|
setA(10)
|
||||||
b(20)
|
setB(20)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking even when run inside effect run inside peek", function()
|
test("should block tracking even when run inside effect run inside peek", function()
|
||||||
local s = atom(1)
|
local s, setS = signal(1)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
peek(function()
|
peek(function()
|
||||||
|
|
@ -61,32 +61,32 @@ test("should block tracking even when run inside effect run inside peek", functi
|
||||||
end)
|
end)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
|
|
||||||
s(2)
|
setS(2)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not cause atom assignments throw", function()
|
test("should not cause signal assignments to throw", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local aChangedTime = atom(0)
|
local aChangedTime, setAChangedTime = signal(0)
|
||||||
|
|
||||||
local dispose = effect(function()
|
local dispose = effect(function()
|
||||||
a()
|
a()
|
||||||
peek(function()
|
peek(function()
|
||||||
aChangedTime(aChangedTime() + 1)
|
setAChangedTime(aChangedTime() + 1)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
assert(aChangedTime() == 2, "aChangedTime should be 2")
|
assert(aChangedTime() == 2, "aChangedTime should be 2")
|
||||||
a(3)
|
setA(3)
|
||||||
assert(aChangedTime() == 3, "aChangedTime should be 3")
|
assert(aChangedTime() == 3, "aChangedTime should be 3")
|
||||||
|
|
||||||
dispose()
|
dispose()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking inside computed atoms", function()
|
test("should block tracking inside computed signals", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
local c = computed(function()
|
local c = computed(function()
|
||||||
return peek(function()
|
return peek(function()
|
||||||
|
|
@ -97,18 +97,18 @@ test("should block tracking inside computed atoms", function()
|
||||||
|
|
||||||
assert(spyCalls == 0, "should not be called yet")
|
assert(spyCalls == 0, "should not be called yet")
|
||||||
assert(c() == 3, "c should be 3")
|
assert(c() == 3, "c should be 3")
|
||||||
a(10)
|
setA(10)
|
||||||
c()
|
c()
|
||||||
b(20)
|
setB(20)
|
||||||
c()
|
c()
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
assert(c() == 3, "c should be 3")
|
assert(c() == 3, "c should be 3")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should track outside peek", function()
|
test("should track outside peek", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local c = atom(3)
|
local c, setC = signal(3)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -119,16 +119,16 @@ test("should track outside peek", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
a(10)
|
setA(10)
|
||||||
assert(spyCalls == 2, "should be called twice")
|
assert(spyCalls == 2, "should be called twice")
|
||||||
b(20)
|
setB(20)
|
||||||
assert(spyCalls == 3, "should be called three times")
|
assert(spyCalls == 3, "should be called three times")
|
||||||
c(30)
|
setC(30)
|
||||||
assert(spyCalls == 3, "should not run on c change")
|
assert(spyCalls == 3, "should not run on c change")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should stack peek", function()
|
test("should stack peek", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -140,7 +140,7 @@ test("should stack peek", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
a(2)
|
setA(2)
|
||||||
assert(spyCalls == 1, "should not be called twice")
|
assert(spyCalls == 1, "should not be called twice")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
type Atom<T> = Charm.Atom<T>
|
local signal = Charm.signal
|
||||||
|
|
||||||
local atom = Charm.atom
|
|
||||||
local computed = Charm.computed
|
local computed = Charm.computed
|
||||||
local subscribe = Charm.subscribe
|
local subscribe = Charm.subscribe
|
||||||
local listen = Charm.listen
|
local listen = Charm.listen
|
||||||
|
|
@ -11,7 +9,7 @@ local effect = Charm.effect
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should subscribe to selector", function()
|
test("should subscribe to selector", function()
|
||||||
local source = atom(0)
|
local source, setSource = signal(0)
|
||||||
local current, previous
|
local current, previous
|
||||||
|
|
||||||
local unsub = subscribe(function()
|
local unsub = subscribe(function()
|
||||||
|
|
@ -22,20 +20,20 @@ test("should subscribe to selector", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(not current and not previous, "should not run initially")
|
assert(not current and not previous, "should not run initially")
|
||||||
source(0)
|
setSource(0)
|
||||||
assert(not current and not previous, "should skip redundant update")
|
assert(not current and not previous, "should skip redundant update")
|
||||||
source(1)
|
setSource(1)
|
||||||
assert(current == 2 and previous == 1, "should run with new value")
|
assert(current == 2 and previous == 1, "should run with new value")
|
||||||
unsub()
|
unsub()
|
||||||
source(2)
|
setSource(2)
|
||||||
assert(current == 2 and previous == 1, "should not run after unsub")
|
assert(current == 2 and previous == 1, "should not run after unsub")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should run in peek", function()
|
test("should run in peek", function()
|
||||||
local runs = 0
|
local runs = 0
|
||||||
local cleanups = 0
|
local cleanups = 0
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
local b = atom(0)
|
local b, setB = signal(0)
|
||||||
|
|
||||||
subscribe(a, function()
|
subscribe(a, function()
|
||||||
runs += 1
|
runs += 1
|
||||||
|
|
@ -50,30 +48,30 @@ test("should run in peek", function()
|
||||||
assert(runs == 0, "should not run once on init")
|
assert(runs == 0, "should not run once on init")
|
||||||
assert(cleanups == 0, "should not cleanup yet")
|
assert(cleanups == 0, "should not cleanup yet")
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
assert(runs == 1, "should run once on change")
|
assert(runs == 1, "should run once on change")
|
||||||
assert(cleanups == 0, "should not cleanup yet")
|
assert(cleanups == 0, "should not cleanup yet")
|
||||||
|
|
||||||
b(1)
|
setB(1)
|
||||||
assert(runs == 1, "should not run on b change")
|
assert(runs == 1, "should not run on b change")
|
||||||
assert(cleanups == 0, "should not cleanup first call")
|
assert(cleanups == 0, "should not cleanup first call")
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
assert(runs == 2, "should run on a change")
|
assert(runs == 2, "should run on a change")
|
||||||
assert(cleanups == 1, "should cleanup first call")
|
assert(cleanups == 1, "should cleanup first call")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should ensure correct execution order", function()
|
test("should ensure correct execution order", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
local n1 = atom(0)
|
local n1, setN1 = signal(0)
|
||||||
local n2 = atom(0)
|
local n2, setN2 = signal(0)
|
||||||
local sum = computed(function()
|
local sum = computed(function()
|
||||||
return n1() + n2()
|
return n1() + n2()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
subscribe(n1, function()
|
subscribe(n1, function()
|
||||||
table.insert(order, 1)
|
table.insert(order, 1)
|
||||||
n2(n2() + 1)
|
setN2(n2() + 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
subscribe(sum, function()
|
subscribe(sum, function()
|
||||||
|
|
@ -84,14 +82,14 @@ test("should ensure correct execution order", function()
|
||||||
table.insert(order, 3)
|
table.insert(order, 3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
n1(n1() + 1)
|
setN1(n1() + 1)
|
||||||
|
|
||||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should track inner subs", function()
|
test("should track inner subs", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
|
|
||||||
local unsub = subscribe(a, function()
|
local unsub = subscribe(a, function()
|
||||||
table.insert(order, 1)
|
table.insert(order, 1)
|
||||||
|
|
@ -103,22 +101,22 @@ test("should track inner subs", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||||
|
|
||||||
table.clear(order)
|
table.clear(order)
|
||||||
unsub()
|
unsub()
|
||||||
a(a() + 1)
|
setA(a() + 1)
|
||||||
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should skip redundant updates", function()
|
test("should skip redundant updates", function()
|
||||||
local order = {}
|
local order = {}
|
||||||
local a = atom(0)
|
local a, setA = signal(0)
|
||||||
|
|
||||||
subscribe(function(): "even" | "odd"
|
subscribe(function(): "even" | "odd"
|
||||||
return if a() % 2 == 0 then "even" else "odd"
|
return if a() % 2 == 0 then "even" else "odd"
|
||||||
|
|
@ -126,10 +124,10 @@ test("should skip redundant updates", function()
|
||||||
table.insert(order, value)
|
table.insert(order, value)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(1)
|
setA(1)
|
||||||
a(3)
|
setA(3)
|
||||||
a(4)
|
setA(4)
|
||||||
a(6)
|
setA(6)
|
||||||
assert(table.concat(order) == "oddeven", `bad order: {table.concat(order)}`)
|
assert(table.concat(order) == "oddeven", `bad order: {table.concat(order)}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
local Charm = require("../")
|
local Charm = require("../")
|
||||||
local suite = require("../../../../test/suite")
|
local suite = require("../../../../test/suite")
|
||||||
|
|
||||||
local atom = Charm.atom
|
local signal = Charm.signal
|
||||||
local computed = Charm.computed
|
local computed = Charm.computed
|
||||||
local effect = Charm.effect
|
local effect = Charm.effect
|
||||||
local effectScope = Charm.effectScope
|
local effectScope = Charm.effectScope
|
||||||
|
|
@ -9,7 +9,7 @@ local untracked = Charm.untracked
|
||||||
local test = suite.test
|
local test = suite.test
|
||||||
|
|
||||||
test("should run detached from effect", function()
|
test("should run detached from effect", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local effects = 0
|
local effects = 0
|
||||||
local innerEffects = {}
|
local innerEffects = {}
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ test("should run detached from effect", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(effects == 1, "should have one effect")
|
assert(effects == 1, "should have one effect")
|
||||||
a(2)
|
setA(2)
|
||||||
assert(effects == 2, "should have two effects")
|
assert(effects == 2, "should have two effects")
|
||||||
stopEffect()
|
stopEffect()
|
||||||
assert(effects == 2, "should still have two effects")
|
assert(effects == 2, "should still have two effects")
|
||||||
|
|
@ -60,8 +60,8 @@ test("should run detached from effect scope", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking inside effects", function()
|
test("should block tracking inside effects", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -72,13 +72,13 @@ test("should block tracking inside effects", function()
|
||||||
end)
|
end)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
|
|
||||||
a(10)
|
setA(10)
|
||||||
b(20)
|
setB(20)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking even when run inside effect run inside untracked", function()
|
test("should block tracking even when run inside effect run inside untracked", function()
|
||||||
local s = atom(1)
|
local s, setS = signal(1)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
untracked(function()
|
untracked(function()
|
||||||
|
|
@ -91,32 +91,32 @@ test("should block tracking even when run inside effect run inside untracked", f
|
||||||
end)
|
end)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
|
|
||||||
s(2)
|
setS(2)
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should not cause atom assignments throw", function()
|
test("should not cause signal assignments to throw", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local aChangedTime = atom(0)
|
local aChangedTime, setAChangedTime = signal(0)
|
||||||
|
|
||||||
local dispose = effect(function()
|
local dispose = effect(function()
|
||||||
a()
|
a()
|
||||||
untracked(function()
|
untracked(function()
|
||||||
aChangedTime(aChangedTime() + 1)
|
setAChangedTime(aChangedTime() + 1)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a(2)
|
setA(2)
|
||||||
assert(aChangedTime() == 2, "aChangedTime should be 2")
|
assert(aChangedTime() == 2, "aChangedTime should be 2")
|
||||||
a(3)
|
setA(3)
|
||||||
assert(aChangedTime() == 3, "aChangedTime should be 3")
|
assert(aChangedTime() == 3, "aChangedTime should be 3")
|
||||||
|
|
||||||
dispose()
|
dispose()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should block tracking inside computed atoms", function()
|
test("should block tracking inside computed signals", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
local c = computed(function()
|
local c = computed(function()
|
||||||
return untracked(function()
|
return untracked(function()
|
||||||
|
|
@ -127,18 +127,18 @@ test("should block tracking inside computed atoms", function()
|
||||||
|
|
||||||
assert(spyCalls == 0, "should not be called yet")
|
assert(spyCalls == 0, "should not be called yet")
|
||||||
assert(c() == 3, "c should be 3")
|
assert(c() == 3, "c should be 3")
|
||||||
a(10)
|
setA(10)
|
||||||
c()
|
c()
|
||||||
b(20)
|
setB(20)
|
||||||
c()
|
c()
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
assert(c() == 3, "c should be 3")
|
assert(c() == 3, "c should be 3")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should track outside untracked", function()
|
test("should track outside untracked", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local b = atom(2)
|
local b, setB = signal(2)
|
||||||
local c = atom(3)
|
local c, setC = signal(3)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -149,16 +149,16 @@ test("should track outside untracked", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
a(10)
|
setA(10)
|
||||||
assert(spyCalls == 2, "should be called twice")
|
assert(spyCalls == 2, "should be called twice")
|
||||||
b(20)
|
setB(20)
|
||||||
assert(spyCalls == 3, "should be called three times")
|
assert(spyCalls == 3, "should be called three times")
|
||||||
c(30)
|
setC(30)
|
||||||
assert(spyCalls == 3, "should not run on c change")
|
assert(spyCalls == 3, "should not run on c change")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("should stack untracked", function()
|
test("should stack untracked", function()
|
||||||
local a = atom(1)
|
local a, setA = signal(1)
|
||||||
local spyCalls = 0
|
local spyCalls = 0
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
|
|
@ -170,7 +170,7 @@ test("should stack untracked", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(spyCalls == 1, "should be called once")
|
assert(spyCalls == 1, "should be called once")
|
||||||
a(2)
|
setA(2)
|
||||||
assert(spyCalls == 1, "should not be called twice")
|
assert(spyCalls == 1, "should not be called twice")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,30 @@
|
||||||
|
--[[
|
||||||
|
Reactive system adapted from alien-signals at
|
||||||
|
https://github.com/stackblitz/alien-signals/blob/master/src/system.ts
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024-present Johnson Chu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
export type ReactiveNode = {
|
export type ReactiveNode = {
|
||||||
deps: Link?,
|
deps: Link?,
|
||||||
depsTail: Link?,
|
depsTail: Link?,
|
||||||
|
|
@ -23,14 +50,14 @@ type Stack<T> = {
|
||||||
|
|
||||||
export type ReactiveFlags = number
|
export type ReactiveFlags = number
|
||||||
|
|
||||||
local NONE = 0b00000000
|
local NONE = 0b0000000
|
||||||
local MUTABLE = 0b00000001
|
local MUTABLE = 0b0000001
|
||||||
local WATCHING = 0b00000010
|
local WATCHING = 0b0000010
|
||||||
local RECURSED_CHECK = 0b00000100
|
local RECURSED_CHECK = 0b0000100
|
||||||
local RECURSED = 0b00001000
|
local RECURSED = 0b0001000
|
||||||
local DIRTY = 0b00010000
|
local DIRTY = 0b0010000
|
||||||
local PENDING = 0b00100000
|
local PENDING = 0b0100000
|
||||||
local PEEKING = 0b10000000
|
local PEEKING = 0b1000000
|
||||||
|
|
||||||
local ReactiveFlags = {
|
local ReactiveFlags = {
|
||||||
None = NONE,
|
None = NONE,
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,40 @@ local createReactiveSystem = alien.createReactiveSystem
|
||||||
local deepFreeze = utils.deepFreeze
|
local deepFreeze = utils.deepFreeze
|
||||||
local noYield = utils.noYield
|
local noYield = utils.noYield
|
||||||
|
|
||||||
local NONE = 0b00000000
|
--[[
|
||||||
local MUTABLE = 0b00000001
|
Reactive system adapted from alien-signals at
|
||||||
local WATCHING = 0b00000010
|
https://github.com/stackblitz/alien-signals/blob/master/src/index.ts
|
||||||
local RECURSED_CHECK = 0b00000100
|
|
||||||
local DIRTY = 0b00010000
|
MIT License
|
||||||
local PENDING = 0b00100000
|
|
||||||
local QUEUED = 0b01000000
|
Copyright (c) 2024-present Johnson Chu
|
||||||
local PEEKING = 0b10000000
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local NONE = 0b0000000
|
||||||
|
local MUTABLE = 0b0000001
|
||||||
|
local WATCHING = 0b0000010
|
||||||
|
local RECURSED_CHECK = 0b0000100
|
||||||
|
local DIRTY = 0b0010000
|
||||||
|
local PENDING = 0b0100000
|
||||||
|
local PEEKING = 0b1000000
|
||||||
|
|
||||||
type EffectScope = ReactiveNode & {
|
type EffectScope = ReactiveNode & {
|
||||||
cleanups: { () -> () },
|
cleanups: { () -> () },
|
||||||
|
|
@ -49,8 +75,8 @@ local shallowPropagate: (link: Link) -> ()
|
||||||
local cycle = 0
|
local cycle = 0
|
||||||
local batchDepth = 0
|
local batchDepth = 0
|
||||||
local notifyIndex = 0
|
local notifyIndex = 0
|
||||||
local queuedEffects: { Effect | EffectScope? } = {}
|
local queued: { Effect? } = {}
|
||||||
local queuedEffectsLength = 0
|
local queuedLength = 0
|
||||||
local activeSub: ReactiveNode?
|
local activeSub: ReactiveNode?
|
||||||
|
|
||||||
local function getActiveSub(): ReactiveNode?
|
local function getActiveSub(): ReactiveNode?
|
||||||
|
|
@ -83,15 +109,9 @@ local function runCleanups(effect: EffectScope)
|
||||||
table.clear(effect.cleanups)
|
table.clear(effect.cleanups)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setNotPending(sub: ReactiveNode, flags: ReactiveFlags)
|
local function run(e: Effect): ()
|
||||||
sub.flags = bit32.band(flags, bit32.bnot(PENDING))
|
local flags = e.flags
|
||||||
end
|
if bit32.btest(flags, DIRTY) or (bit32.btest(flags, PENDING) and checkDirty(e.deps :: Link, e)) then
|
||||||
|
|
||||||
local function run(e: Effect, flags: ReactiveFlags): ()
|
|
||||||
if
|
|
||||||
bit32.btest(flags, DIRTY)
|
|
||||||
or (bit32.btest(flags, PENDING) and (checkDirty(e.deps :: Link, e) or setNotPending(e, flags)))
|
|
||||||
then
|
|
||||||
runCleanups(e)
|
runCleanups(e)
|
||||||
cycle += 1
|
cycle += 1
|
||||||
e.depsTail = nil
|
e.depsTail = nil
|
||||||
|
|
@ -107,30 +127,19 @@ local function run(e: Effect, flags: ReactiveFlags): ()
|
||||||
table.insert(e.cleanups, result)
|
table.insert(e.cleanups, result)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local link = e.deps
|
e.flags = WATCHING
|
||||||
while link do
|
|
||||||
local dep = link.dep
|
|
||||||
local depFlags = dep.flags
|
|
||||||
if bit32.btest(depFlags, QUEUED) then
|
|
||||||
depFlags = bit32.band(depFlags, bit32.bnot(QUEUED))
|
|
||||||
dep.flags = depFlags
|
|
||||||
run(dep :: Effect, depFlags)
|
|
||||||
end
|
|
||||||
link = link.nextDep
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function flush(): ()
|
local function flush(): ()
|
||||||
while notifyIndex < queuedEffectsLength do
|
while notifyIndex < queuedLength do
|
||||||
|
local effect = queued[notifyIndex] :: Effect
|
||||||
|
queued[notifyIndex] = nil
|
||||||
notifyIndex += 1
|
notifyIndex += 1
|
||||||
local effect = queuedEffects[notifyIndex] :: Effect
|
run(effect)
|
||||||
queuedEffects[notifyIndex] = nil
|
|
||||||
effect.flags = bit32.band(effect.flags, bit32.bnot(QUEUED))
|
|
||||||
run(effect, effect.flags)
|
|
||||||
end
|
end
|
||||||
notifyIndex = 0
|
notifyIndex = 0
|
||||||
queuedEffectsLength = 0
|
queuedLength = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getBatchDepth(): number
|
local function getBatchDepth(): number
|
||||||
|
|
@ -183,6 +192,10 @@ local function updateSignal(s: Signal): boolean
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function setNotPending(sub: ReactiveNode, flags: ReactiveFlags)
|
||||||
|
sub.flags = bit32.band(flags, bit32.bnot(PENDING))
|
||||||
|
end
|
||||||
|
|
||||||
local function computedOper<T>(this: Computed<T>): () -> T
|
local function computedOper<T>(this: Computed<T>): () -> T
|
||||||
return function(): T
|
return function(): T
|
||||||
local flags = this.flags
|
local flags = this.flags
|
||||||
|
|
@ -196,6 +209,16 @@ local function computedOper<T>(this: Computed<T>): () -> T
|
||||||
shallowPropagate(subs)
|
shallowPropagate(subs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif flags == NONE then
|
||||||
|
this.flags = MUTABLE
|
||||||
|
local prevSub = setActiveSub(this)
|
||||||
|
local success, result = pcall(this.getter)
|
||||||
|
activeSub = prevSub
|
||||||
|
if success then
|
||||||
|
this.value = result
|
||||||
|
else
|
||||||
|
error(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if activeSub then
|
if activeSub then
|
||||||
link(this, activeSub, cycle)
|
link(this, activeSub, cycle)
|
||||||
|
|
@ -332,7 +355,7 @@ end
|
||||||
--- @see https://github.com/littensy/charm?tab=readme-ov-file#computedgetter
|
--- @see https://github.com/littensy/charm?tab=readme-ov-file#computedgetter
|
||||||
local function computed<T>(getter: (previousValue: T?) -> T): () -> T
|
local function computed<T>(getter: (previousValue: T?) -> T): () -> T
|
||||||
return computedOper({
|
return computedOper({
|
||||||
flags = bit32.bor(MUTABLE, DIRTY),
|
flags = NONE,
|
||||||
getter = getter,
|
getter = getter,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -536,30 +559,40 @@ local function listen<T>(getter: () -> T, callback: (state: T, previous: T?, dis
|
||||||
return dispose
|
return dispose
|
||||||
end
|
end
|
||||||
|
|
||||||
local function notify(e: ReactiveNode)
|
|
||||||
local flags = e.flags
|
|
||||||
if not bit32.btest(flags, QUEUED) then
|
|
||||||
e.flags = bit32.bor(flags, QUEUED)
|
|
||||||
local subs = e.subs
|
|
||||||
if subs then
|
|
||||||
notify(subs.sub)
|
|
||||||
else
|
|
||||||
queuedEffectsLength += 1
|
|
||||||
queuedEffects[queuedEffectsLength] = e :: Effect
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local reactiveSystem = createReactiveSystem({
|
local reactiveSystem = createReactiveSystem({
|
||||||
update = function(node: ReactiveNode): boolean
|
update = function(node)
|
||||||
if node.depsTail then
|
if node.depsTail then
|
||||||
return updateComputed(node :: Computed)
|
return updateComputed(node :: Computed)
|
||||||
else
|
else
|
||||||
return updateSignal(node :: Signal)
|
return updateSignal(node :: Signal)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
notify = notify,
|
notify = function(effect)
|
||||||
unwatched = function(node: ReactiveNode)
|
local insertIndex = queuedLength
|
||||||
|
local firstInsertedIndex = insertIndex
|
||||||
|
|
||||||
|
repeat
|
||||||
|
effect.flags = bit32.band(effect.flags, bit32.bnot(WATCHING))
|
||||||
|
queued[insertIndex] = effect :: Effect
|
||||||
|
insertIndex += 1
|
||||||
|
effect = (effect.subs and effect.subs.sub) :: Effect
|
||||||
|
if not effect or bit32.band(effect.flags, WATCHING) == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
until false
|
||||||
|
|
||||||
|
queuedLength = insertIndex
|
||||||
|
|
||||||
|
insertIndex -= 1
|
||||||
|
while firstInsertedIndex < insertIndex do
|
||||||
|
local left = queued[firstInsertedIndex]
|
||||||
|
queued[firstInsertedIndex] = queued[insertIndex]
|
||||||
|
queued[insertIndex] = left
|
||||||
|
firstInsertedIndex += 1
|
||||||
|
insertIndex -= 1
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
unwatched = function(node)
|
||||||
if bit32.band(node.flags, MUTABLE) == 0 then
|
if bit32.band(node.flags, MUTABLE) == 0 then
|
||||||
effectScopeOper(node :: EffectScope)()
|
effectScopeOper(node :: EffectScope)()
|
||||||
elseif node.depsTail then
|
elseif node.depsTail then
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
local function noYieldHandler(thread: thread, ok: boolean, ...)
|
local function noYieldHandler(thread: thread, ok: boolean, ...): (boolean, ...any)
|
||||||
if not ok then
|
if ok and coroutine.status(thread) == "dead" then
|
||||||
return false, debug.traceback(thread, tostring((...)))
|
|
||||||
elseif coroutine.status(thread) ~= "dead" then
|
|
||||||
return false, debug.traceback(thread, "Attempted to yield!")
|
|
||||||
else
|
|
||||||
return true, ...
|
return true, ...
|
||||||
|
elseif not ok then
|
||||||
|
return false, debug.traceback(thread, tostring((...)))
|
||||||
|
else
|
||||||
|
return false, debug.traceback(thread, "Attempted to yield!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
wally = "UpliftGames/wally@0.3.2"
|
wally = "UpliftGames/wally@0.3.2"
|
||||||
rojo = "rojo-rbx/rojo@7.5.1"
|
rojo = "rojo-rbx/rojo@7.6.0"
|
||||||
stylua = "johnnymorganz/stylua@2.2.0"
|
stylua = "johnnymorganz/stylua@2.3.0"
|
||||||
selene = "Kampfkarren/selene@0.29.0"
|
selene = "Kampfkarren/selene@0.29.0"
|
||||||
luau-lsp = "johnnymorganz/luau-lsp@1.53.5"
|
luau-lsp = "johnnymorganz/luau-lsp@1.55.0"
|
||||||
lune = "lune-org/lune@0.10.2"
|
lune = "lune-org/lune@0.10.4"
|
||||||
lute = "luau-lang/lute@0.1.0-nightly.20250923"
|
lute = "luau-lang/lute@0.1.0-nightly.20250923"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue