feat: allow conditional dependencies (#4)

* feat: allow conditional dependencies

* docs: remove conditional deps warnings

* fix: allow nested captures

* docs: add dev flag

* fix: `try()` uses pcall if passed `finally()`

* docs: interval is optional

* docs: update readme

* docs: add client/server tags
This commit is contained in:
richard 2024-06-11 17:13:43 -07:00 • committed by GitHub
parent fbc86d22c6
commit a90d131fed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 290 additions and 98 deletions

View file

@ -7,34 +7,23 @@ local atom = require(Package.atom)
local computed = require(Package.computed)
local function parameterGenerator()
return atom({ count = 0 })
return atom(0)
end
local benchmarks = {
atom = function(_, source: any)
for _ = 1, 500 do
source({ count = math.random(1, 2) })
source(math.random(1, 2))
end
end,
computed = function(_, source: any)
computed(function()
return { count = 2 * source().count }
return 2 * source()
end)
for _ = 1, 500 do
source({ count = math.random(1, 2) })
end
end,
computed_nested = function(_, source: any)
local double = computed(function()
return { count = 2 * source().count }
end)
computed(function()
return { count = 2 * double().count }
end)
for _ = 1, 500 do
source({ count = math.random(1, 2) })
source(math.random(1, 2))
end
end,
}

View file

@ -2,21 +2,47 @@ local atom = require(script.Parent.Parent.atom)
local store = require(script.Parent.Parent.store)
return function()
it("gracefully handles yielding", function()
expect(function()
store.capture(function()
task.wait(10)
return 1
end)
end).to.throw()
end)
it("captures atoms from a function", function()
local a, b, c = atom(1), atom(1), atom(2)
local captured, result = store.capture(function()
local deps, result = store.capture(function()
return a() + b()
end)
expect(captured[a]).to.be.ok()
expect(captured[b]).to.be.ok()
expect(captured[c]).to.never.be.ok()
expect(deps[a]).to.be.ok()
expect(deps[b]).to.be.ok()
expect(deps[c]).to.never.be.ok()
expect(result).to.equal(2)
end)
it("captures an atom by itself", function()
local a = atom(1)
local captured, result = store.capture(a)
expect(captured[a]).to.be.ok()
local deps, result = store.capture(a)
expect(deps[a]).to.be.ok()
expect(result).to.equal(1)
end)
it("allows nested captures", function()
local a = atom(1)
local b = atom(1)
local inner
local outer = store.capture(function()
inner = store.capture(function()
return b()
end)
return a()
end)
assert(outer[a], "outer did not capture a")
assert(outer[b], "outer did not capture b")
assert(inner[a] == nil, "inner captured a")
assert(inner[b], "inner did not capture b")
end)
end

View file

@ -68,4 +68,35 @@ return function()
assert(count(store.listeners[source]) == 0, "nested computed atom did not gc")
end)
it("tracks conditional dependencies", function()
local condition = atom(false)
local a = atom(0)
local b = atom(1)
local reruns = 0
local current
-- assign variable to avoid gc
local _ = computed(function()
reruns += 1
current = if condition() then a() else b()
return current
end)
expect(reruns).to.equal(1)
expect(current).to.equal(1)
condition(true)
expect(reruns).to.equal(2)
expect(current).to.equal(0)
a(1)
b(2)
expect(reruns).to.equal(3)
expect(current).to.equal(1)
condition(false)
expect(reruns).to.equal(4)
expect(current).to.equal(2)
end)
end

View file

@ -43,4 +43,33 @@ return function()
unsubscribe()
expect(cleanups).to.equal(2)
end)
it("tracks conditional dependencies", function()
local condition = atom(false)
local a = atom(0)
local b = atom(1)
local reruns = 0
local current
effect(function()
reruns += 1
current = if condition() then a() else b()
end)
expect(reruns).to.equal(1)
expect(current).to.equal(1)
condition(true)
expect(reruns).to.equal(2)
expect(current).to.equal(0)
a(1)
b(2)
expect(reruns).to.equal(3)
expect(current).to.equal(1)
condition(false)
expect(reruns).to.equal(4)
expect(current).to.equal(2)
end)
end

View file

@ -4,12 +4,12 @@ local store = require(script.Parent.Parent.store)
return function()
it("calls an atom without capturing it", function()
local a, b, c = atom(1), atom(1), atom(1)
local captured, result = store.capture(function()
local deps, result = store.capture(function()
return a() + store.peek(b) + c()
end)
expect(captured[a]).to.be.ok()
expect(captured[b]).to.never.be.ok()
expect(captured[c]).to.be.ok()
expect(deps[a]).to.be.ok()
expect(deps[b]).to.never.be.ok()
expect(deps[c]).to.be.ok()
expect(result).to.equal(3)
end)

View file

@ -67,4 +67,32 @@ return function()
assert(count(store.listeners[source]) == 0, "computed atom did not collect")
end)
it("tracks conditional dependencies", function()
local condition = atom(false)
local a = atom(0)
local b = atom(1)
local reruns = 0
local current
subscribe(function()
return if condition() then a() else b()
end, function(state)
reruns += 1
current = state
end)
condition(true)
expect(reruns).to.equal(1)
expect(current).to.equal(0)
a(1)
b(2)
expect(reruns).to.equal(2)
expect(current).to.equal(1)
condition(false)
expect(reruns).to.equal(3)
expect(current).to.equal(2)
end)
end