mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
refactor: reduce type casting, housekeeping
This commit is contained in:
parent
77c98ca94a
commit
a8051257e5
5 changed files with 57 additions and 52 deletions
|
|
@ -52,12 +52,14 @@ local startTracking: (sub: ReactiveNode) -> ()
|
|||
local shallowPropagate: (link: Link) -> ()
|
||||
|
||||
local batchDepth = 0
|
||||
local queuedEffects: { Effect | EffectScope? } = {}
|
||||
|
||||
local queuedEffects: { Effect } = {}
|
||||
local queuedEffectsLength = 0
|
||||
local notifyIndex = 0
|
||||
local activeSubTracking = true
|
||||
local activeSub: ReactiveNode?
|
||||
|
||||
local activeScope: Effect | EffectScope?
|
||||
local activeSub: ReactiveNode?
|
||||
local activeSubWatching = true
|
||||
|
||||
local function getCurrentSub(): ReactiveNode?
|
||||
return activeSub
|
||||
|
|
@ -79,16 +81,16 @@ local function setCurrentScope(scope: Effect | EffectScope?)
|
|||
return prevScope
|
||||
end
|
||||
|
||||
local function setCurrentTracking(tracking: boolean): boolean
|
||||
local prevSubTracking = activeSubTracking
|
||||
activeSubTracking = tracking
|
||||
return prevSubTracking
|
||||
local function setCurrentWatching(watching: boolean): boolean
|
||||
local prevSubWatching = activeSubWatching
|
||||
activeSubWatching = watching
|
||||
return prevSubWatching
|
||||
end
|
||||
|
||||
local function runCleanups(effect: EffectScope)
|
||||
for _, cleanup in effect.cleanups do
|
||||
local prev = setCurrentSub(nil)
|
||||
local success, result: unknown = (if config.strict then noYield else pcall)(cleanup)
|
||||
local success, result: any = (config.strict and noYield or pcall)(cleanup)
|
||||
setCurrentSub(prev)
|
||||
if not success then
|
||||
error(result)
|
||||
|
|
@ -98,7 +100,7 @@ local function runCleanups(effect: EffectScope)
|
|||
end
|
||||
|
||||
local function onCleanup(cleanup: () -> (), failSilently: boolean?)
|
||||
if activeSub then
|
||||
if activeSub and (activeSub :: Effect).cleanups then
|
||||
table.insert((activeSub :: Effect).cleanups, cleanup)
|
||||
elseif activeScope then
|
||||
table.insert(activeScope.cleanups, cleanup)
|
||||
|
|
@ -107,16 +109,16 @@ local function onCleanup(cleanup: () -> (), failSilently: boolean?)
|
|||
end
|
||||
end
|
||||
|
||||
local function run(effect: Effect | EffectScope, flags: ReactiveFlags): ()
|
||||
local function run(effect: Effect, flags: ReactiveFlags): ()
|
||||
if bit32.btest(flags, DIRTY) or (bit32.btest(flags, PENDING) and checkDirty(effect.deps :: Link, effect)) then
|
||||
runCleanups(effect)
|
||||
local prev = setCurrentSub(effect)
|
||||
local prevTracking = setCurrentTracking(true)
|
||||
local prevTracking = setCurrentWatching(true)
|
||||
startTracking(effect)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)((effect :: Effect).fn)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(effect.fn)
|
||||
setCurrentSub(prev)
|
||||
endTracking(effect)
|
||||
setCurrentTracking(prevTracking)
|
||||
setCurrentWatching(prevTracking)
|
||||
if not success then
|
||||
error(result)
|
||||
elseif result then
|
||||
|
|
@ -124,7 +126,7 @@ local function run(effect: Effect | EffectScope, flags: ReactiveFlags): ()
|
|||
end
|
||||
return
|
||||
elseif bit32.btest(flags, PENDING) then
|
||||
(effect :: Effect).flags = bit32.band(flags, bit32.bnot(PENDING))
|
||||
effect.flags = bit32.band(flags, bit32.bnot(PENDING))
|
||||
end
|
||||
local link = effect.deps
|
||||
while link do
|
||||
|
|
@ -142,7 +144,7 @@ end
|
|||
local function flush(): ()
|
||||
while notifyIndex < queuedEffectsLength do
|
||||
notifyIndex += 1
|
||||
local effect = queuedEffects[notifyIndex] :: Effect
|
||||
local effect = queuedEffects[notifyIndex]
|
||||
queuedEffects[notifyIndex] = nil
|
||||
effect.flags = bit32.band(effect.flags, bit32.bnot(QUEUED))
|
||||
run(effect, effect.flags)
|
||||
|
|
@ -168,13 +170,13 @@ end
|
|||
|
||||
local function updateComputed(computed: Computed): boolean
|
||||
local prevSub = setCurrentSub(computed)
|
||||
local prevTracking = setCurrentTracking(true)
|
||||
local prevWatching = setCurrentWatching(true)
|
||||
startTracking(computed)
|
||||
local oldValue = computed.value
|
||||
local success, result = (if config.strict then noYield else pcall)(computed.getter, oldValue)
|
||||
local success, result = (config.strict and noYield or pcall)(computed.getter, oldValue)
|
||||
setCurrentSub(prevSub)
|
||||
endTracking(computed)
|
||||
setCurrentTracking(prevTracking)
|
||||
setCurrentWatching(prevWatching)
|
||||
if not success then
|
||||
error(result)
|
||||
end
|
||||
|
|
@ -207,7 +209,7 @@ local function readAtom<T>(atom: Atom<T>): T
|
|||
end
|
||||
end
|
||||
end
|
||||
if activeSub and activeSubTracking then
|
||||
if activeSub and activeSubWatching then
|
||||
link(atom, activeSub)
|
||||
end
|
||||
return value
|
||||
|
|
@ -215,7 +217,7 @@ end
|
|||
|
||||
local function writeAtom<T>(atom: Atom<T>, oldValue: T, newValue: T): T
|
||||
local equals = atom.equals
|
||||
if if equals then not equals(oldValue, newValue :: T) else oldValue ~= newValue then
|
||||
if if equals then not equals(oldValue, newValue) else oldValue ~= newValue then
|
||||
if config.frozen then
|
||||
deepFreeze(newValue)
|
||||
end
|
||||
|
|
@ -242,7 +244,7 @@ local function atomGetterSetter<T>(atom: Atom<T>): (...((T) -> T) | T) -> T
|
|||
local newValue = ...
|
||||
if type(newValue) == "function" then
|
||||
local prev = setCurrentSub(nil)
|
||||
local success, result = (if config.strict then noYield else pcall)(newValue, oldValue)
|
||||
local success, result = (config.strict and noYield or pcall)(newValue, oldValue)
|
||||
setCurrentSub(prev)
|
||||
if not success then
|
||||
error(result)
|
||||
|
|
@ -268,10 +270,8 @@ local function computedGetter<T>(computed: Computed<T>): () -> T
|
|||
elseif bit32.btest(flags, PENDING) then
|
||||
computed.flags = bit32.band(flags, bit32.bnot(PENDING))
|
||||
end
|
||||
if activeSub then
|
||||
if activeSubTracking then
|
||||
link(computed, activeSub)
|
||||
end
|
||||
if activeSub and activeSubWatching then
|
||||
link(computed, activeSub)
|
||||
elseif activeScope then
|
||||
link(computed, activeScope)
|
||||
end
|
||||
|
|
@ -279,7 +279,7 @@ local function computedGetter<T>(computed: Computed<T>): () -> T
|
|||
end
|
||||
end
|
||||
|
||||
local function effectStop(self: Effect | EffectScope): () -> ()
|
||||
local function effectStop(self: EffectScope): () -> ()
|
||||
return function()
|
||||
local dep = self.deps
|
||||
while dep do
|
||||
|
|
@ -289,38 +289,38 @@ local function effectStop(self: Effect | EffectScope): () -> ()
|
|||
if sub then
|
||||
unlink(sub)
|
||||
end
|
||||
(self :: Effect).flags = NONE
|
||||
self.flags = NONE
|
||||
runCleanups(self)
|
||||
end
|
||||
end
|
||||
|
||||
local function update(atom: Atom | Computed): boolean
|
||||
local function update(atom: ReactiveNode): boolean
|
||||
if (atom :: Computed).getter then
|
||||
return updateComputed(atom :: Computed)
|
||||
else
|
||||
return updateAtom(atom :: Atom, atom.value)
|
||||
return updateAtom(atom :: Atom, (atom :: Atom).value)
|
||||
end
|
||||
end
|
||||
|
||||
local function notify(effect: Effect | EffectScope)
|
||||
local function notify(effect: ReactiveNode)
|
||||
local flags = effect.flags
|
||||
if not bit32.btest(flags, QUEUED) then
|
||||
(effect :: Effect).flags = bit32.bor(flags, QUEUED)
|
||||
effect.flags = bit32.bor(flags, QUEUED)
|
||||
local subs = effect.subs
|
||||
if subs then
|
||||
notify(subs.sub :: Effect)
|
||||
else
|
||||
queuedEffectsLength += 1
|
||||
queuedEffects[queuedEffectsLength] = effect
|
||||
queuedEffects[queuedEffectsLength] = effect :: Effect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function unwatched(node: Atom | Computed | Effect | EffectScope)
|
||||
local function unwatched(node: ReactiveNode)
|
||||
if (node :: Computed).getter then
|
||||
local toRemove = node.deps
|
||||
if toRemove then
|
||||
(node :: Computed).flags = bit32.bor(MUTABLE, DIRTY)
|
||||
node.flags = bit32.bor(MUTABLE, DIRTY)
|
||||
repeat
|
||||
toRemove = unlink(toRemove, node)
|
||||
until not toRemove
|
||||
|
|
@ -362,10 +362,10 @@ local function effect(fn: () -> ...(() -> ())?): () -> ()
|
|||
link(self, activeScope)
|
||||
end
|
||||
local prev = setCurrentSub(self)
|
||||
local prevTracking = setCurrentTracking(true)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)(fn)
|
||||
local prevWatching = setCurrentWatching(true)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(fn)
|
||||
setCurrentSub(prev)
|
||||
setCurrentTracking(prevTracking)
|
||||
setCurrentWatching(prevWatching)
|
||||
if not success then
|
||||
error(result)
|
||||
elseif result then
|
||||
|
|
@ -384,7 +384,7 @@ local function effectScope(fn: () -> ...(() -> ())?, detached: boolean?): () ->
|
|||
end
|
||||
local prevSub = setCurrentSub(nil)
|
||||
local prevScope = setCurrentScope(self)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)(fn)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(fn)
|
||||
setCurrentScope(prevScope)
|
||||
setCurrentSub(prevSub)
|
||||
if not success then
|
||||
|
|
@ -396,9 +396,9 @@ local function effectScope(fn: () -> ...(() -> ())?, detached: boolean?): () ->
|
|||
end
|
||||
|
||||
local reactiveSystem = createReactiveSystem({
|
||||
update = update :: (ReactiveNode) -> boolean,
|
||||
notify = notify :: (ReactiveNode) -> (),
|
||||
unwatched = unwatched :: (ReactiveNode) -> (),
|
||||
update = update,
|
||||
notify = notify,
|
||||
unwatched = unwatched,
|
||||
})
|
||||
|
||||
link = reactiveSystem.link
|
||||
|
|
@ -416,7 +416,7 @@ return {
|
|||
setCurrentSub = setCurrentSub,
|
||||
getCurrentScope = getCurrentScope,
|
||||
setCurrentScope = setCurrentScope,
|
||||
setCurrentTracking = setCurrentTracking,
|
||||
setCurrentWatching = setCurrentWatching,
|
||||
onCleanup = onCleanup,
|
||||
getBatchDepth = getBatchDepth,
|
||||
startBatch = startBatch,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ local isO2 = inlineTest() ~= "inlineTest"
|
|||
|
||||
return {
|
||||
--- Enforces synchronous, non-yielding behavior in atoms and effects.
|
||||
--- Enabled if the optimize level is below 2, which is the case in studio.
|
||||
--- Also enables state validation in Charm Sync to catch sync errors early.
|
||||
--- Enabled if the optimization level is below 2, which is true in studio.
|
||||
strict = not isO2,
|
||||
--- Enforces immutability of atoms by deep-freezing table values. Disabled
|
||||
--- by default, but can be enabled to detect accidental mutations.
|
||||
frozen = false,
|
||||
--- Enforces immutability of atoms by deep-freezing table values. Enabled
|
||||
--- if the optimization level is below 2, which is true in studio.
|
||||
frozen = not isO2,
|
||||
}
|
||||
|
|
|
|||
4
packages/charm/src/index.d.ts
vendored
4
packages/charm/src/index.d.ts
vendored
|
|
@ -57,8 +57,6 @@ export function observe<V, K extends Key>(
|
|||
observer: (value: V, key: K) => Cleanup | void,
|
||||
): Cleanup;
|
||||
|
||||
export function onCleanup(callback: Cleanup, failSilently?: boolean): void;
|
||||
|
||||
export function batched<Args extends any[], Result>(
|
||||
callback: (...args: Args) => Result,
|
||||
...args: Args
|
||||
|
|
@ -66,6 +64,8 @@ export function batched<Args extends any[], Result>(
|
|||
|
||||
export function untracked<Args extends any[], Result>(callback: (...args: Args) => Result, ...args: Args): Result;
|
||||
|
||||
export function onCleanup(callback: Cleanup, failSilently?: boolean): void;
|
||||
|
||||
/**
|
||||
* @deprecated Use `batched()` instead.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ local function mapped<KI, VI, KO, VO>(selector: () -> { [KI]: VI }, mapper: (ite
|
|||
if currentData[key] ~= nil then
|
||||
continue
|
||||
end
|
||||
|
||||
local cachedKey = keyCache[key]
|
||||
|
||||
if cachedKey then
|
||||
currentOutput = if currentOutput == previousOutput then table.clone(currentOutput) else currentOutput
|
||||
currentOutput[cachedKey] = nil
|
||||
|
|
@ -44,7 +46,9 @@ local function mapped<KI, VI, KO, VO>(selector: () -> { [KI]: VI }, mapper: (ite
|
|||
if value == previousData[key] then
|
||||
continue
|
||||
end
|
||||
|
||||
local valueOut, keyOut = map(value, key)
|
||||
|
||||
if currentOutput[keyOut] ~= valueOut then
|
||||
currentOutput = if currentOutput == previousOutput then table.clone(currentOutput) else currentOutput
|
||||
currentOutput[keyOut] = valueOut
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
local core = require("./core")
|
||||
|
||||
local setCurrentTracking = core.setCurrentTracking
|
||||
local setCurrentWatching = core.setCurrentWatching
|
||||
|
||||
local function handleUntracked(prevTracking: boolean, ok: boolean, ...)
|
||||
setCurrentTracking(prevTracking)
|
||||
local function handleUntracked(prevWatching: boolean, ok: boolean, ...)
|
||||
setCurrentWatching(prevWatching)
|
||||
if ok then
|
||||
return ...
|
||||
else
|
||||
|
|
@ -12,7 +12,7 @@ local function handleUntracked(prevTracking: boolean, ok: boolean, ...)
|
|||
end
|
||||
|
||||
local function untracked<Args..., Results...>(callback: (Args...) -> Results..., ...: Args...): Results...
|
||||
return handleUntracked(setCurrentTracking(false), pcall(callback, ...))
|
||||
return handleUntracked(setCurrentWatching(false), pcall(callback, ...))
|
||||
end
|
||||
|
||||
return untracked
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue