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