perf: run observe fn in untracked

This commit is contained in:
littensy 2026-04-01 04:37:50 -07:00
parent e3e6209891
commit 27c7f5a6fd

View file

@ -178,8 +178,8 @@ end
@param cleanups List of cleanup functions @param cleanups List of cleanup functions
]] ]]
local function runCleanups(cleanups: { Cleanup }) local function runCleanups(cleanups: { Cleanup })
for key, cleanup in cleanups do for index, cleanup in cleanups do
cleanups[key] = nil cleanups[index] = nil
local prevSub = setActiveSub(nil) local prevSub = setActiveSub(nil)
local success, result: any = pcall(cleanup) local success, result: any = pcall(cleanup)
@ -418,10 +418,11 @@ end
Called when a node is not being watched by any subscribers. Cleans up the Called when a node is not being watched by any subscribers. Cleans up the
node's dependencies and performs the necessary cleanup operations: node's dependencies and performs the necessary cleanup operations:
- Computed signals: Unlink dependencies and mark as dirty for potential future updates - Computed signals: Unlink dependencies and mark as dirty for potential
future updates
- Effects/scopes: Perform full cleanup to free resources - Effects/scopes: Perform full cleanup to free resources
@param node Reactive node (computed or effect/scope) @param node Reactive node (computed, effect, or effect scope)
]] ]]
local function unwatched(node: ReactiveNode) local function unwatched(node: ReactiveNode)
if bit32.band(node.flags, MUTABLE) == 0 then if bit32.band(node.flags, MUTABLE) == 0 then
@ -649,7 +650,7 @@ end
signals created within it so that they can be disposed together. signals created within it so that they can be disposed together.
@param fn Function that creates effects within the scope @param fn Function that creates effects within the scope
@param detached If true, the scope will not be tracked by the parent effect/scope @param detached If true, the scope will not be tracked by the parent effect or scope
@return A cleanup function that stops all effects in the scope @return A cleanup function that stops all effects in the scope
@see https://github.com/littensy/charm?tab=readme-ov-file#effectscopecallback-detached @see https://github.com/littensy/charm?tab=readme-ov-file#effectscopecallback-detached
]] ]]
@ -719,7 +720,7 @@ end
set to `true`. set to `true`.
@param fn Function to run during cleanup @param fn Function to run during cleanup
@param failSilently If true, silences the warning when called outside an effect/scope @param failSilently If true, silences the warning when called outside an effect or scope
@see https://github.com/littensy/charm?tab=readme-ov-file#oncleanupcallback-failsilently @see https://github.com/littensy/charm?tab=readme-ov-file#oncleanupcallback-failsilently
]] ]]
local function onCleanup(fn: Cleanup, failSilently: boolean?) local function onCleanup(fn: Cleanup, failSilently: boolean?)
@ -788,7 +789,7 @@ end
@param fn Function to check @param fn Function to check
@return `true` if `fn` is a signal getter, computed signal, or atom function, `false` otherwise @return `true` if `fn` is a signal getter, computed signal, or atom function, `false` otherwise
]] ]]
local function isSignalOrComputed(fn: Getter<any>): boolean local function isSignal(fn: Getter<any>): boolean
local name = debug.info(fn, "n") local name = debug.info(fn, "n")
return name == "readSignal" or name == "computedOper" or name == "atomOper" return name == "readSignal" or name == "computedOper" or name == "atomOper"
end end
@ -804,7 +805,7 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback
]] ]]
local function listen<T>(getter: Getter<T>, fn: (state: T, lastState: T?) -> ()): Cleanup local function listen<T>(getter: Getter<T>, fn: (state: T, lastState: T?) -> ()): Cleanup
local tracked: Getter<T> = if isSignalOrComputed(getter) then getter else computed(getter) local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter)
local current: T local current: T
return effect(function() return effect(function()
@ -825,7 +826,7 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback
]] ]]
local function subscribe<T>(getter: Getter<T>, fn: (state: T, lastState: T) -> ()): Cleanup local function subscribe<T>(getter: Getter<T>, fn: (state: T, lastState: T) -> ()): Cleanup
local tracked: Getter<T> = if isSignalOrComputed(getter) then getter else computed(getter) local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter)
local current: T local current: T
local firstRun = true local firstRun = true
@ -841,9 +842,9 @@ local function subscribe<T>(getter: Getter<T>, fn: (state: T, lastState: T) -> (
end end
--[[ --[[
Calls the callback function for each unique key in the table returned by the Calls the callback function for each unique key in the table returned by
getter. The callback is called once for each key, and again when a new key the getter. The callback is called once for each key, and again when a new
is added. key is added.
The callback may return a cleanup function, which will be called when the The callback may return a cleanup function, which will be called when the
key is removed or the `observe()` effect is disposed. Effects created key is removed or the `observe()` effect is disposed. Effects created
@ -885,10 +886,9 @@ local function observe<K, V>(getter: Getter<{ [K]: V }>, fn: (item: V, key: K) -
return effectScope(function() return effectScope(function()
effect(function() effect(function()
-- Batch so that side effects run after all scopes are created. -- Wrap in untracked so that calling fn inside updateScopes does
-- This ensures that a scope creating or removing items doesn't -- not register dependencies to this effect
-- interfere with the current update cycle. untracked(updateScopes, getter())
batch(updateScopes, getter())
end) end)
return function() return function()
@ -930,8 +930,8 @@ local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI,
return valueOut, if keyOut == nil then key :: any else keyOut return valueOut, if keyOut == nil then key :: any else keyOut
end end
return computed(function(lastOutput) return computed(function(lastOutput: { [KO]: VO }?): { [KO]: VO }
local nextOutput = lastOutput or {} local nextOutput: { [KO]: VO } = lastOutput or {}
local prevInput = nextInput local prevInput = nextInput
nextInput = getter() nextInput = getter()