mirror of
https://github.com/littensy/charm.git
synced 2026-10-22 21:01:38 +00:00
perf: remove clearPending inline operation
This commit is contained in:
parent
71d155e947
commit
b00e6760c0
1 changed files with 48 additions and 50 deletions
|
|
@ -17,15 +17,10 @@ local shallowPropagate = system.shallowPropagate
|
||||||
local createReactiveSystem = system.createReactiveSystem
|
local createReactiveSystem = system.createReactiveSystem
|
||||||
|
|
||||||
export type Getter<T> = () -> T
|
export type Getter<T> = () -> T
|
||||||
|
|
||||||
export type Setter<T> = (update: Update<T>) -> T
|
export type Setter<T> = (update: Update<T>) -> T
|
||||||
|
|
||||||
export type Atom<T = any> = (...Update<T>) -> T
|
export type Atom<T = any> = (...Update<T>) -> T
|
||||||
|
|
||||||
export type Update<T> = ((current: T) -> T) | T
|
export type Update<T> = ((current: T) -> T) | T
|
||||||
|
|
||||||
export type Equals<T> = (current: T, incoming: T) -> boolean
|
export type Equals<T> = (current: T, incoming: T) -> boolean
|
||||||
|
|
||||||
export type Cleanup = () -> ()
|
export type Cleanup = () -> ()
|
||||||
|
|
||||||
type EffectScopeNode = ReactiveNode & {
|
type EffectScopeNode = ReactiveNode & {
|
||||||
|
|
@ -63,7 +58,7 @@ local PENDING = 0b100000 -- Might be dirty, pending verification
|
||||||
local cycle = 0
|
local cycle = 0
|
||||||
-- Effects runs are queued to prevent redundant executions when multiple
|
-- Effects runs are queued to prevent redundant executions when multiple
|
||||||
-- signals update in the same batch.
|
-- signals update in the same batch.
|
||||||
local queued: { EffectNode? } = {}
|
local queued: { EffectNode } = {}
|
||||||
local queuedLength = 0
|
local queuedLength = 0
|
||||||
local notifyIndex = 0
|
local notifyIndex = 0
|
||||||
-- Batch operations group multiple updates together, deferring effect runs
|
-- Batch operations group multiple updates together, deferring effect runs
|
||||||
|
|
@ -216,6 +211,7 @@ local function run(effect: EffectNode)
|
||||||
then
|
then
|
||||||
runCleanups(effect.cleanups)
|
runCleanups(effect.cleanups)
|
||||||
|
|
||||||
|
-- Cancel the run if the effect was disposed during cleanup
|
||||||
if effect.flags == NONE then
|
if effect.flags == NONE then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -236,6 +232,8 @@ local function run(effect: EffectNode)
|
||||||
table.insert(effect.cleanups, wrapUserSpace(result))
|
table.insert(effect.cleanups, wrapUserSpace(result))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- If the effect was disposed during execution but before a cleanup was
|
||||||
|
-- was registered, run the cleanups that would have been missed
|
||||||
if effect.flags == NONE then
|
if effect.flags == NONE then
|
||||||
runCleanups(effect.cleanups)
|
runCleanups(effect.cleanups)
|
||||||
end
|
end
|
||||||
|
|
@ -257,18 +255,18 @@ local function flush()
|
||||||
|
|
||||||
local success, err = pcall(function()
|
local success, err = pcall(function()
|
||||||
while notifyIndex < queuedLength do
|
while notifyIndex < queuedLength do
|
||||||
|
notifyIndex += 1
|
||||||
local effect = queued[notifyIndex] :: EffectNode
|
local effect = queued[notifyIndex] :: EffectNode
|
||||||
queued[notifyIndex] = nil
|
queued[notifyIndex] = nil
|
||||||
notifyIndex += 1
|
|
||||||
run(effect)
|
run(effect)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Skip over any remaining queued effects that were not run due to an error
|
-- Skip over any remaining queued effects that were not run due to an error
|
||||||
while notifyIndex < queuedLength do
|
while notifyIndex < queuedLength do
|
||||||
|
notifyIndex += 1
|
||||||
local effect = queued[notifyIndex] :: EffectNode
|
local effect = queued[notifyIndex] :: EffectNode
|
||||||
queued[notifyIndex] = nil
|
queued[notifyIndex] = nil
|
||||||
notifyIndex += 1
|
|
||||||
effect.flags = bit32.bor(effect.flags, bit32.bor(WATCHING, RECURSED))
|
effect.flags = bit32.bor(effect.flags, bit32.bor(WATCHING, RECURSED))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -337,17 +335,6 @@ local function updateSignal(signal: SignalNode): boolean
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
|
||||||
Clears the pending flag from a subscriber. Used inside of if conditions to
|
|
||||||
inline the operation.
|
|
||||||
|
|
||||||
@param sub Effect or computed node
|
|
||||||
@param subFlags Subscriber's current flags
|
|
||||||
]]
|
|
||||||
local function clearPending(sub: ReactiveNode, subFlags: ReactiveFlags)
|
|
||||||
sub.flags = bit32.band(subFlags, bit32.bnot(PENDING))
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Clears all dependencies and subscribers from the effect node. After calling
|
Clears all dependencies and subscribers from the effect node. After calling
|
||||||
this, the node will no longer be subscribed to any dependencies and will be
|
this, the node will no longer be subscribed to any dependencies and will be
|
||||||
|
|
@ -359,6 +346,7 @@ local function stopEffect(effect: EffectScopeNode)
|
||||||
|
|
||||||
purgeDeps(effect)
|
purgeDeps(effect)
|
||||||
|
|
||||||
|
-- Unlink from the parent effect, if it exists
|
||||||
local sub = effect.subs
|
local sub = effect.subs
|
||||||
if sub then
|
if sub then
|
||||||
unlink(sub, effect)
|
unlink(sub, effect)
|
||||||
|
|
@ -383,15 +371,18 @@ end
|
||||||
correct order.
|
correct order.
|
||||||
]]
|
]]
|
||||||
local function notify(effect: ReactiveNode)
|
local function notify(effect: ReactiveNode)
|
||||||
local insertIndex = queuedLength
|
local startIndex = queuedLength + 1
|
||||||
local firstInsertedIndex = insertIndex
|
|
||||||
|
|
||||||
-- Collect inner effects in a chain
|
-- Collect inner effects in a chain
|
||||||
repeat
|
repeat
|
||||||
queued[insertIndex] = effect :: EffectNode
|
queuedLength += 1
|
||||||
insertIndex += 1
|
queued[queuedLength] = effect :: EffectNode
|
||||||
|
|
||||||
|
-- Mark as not watching to prevent duplicates in the same flush
|
||||||
effect.flags = bit32.band(effect.flags, bit32.bnot(WATCHING))
|
effect.flags = bit32.band(effect.flags, bit32.bnot(WATCHING))
|
||||||
|
|
||||||
|
-- Traverse to the parent effect, if it exists and is not queued, to
|
||||||
|
-- ensure it gets notified before the child effect
|
||||||
effect = (effect.subs and effect.subs.sub) :: EffectNode
|
effect = (effect.subs and effect.subs.sub) :: EffectNode
|
||||||
|
|
||||||
if not effect or bit32.band(effect.flags, WATCHING) == 0 then
|
if not effect or bit32.band(effect.flags, WATCHING) == 0 then
|
||||||
|
|
@ -399,17 +390,15 @@ local function notify(effect: ReactiveNode)
|
||||||
end
|
end
|
||||||
until false
|
until false
|
||||||
|
|
||||||
queuedLength = insertIndex
|
local endIndex = queuedLength
|
||||||
|
|
||||||
-- Reverse the collected effects to ensure outer effects run first
|
-- Reverse the collected effects since they were collected from child to
|
||||||
repeat
|
-- parent, but should be notified from parent to child
|
||||||
insertIndex -= 1
|
while startIndex < endIndex do
|
||||||
if firstInsertedIndex >= insertIndex then
|
queued[startIndex], queued[endIndex] = queued[endIndex], queued[startIndex]
|
||||||
break
|
startIndex += 1
|
||||||
|
endIndex -= 1
|
||||||
end
|
end
|
||||||
queued[firstInsertedIndex], queued[insertIndex] = queued[insertIndex], queued[firstInsertedIndex]
|
|
||||||
firstInsertedIndex += 1
|
|
||||||
until false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
@ -555,20 +544,24 @@ local function computed<T>(getter: (previousValue: T?) -> T): Getter<T>
|
||||||
local function computedOper(): T
|
local function computedOper(): T
|
||||||
local nodeFlags = node.flags
|
local nodeFlags = node.flags
|
||||||
|
|
||||||
if
|
if bit32.btest(nodeFlags, DIRTY) then
|
||||||
bit32.btest(nodeFlags, DIRTY)
|
|
||||||
or (
|
|
||||||
bit32.btest(nodeFlags, PENDING)
|
|
||||||
and (checkDirty(node.deps :: Link, node) or clearPending(node, nodeFlags))
|
|
||||||
)
|
|
||||||
then
|
|
||||||
if updateComputed(node) then
|
if updateComputed(node) then
|
||||||
-- Notify subscribers if the value changed
|
|
||||||
local subs = node.subs
|
local subs = node.subs
|
||||||
if subs then
|
if subs then
|
||||||
shallowPropagate(subs)
|
shallowPropagate(subs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif bit32.btest(nodeFlags, PENDING) then
|
||||||
|
if checkDirty(node.deps :: Link, node) then
|
||||||
|
if updateComputed(node) then
|
||||||
|
local subs = node.subs
|
||||||
|
if subs then
|
||||||
|
shallowPropagate(subs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
node.flags = bit32.band(nodeFlags, bit32.bnot(PENDING))
|
||||||
|
end
|
||||||
elseif nodeFlags == NONE then
|
elseif nodeFlags == NONE then
|
||||||
-- First access should initialize the computed value
|
-- First access should initialize the computed value
|
||||||
node.flags = bit32.bor(MUTABLE, RECURSED_CHECK)
|
node.flags = bit32.bor(MUTABLE, RECURSED_CHECK)
|
||||||
|
|
@ -590,7 +583,7 @@ local function computed<T>(getter: (previousValue: T?) -> T): Getter<T>
|
||||||
link(node, activeSub, cycle)
|
link(node, activeSub, cycle)
|
||||||
end
|
end
|
||||||
|
|
||||||
return node.value :: T
|
return node.value
|
||||||
end
|
end
|
||||||
|
|
||||||
return computedOper
|
return computedOper
|
||||||
|
|
@ -618,26 +611,30 @@ local function effect(fn: () -> ...Cleanup?): Cleanup
|
||||||
cleanups = {},
|
cleanups = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Inner effects are tracked as a dependency of the parent effect, so that
|
||||||
|
-- they can be automatically cleaned up when the parent effect re-runs, and
|
||||||
|
-- to ensure the correct execution order when nested effects are notified
|
||||||
if activeSub and (flags.trackInnerEffects or activeSub.flags == NONE) then
|
if activeSub and (flags.trackInnerEffects or activeSub.flags == NONE) then
|
||||||
link(node, activeSub, 0)
|
link(node, activeSub, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Start a batch so that any signal updates triggered during the effect
|
-- Start a batch so that the effect can register a cleanup function before
|
||||||
-- execution are queued after the effect registers a cleanup function
|
-- it can receive notifications, ensuring the cleanup always runs
|
||||||
startBatch()
|
startBatch()
|
||||||
|
|
||||||
local prevSub = setActiveSub(node)
|
local prevSub = setActiveSub(node)
|
||||||
local success, result = pcall(node.fn)
|
local success, result = pcall(node.fn)
|
||||||
activeSub = prevSub
|
activeSub = prevSub
|
||||||
|
|
||||||
if not success then
|
if success then
|
||||||
endBatch()
|
if result then
|
||||||
error(result, 2)
|
|
||||||
elseif result then
|
|
||||||
table.insert(node.cleanups, wrapUserSpace(result))
|
table.insert(node.cleanups, wrapUserSpace(result))
|
||||||
end
|
end
|
||||||
|
|
||||||
endBatch()
|
endBatch()
|
||||||
|
else
|
||||||
|
endBatch()
|
||||||
|
error(result, 2)
|
||||||
|
end
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
stopEffect(node)
|
stopEffect(node)
|
||||||
|
|
@ -659,6 +656,7 @@ local function effectScope(fn: () -> ...Cleanup?, detached: boolean?): Cleanup
|
||||||
cleanups = {},
|
cleanups = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Inner effect scopes are tracked as a dependency of the parent effect
|
||||||
if activeSub and not detached then
|
if activeSub and not detached then
|
||||||
link(node, activeSub, 0)
|
link(node, activeSub, 0)
|
||||||
end
|
end
|
||||||
|
|
@ -948,7 +946,7 @@ local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, transform: (
|
||||||
end
|
end
|
||||||
|
|
||||||
local nextValueOut, keyOutOrNil = transform(nextValueIn, keyIn)
|
local nextValueOut, keyOutOrNil = transform(nextValueIn, keyIn)
|
||||||
local nextKeyOut: KO = if keyOutOrNil ~= nil then keyOutOrNil else keyIn :: any
|
local nextKeyOut: KO = if keyOutOrNil ~= nil then keyOutOrNil else keyIn
|
||||||
local prevKeyOut = keyMap[keyIn]
|
local prevKeyOut = keyMap[keyIn]
|
||||||
|
|
||||||
if prevKeyOut ~= nil and prevKeyOut ~= nextKeyOut then
|
if prevKeyOut ~= nil and prevKeyOut ~= nextKeyOut then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue