fix: always return pending value in signal setter

This commit is contained in:
littensy 2026-05-05 21:23:45 -07:00
parent 6499a0064f
commit ccdd539b28
2 changed files with 62 additions and 45 deletions

View file

@ -226,11 +226,9 @@ local function run(effect: EffectNode)
purgeDeps(effect) purgeDeps(effect)
if success then if success and result then
if type(result) == "function" then
table.insert(effect.cleanups, wrapUserSpace(result)) table.insert(effect.cleanups, wrapUserSpace(result))
end elseif not success then
else
error(result, 0) error(result, 0)
end end
@ -442,6 +440,32 @@ local function signal<T>(initialValue: T, equals: Equals<T>?): (Getter<T>, Sette
deepFreeze(initialValue) deepFreeze(initialValue)
end end
local function signalGetter(): T
-- Check if the signal has a pending update that needs to be applied
if bit32.btest(node.flags, DIRTY) then
if updateSignal(node) then
local subs = node.subs
if subs then
shallowPropagate(subs)
end
end
end
-- Register this signal as a dependency of the first subscriber in the
-- chain that is a computed signal or an effect
local sub = activeSub
while sub do
if bit32.btest(sub.flags, bit32.bor(MUTABLE, WATCHING)) then
link(node, sub, cycle)
break
end
local subs = sub.subs
sub = if subs then subs.sub else nil
end
return node.currentValue
end
local function signalSetter(update: Update<T>): T local function signalSetter(update: Update<T>): T
local value: T local value: T
@ -475,37 +499,9 @@ local function signal<T>(initialValue: T, equals: Equals<T>?): (Getter<T>, Sette
propagate(subs) propagate(subs)
flush() flush()
end end
return value
end end
return node.currentValue return node.pendingValue
end
local function signalGetter(): T
-- Check if the signal has a pending update that needs to be applied
if bit32.btest(node.flags, DIRTY) then
if updateSignal(node) then
local subs = node.subs
if subs then
shallowPropagate(subs)
end
end
end
-- Register this signal as a dependency of the first subscriber in the
-- chain that is a computed signal or an effect
local sub = activeSub
while sub do
if bit32.btest(sub.flags, bit32.bor(MUTABLE, WATCHING)) then
link(node, sub, cycle)
break
end
local subs = sub.subs
sub = if subs then subs.sub else nil
end
return node.currentValue
end end
return signalGetter, signalSetter return signalGetter, signalSetter
@ -522,9 +518,11 @@ end
]] ]]
local function atom<T>(initialValue: T, equals: Equals<T>?): Atom<T> local function atom<T>(initialValue: T, equals: Equals<T>?): Atom<T>
local get, set = signal(initialValue, equals) local get, set = signal(initialValue, equals)
local function atomOper(...) local function atomOper(...)
return if select("#", ...) == 0 then get() else set((...)) return if select("#", ...) == 0 then get() else set((...))
end end
return atomOper return atomOper
end end
@ -628,16 +626,15 @@ local function effect(fn: () -> ...Cleanup?): Cleanup
local success, result = pcall(node.fn) local success, result = pcall(node.fn)
activeSub = prevSub activeSub = prevSub
if success then if success and result then
if type(result) == "function" then
table.insert(node.cleanups, wrapUserSpace(result)) table.insert(node.cleanups, wrapUserSpace(result))
end elseif not success then
endBatch()
else
endBatch() endBatch()
error(result, 2) error(result, 2)
end end
endBatch()
return function() return function()
stopEffect(node) stopEffect(node)
end end
@ -667,11 +664,9 @@ local function effectScope(fn: () -> ...Cleanup?, detached: boolean?): Cleanup
local success, result = pcall(wrapUserSpace(fn)) local success, result = pcall(wrapUserSpace(fn))
activeSub = prevSub activeSub = prevSub
if success then if success and result then
if type(result) == "function" then
table.insert(node.cleanups, wrapUserSpace(result)) table.insert(node.cleanups, wrapUserSpace(result))
end elseif not success then
else
error(result, 2) error(result, 2)
end end
@ -750,9 +745,11 @@ local function untracked<Args..., Results...>(fn: (Args...) -> Results..., ...:
local prevSub = setActiveSub(nil) local prevSub = setActiveSub(nil)
local results: { any } = { pcall(wrapUserSpace(fn), ...) } local results: { any } = { pcall(wrapUserSpace(fn), ...) }
activeSub = prevSub activeSub = prevSub
if not results[1] then if not results[1] then
error(results[2], 2) error(results[2], 2)
end end
return unpack(results, 2) return unpack(results, 2)
else else
return fn(...) return fn(...)
@ -772,9 +769,11 @@ local function batch<Args..., Results...>(fn: (Args...) -> Results..., ...: Args
startBatch() startBatch()
local results: { any } = { pcall(wrapUserSpace(fn), ...) } local results: { any } = { pcall(wrapUserSpace(fn), ...) }
endBatch() endBatch()
if not results[1] then if not results[1] then
error(results[2], 2) error(results[2], 2)
end end
return unpack(results, 2) return unpack(results, 2)
end end

View file

@ -46,4 +46,22 @@ test("should not freeze values with metatables", function()
) )
end) end)
test("should setter return same value as getter even if equal", function()
local src, setSrc = signal(0, function()
return true
end)
assert(setSrc(1) == src(), `setSrc(1) expected 0, got {setSrc(1)}`)
end)
test("should setter return latest value", function()
local src, setSrc = signal(0)
effect(function()
if src() == 1 then
setSrc(2)
end
end)
assert(setSrc(1) == 2, `setSrc(1) expected 2, got {setSrc(1)}`)
assert(src() == 2, `src() expected 2, got {src()}`)
end)
return {} return {}