fix: allow dispose in observers

This commit is contained in:
littensy 2025-11-05 12:35:25 -08:00
parent d890f4fc2c
commit fdc598f6e6
2 changed files with 53 additions and 4 deletions

View file

@ -132,4 +132,40 @@ test("should handle self removal", function()
assert(table.concat(edits) == "+1+2+3+4-1-2-3-4", `bad edits: {table.concat(edits)}`) assert(table.concat(edits) == "+1+2+3+4-1-2-3-4", `bad edits: {table.concat(edits)}`)
end) end)
test("should allow disposal during addition", function()
local items, setItems = signal({})
local edits = {}
local dispose
dispose = observe(items, function(item)
if item == 1 then
dispose()
end
table.insert(edits, `+{item}`)
return function()
table.insert(edits, `-{item}`)
end
end)
setItems({ 1, 2, 3, 4 })
assert(table.concat(edits) == "+1-1", `bad edits: {table.concat(edits)}`)
end)
test("should allow disposal during removal", function()
local items, setItems = signal({ 1, 2, 3, 4 })
local edits = {}
local dispose
dispose = observe(items, function(item)
table.insert(edits, `+{item}`)
return function()
dispose()
table.insert(edits, `-{item}`)
end
end)
setItems({ 1, 2, 3, nil :: any, 5 })
assert(table.concat(edits) == "+1+2+3+4-1-2-3-4", `bad edits: {table.concat(edits)}`)
end)
return {} return {}

View file

@ -19,12 +19,16 @@ local recursive = core.recursive
--- @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback --- @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback
local function observe<K, V>(getter: () -> { [K]: V }, callback: (item: V, key: K) -> ...(() -> ())?): () -> () local function observe<K, V>(getter: () -> { [K]: V }, callback: (item: V, key: K) -> ...(() -> ())?): () -> ()
local innerScopes: { [K]: () -> () } = {} local innerScopes: { [K]: () -> () } = {}
local disposed = false
local function updateScopes(data: { [K]: V }) local function updateScopes(data: { [K]: V })
for key, dispose in innerScopes do for key, dispose in innerScopes do
if data[key] == nil then if data[key] == nil then
innerScopes[key] = nil innerScopes[key] = nil
dispose() dispose()
if disposed then
return
end
end end
end end
@ -32,9 +36,15 @@ local function observe<K, V>(getter: () -> { [K]: V }, callback: (item: V, key:
if innerScopes[key] == nil then if innerScopes[key] == nil then
-- Detach the inner scope from the parent effect to prevent -- Detach the inner scope from the parent effect to prevent
-- disposing when the effect re-runs. -- disposing when the effect re-runs.
innerScopes[key] = effectScope(function() local stopScope = effectScope(function()
return callback(value, key) return callback(value, key)
end, true) end, true)
if disposed then
return stopScope()
else
innerScopes[key] = stopScope
end
end end
end end
end end
@ -51,10 +61,13 @@ local function observe<K, V>(getter: () -> { [K]: V }, callback: (item: V, key:
end) end)
return function() return function()
for _, dispose in innerScopes do if not disposed then
dispose() disposed = true
for key, dispose in innerScopes do
innerScopes[key] = nil
dispose()
end
end end
table.clear(innerScopes)
end end
end) end)
end end