mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
refactor: rewrite untracked, update alien-signals
This commit is contained in:
parent
a8051257e5
commit
cb52ece4d3
15 changed files with 239 additions and 114 deletions
|
|
@ -4,6 +4,7 @@ local bench = require("./bench")
|
|||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local listen = Charm.listen
|
||||
|
||||
for _, w in { 10, 100 } do
|
||||
for _, h in { 10, 100 } do
|
||||
|
|
@ -25,3 +26,22 @@ for _, w in { 10, 100 } do
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
for _, w in { 10, 100 } do
|
||||
for _, h in { 10, 100 } do
|
||||
bench.run(`propagate (listen) {w}*{h}`, function()
|
||||
local src = atom(1)
|
||||
for _ = 1, w do
|
||||
local last = src
|
||||
for _ = 1, h do
|
||||
local prev = last
|
||||
last = computed(function()
|
||||
return prev() + 1
|
||||
end)
|
||||
end
|
||||
listen(last, function() end)
|
||||
end
|
||||
src(src() + 1)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"publish:npm": "pnpm publish -r --access public",
|
||||
"publish:wally": "pnpm -r --workspace-concurrency 1 publish:wally"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"devDependencies": {
|
||||
"@rbxts-js/react": "17.1.20-ts.1",
|
||||
"@rbxts-js/react-roblox": "17.1.20-ts.1",
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ type Atom<T> = Charm.Atom<T>
|
|||
|
||||
--- A payload that can be sent from the server to the client to synchronize
|
||||
--- state between the two.
|
||||
export type SyncPayload = {
|
||||
type: "init",
|
||||
data: { [string | number]: any },
|
||||
} | {
|
||||
type: "patch",
|
||||
data: { [string | number]: any },
|
||||
}
|
||||
export type SyncPayload =
|
||||
| {
|
||||
type: "init",
|
||||
data: { [string | number]: any },
|
||||
}
|
||||
| {
|
||||
type: "patch",
|
||||
data: { [string | number]: any },
|
||||
}
|
||||
|
||||
export type AtomMap = {
|
||||
[string | number]: Atom<any>,
|
||||
|
|
|
|||
|
|
@ -356,4 +356,25 @@ test("should run cleanups outside any evaluation context", function()
|
|||
assert(calls == 0, "should not run after b update")
|
||||
end)
|
||||
|
||||
test("should allow self cleanup", function()
|
||||
local calls = 0
|
||||
local cleanups = 0
|
||||
local a = atom(0)
|
||||
|
||||
effect(function(stopEffect)
|
||||
onCleanup(function()
|
||||
cleanups += 1
|
||||
end)
|
||||
stopEffect()
|
||||
a()
|
||||
calls += 1
|
||||
end)
|
||||
|
||||
assert(calls == 1, "should call effect once")
|
||||
assert(cleanups == 1, "should call cleanup once")
|
||||
a(1)
|
||||
assert(calls == 1, "should not call effect after a update")
|
||||
assert(cleanups == 1, "should not call cleanup after a update")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ local suite = require("../../../../test/suite")
|
|||
local atom = Charm.atom
|
||||
local listen = Charm.listen
|
||||
local effect = Charm.effect
|
||||
local onCleanup = Charm.onCleanup
|
||||
local test = suite.test
|
||||
|
||||
test("should listen to selector", function()
|
||||
|
|
@ -115,4 +116,24 @@ test("should skip redundant updates", function()
|
|||
assert(table.concat(order) == "evenoddeven", `bad order: {table.concat(order)}`)
|
||||
end)
|
||||
|
||||
test("should allow self cleanup", function()
|
||||
local calls = 0
|
||||
local cleanups = 0
|
||||
local a = atom(0)
|
||||
|
||||
listen(a, function(_, _, stopEffect)
|
||||
onCleanup(function()
|
||||
cleanups += 1
|
||||
end)
|
||||
stopEffect()
|
||||
calls += 1
|
||||
end)
|
||||
|
||||
assert(calls == 1, "should call effect once")
|
||||
assert(cleanups == 1, "should call cleanup once")
|
||||
a(1)
|
||||
assert(calls == 1, "should not call effect after a update")
|
||||
assert(cleanups == 1, "should not call cleanup after a update")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -61,4 +61,36 @@ test("should dispose scopes", function()
|
|||
assert(table.concat(runs) == "", `should not rerun: {table.concat(runs)}`)
|
||||
end)
|
||||
|
||||
test("should queue addition", function()
|
||||
local items = atom({ 1 })
|
||||
local edits = {}
|
||||
|
||||
observe(items, function(item)
|
||||
items({ 1, 2 })
|
||||
items({ 1, 2, 3, 4 })
|
||||
table.insert(edits, `+{item}`)
|
||||
return function()
|
||||
table.insert(edits, `-{item}`)
|
||||
end
|
||||
end)
|
||||
|
||||
assert(table.concat(edits) == "+1+2+3+4", `bad edits: {table.concat(edits)}`)
|
||||
end)
|
||||
|
||||
test("should queue removal", function()
|
||||
local items = atom({ 1, 2, 3, 4 })
|
||||
local edits = {}
|
||||
|
||||
observe(items, function(item)
|
||||
items({ 1, 2, 3 })
|
||||
items({ 1 })
|
||||
table.insert(edits, `+{item}`)
|
||||
return function()
|
||||
table.insert(edits, `-{item}`)
|
||||
end
|
||||
end)
|
||||
|
||||
assert(table.concat(edits) == "+1+2+3+4-2-3-4", `bad edits: {table.concat(edits)}`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -105,4 +105,26 @@ test("should block tracking inside computed atoms", function()
|
|||
assert(c() == 3, "c should be 3")
|
||||
end)
|
||||
|
||||
test("should track outside untracked", function()
|
||||
local a = atom(1)
|
||||
local b = atom(2)
|
||||
local c = atom(3)
|
||||
local spyCalls = 0
|
||||
|
||||
effect(function()
|
||||
a()
|
||||
untracked(c)
|
||||
b()
|
||||
spyCalls += 1
|
||||
end)
|
||||
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
a(10)
|
||||
assert(spyCalls == 2, "should be called twice")
|
||||
b(20)
|
||||
assert(spyCalls == 3, "should be called three times")
|
||||
c(30)
|
||||
assert(spyCalls == 3, "should not run on c change")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
local core = require("./core")
|
||||
local flags = require("./flags")
|
||||
local utils = require("./utils")
|
||||
|
||||
local startBatch = core.startBatch
|
||||
local endBatch = core.endBatch
|
||||
local noYield = utils.noYield
|
||||
|
||||
local function handleBatched(ok: boolean, ...)
|
||||
endBatch()
|
||||
|
|
@ -14,7 +17,7 @@ end
|
|||
|
||||
local function batched<Args..., Results...>(callback: (Args...) -> Results..., ...: Args...): Results...
|
||||
startBatch()
|
||||
return handleBatched(pcall(callback, ...))
|
||||
return handleBatched((if flags.strict then noYield else pcall)(callback, ...))
|
||||
end
|
||||
|
||||
return batched
|
||||
|
|
|
|||
|
|
@ -21,14 +21,16 @@ local WATCHING = 0b0000010
|
|||
local DIRTY = 0b0010000
|
||||
local PENDING = 0b0100000
|
||||
local QUEUED = 0b1000000
|
||||
local UNTRACKED = 0b10000000
|
||||
|
||||
type EffectScope = ReactiveNode & {
|
||||
cleanups: { () -> () },
|
||||
}
|
||||
|
||||
type Effect = ReactiveNode & {
|
||||
fn: () -> ...(() -> ())?,
|
||||
fn: (dispose: () -> ()) -> ...(() -> ())?,
|
||||
cleanups: { () -> () },
|
||||
cleanup: () -> (),
|
||||
}
|
||||
|
||||
type Computed<T = any> = ReactiveNode & {
|
||||
|
|
@ -59,7 +61,6 @@ local notifyIndex = 0
|
|||
|
||||
local activeScope: Effect | EffectScope?
|
||||
local activeSub: ReactiveNode?
|
||||
local activeSubWatching = true
|
||||
|
||||
local function getCurrentSub(): ReactiveNode?
|
||||
return activeSub
|
||||
|
|
@ -81,16 +82,10 @@ local function setCurrentScope(scope: Effect | EffectScope?)
|
|||
return prevScope
|
||||
end
|
||||
|
||||
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: any = (config.strict and noYield or pcall)(cleanup)
|
||||
local success, result: any = (if config.strict then noYield else pcall)(cleanup)
|
||||
setCurrentSub(prev)
|
||||
if not success then
|
||||
error(result)
|
||||
|
|
@ -113,12 +108,10 @@ 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 = setCurrentWatching(true)
|
||||
startTracking(effect)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(effect.fn)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)(effect.fn, effect.cleanup)
|
||||
setCurrentSub(prev)
|
||||
endTracking(effect)
|
||||
setCurrentWatching(prevTracking)
|
||||
if not success then
|
||||
error(result)
|
||||
elseif result then
|
||||
|
|
@ -170,13 +163,11 @@ end
|
|||
|
||||
local function updateComputed(computed: Computed): boolean
|
||||
local prevSub = setCurrentSub(computed)
|
||||
local prevWatching = setCurrentWatching(true)
|
||||
startTracking(computed)
|
||||
local oldValue = computed.value
|
||||
local success, result = (config.strict and noYield or pcall)(computed.getter, oldValue)
|
||||
local success, result = (if config.strict then noYield else pcall)(computed.getter, oldValue)
|
||||
setCurrentSub(prevSub)
|
||||
endTracking(computed)
|
||||
setCurrentWatching(prevWatching)
|
||||
if not success then
|
||||
error(result)
|
||||
end
|
||||
|
|
@ -209,7 +200,7 @@ local function readAtom<T>(atom: Atom<T>): T
|
|||
end
|
||||
end
|
||||
end
|
||||
if activeSub and activeSubWatching then
|
||||
if activeSub and not bit32.btest(activeSub.flags, UNTRACKED) then
|
||||
link(atom, activeSub)
|
||||
end
|
||||
return value
|
||||
|
|
@ -244,7 +235,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 = (config.strict and noYield or pcall)(newValue, oldValue)
|
||||
local success, result = (if config.strict then noYield else pcall)(newValue, oldValue)
|
||||
setCurrentSub(prev)
|
||||
if not success then
|
||||
error(result)
|
||||
|
|
@ -270,7 +261,7 @@ 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 and activeSubWatching then
|
||||
if activeSub and not bit32.btest(activeSub.flags, UNTRACKED) then
|
||||
link(computed, activeSub)
|
||||
elseif activeScope then
|
||||
link(computed, activeScope)
|
||||
|
|
@ -350,28 +341,28 @@ local function computed<T>(getter: (previousValue: T?) -> T, equals: ((T, T) ->
|
|||
})
|
||||
end
|
||||
|
||||
local function effect(fn: () -> ...(() -> ())?): () -> ()
|
||||
local function effect(fn: (dispose: () -> ()) -> ...(() -> ())?): () -> ()
|
||||
local self: Effect = {
|
||||
fn = fn,
|
||||
flags = WATCHING,
|
||||
cleanups = {},
|
||||
cleanup = nil :: any,
|
||||
}
|
||||
self.cleanup = effectStop(self)
|
||||
if activeSub then
|
||||
link(self, activeSub)
|
||||
elseif activeScope then
|
||||
link(self, activeScope)
|
||||
end
|
||||
local prev = setCurrentSub(self)
|
||||
local prevWatching = setCurrentWatching(true)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(fn)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)(fn, self.cleanup)
|
||||
setCurrentSub(prev)
|
||||
setCurrentWatching(prevWatching)
|
||||
if not success then
|
||||
error(result)
|
||||
elseif result then
|
||||
table.insert(self.cleanups, result)
|
||||
end
|
||||
return effectStop(self)
|
||||
return self.cleanup
|
||||
end
|
||||
|
||||
local function effectScope(fn: () -> ...(() -> ())?, detached: boolean?): () -> ()
|
||||
|
|
@ -384,7 +375,7 @@ local function effectScope(fn: () -> ...(() -> ())?, detached: boolean?): () ->
|
|||
end
|
||||
local prevSub = setCurrentSub(nil)
|
||||
local prevScope = setCurrentScope(self)
|
||||
local success, result: (() -> ())? = (config.strict and noYield or pcall)(fn)
|
||||
local success, result: (() -> ())? = (if config.strict then noYield else pcall)(fn)
|
||||
setCurrentScope(prevScope)
|
||||
setCurrentSub(prevSub)
|
||||
if not success then
|
||||
|
|
@ -416,7 +407,6 @@ return {
|
|||
setCurrentSub = setCurrentSub,
|
||||
getCurrentScope = getCurrentScope,
|
||||
setCurrentScope = setCurrentScope,
|
||||
setCurrentWatching = setCurrentWatching,
|
||||
onCleanup = onCleanup,
|
||||
getBatchDepth = getBatchDepth,
|
||||
startBatch = startBatch,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
--!nolint DeprecatedApi
|
||||
|
||||
local batched = require("@self/batched")
|
||||
local core = require("@self/core")
|
||||
local flags = require("@self/flags")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ local core = require("./core")
|
|||
local effect = core.effect
|
||||
local effectScope = core.effectScope
|
||||
local onCleanup = core.onCleanup
|
||||
local startBatch = core.startBatch
|
||||
local endBatch = core.endBatch
|
||||
|
||||
local function observe<K, V>(selector: () -> { [K]: V }, observer: (item: V, key: K) -> ...(() -> ())?): () -> ()
|
||||
local scopes: { [K]: () -> () } = {}
|
||||
|
|
@ -18,7 +20,7 @@ local function observe<K, V>(selector: () -> { [K]: V }, observer: (item: V, key
|
|||
end
|
||||
|
||||
for key, value in currentItems do
|
||||
if previousItems[key] == nil then
|
||||
if previousItems[key] == nil and scopes[key] == nil then
|
||||
scopes[key] = effectScope(function()
|
||||
return observer(value, key)
|
||||
end, true)
|
||||
|
|
@ -37,7 +39,9 @@ local function observe<K, V>(selector: () -> { [K]: V }, observer: (item: V, key
|
|||
local previousItems = currentItems
|
||||
currentItems = selector()
|
||||
if currentItems ~= previousItems then
|
||||
startBatch()
|
||||
updateScopes(currentItems, previousItems)
|
||||
endBatch()
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export type ReactiveNode = {
|
|||
}
|
||||
|
||||
export type Link = {
|
||||
version: number,
|
||||
dep: ReactiveNode,
|
||||
sub: ReactiveNode,
|
||||
prevSub: Link?,
|
||||
|
|
@ -26,13 +27,14 @@ type Stack<T> = {
|
|||
|
||||
export type ReactiveFlags = number
|
||||
|
||||
local NONE = 0b0000000
|
||||
local MUTABLE = 0b0000001
|
||||
local WATCHING = 0b0000010
|
||||
local RECURSED_CHECK = 0b0000100
|
||||
local RECURSED = 0b0001000
|
||||
local DIRTY = 0b0010000
|
||||
local PENDING = 0b0100000
|
||||
local NONE = 0b00000000
|
||||
local MUTABLE = 0b00000001
|
||||
local WATCHING = 0b00000010
|
||||
local RECURSED_CHECK = 0b00000100
|
||||
local RECURSED = 0b00001000
|
||||
local DIRTY = 0b00010000
|
||||
local PENDING = 0b00100000
|
||||
local UNTRACKED = 0b10000000
|
||||
|
||||
local ReactiveFlags = {
|
||||
None = NONE,
|
||||
|
|
@ -42,6 +44,7 @@ local ReactiveFlags = {
|
|||
Recursed = RECURSED,
|
||||
Dirty = DIRTY,
|
||||
Pending = PENDING,
|
||||
Untracked = UNTRACKED,
|
||||
}
|
||||
|
||||
local function createReactiveSystem(props: {
|
||||
|
|
@ -52,20 +55,15 @@ local function createReactiveSystem(props: {
|
|||
local update = props.update
|
||||
local notify = props.notify
|
||||
local unwatched = props.unwatched
|
||||
local currentVersion = 0
|
||||
|
||||
local function isValidLink(checkLink: Link, sub: ReactiveNode): boolean
|
||||
local depsTail = sub.depsTail
|
||||
if depsTail then
|
||||
local link = sub.deps :: Link
|
||||
repeat
|
||||
if link == checkLink then
|
||||
return true
|
||||
end
|
||||
if link == depsTail then
|
||||
break
|
||||
end
|
||||
link = link.nextDep :: Link
|
||||
until not link
|
||||
local link = sub.depsTail
|
||||
while link do
|
||||
if link == checkLink then
|
||||
return true
|
||||
end
|
||||
link = link.prevDep
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
|
@ -75,20 +73,18 @@ local function createReactiveSystem(props: {
|
|||
if prevDep and prevDep.dep == dep then
|
||||
return
|
||||
end
|
||||
local nextDep: Link?
|
||||
local recursedCheck = bit32.btest(sub.flags, RECURSED_CHECK)
|
||||
if recursedCheck then
|
||||
nextDep = if prevDep then prevDep.nextDep else sub.deps
|
||||
if nextDep and nextDep.dep == dep then
|
||||
sub.depsTail = nextDep
|
||||
return
|
||||
end
|
||||
local nextDep = if prevDep then prevDep.nextDep else sub.deps
|
||||
if nextDep and nextDep.dep == dep then
|
||||
nextDep.version = currentVersion
|
||||
sub.depsTail = nextDep
|
||||
return
|
||||
end
|
||||
local prevSub = dep.subsTail
|
||||
if prevSub and prevSub.sub == sub and (not recursedCheck or isValidLink(prevSub, sub)) then
|
||||
if prevSub and prevSub.version == currentVersion and prevSub.sub == sub then
|
||||
return
|
||||
end
|
||||
local newLink: Link = {
|
||||
version = currentVersion,
|
||||
dep = dep,
|
||||
sub = sub,
|
||||
prevDep = prevDep,
|
||||
|
|
@ -154,35 +150,34 @@ local function createReactiveSystem(props: {
|
|||
|
||||
local flags = sub.flags
|
||||
|
||||
if bit32.btest(flags, bit32.bor(MUTABLE, WATCHING)) then
|
||||
if not bit32.btest(flags, bit32.bor(RECURSED_CHECK, RECURSED, DIRTY, PENDING)) then
|
||||
sub.flags = bit32.bor(flags, PENDING)
|
||||
elseif not bit32.btest(flags, bit32.bor(RECURSED_CHECK, RECURSED)) then
|
||||
flags = NONE
|
||||
elseif not bit32.btest(flags, RECURSED_CHECK) then
|
||||
sub.flags = bit32.bor(bit32.band(flags, bit32.bnot(RECURSED)), PENDING)
|
||||
elseif not bit32.btest(flags, bit32.bor(DIRTY, PENDING)) and isValidLink(link, sub) then
|
||||
sub.flags = bit32.bor(flags, bit32.bor(RECURSED, PENDING))
|
||||
flags = bit32.band(flags, MUTABLE)
|
||||
else
|
||||
-- selene: allow(if_same_then_else)
|
||||
flags = NONE
|
||||
end
|
||||
if not bit32.btest(flags, bit32.bor(RECURSED_CHECK, RECURSED, DIRTY, PENDING)) then
|
||||
sub.flags = bit32.bor(flags, PENDING)
|
||||
elseif not bit32.btest(flags, bit32.bor(RECURSED_CHECK, RECURSED)) then
|
||||
flags = NONE
|
||||
elseif not bit32.btest(flags, RECURSED_CHECK) then
|
||||
sub.flags = bit32.bor(bit32.band(flags, bit32.bnot(RECURSED)), PENDING)
|
||||
elseif not bit32.btest(flags, bit32.bor(DIRTY, PENDING)) and isValidLink(link, sub) then
|
||||
sub.flags = bit32.bor(flags, bit32.bor(RECURSED, PENDING))
|
||||
flags = bit32.band(flags, MUTABLE)
|
||||
else
|
||||
-- selene: allow(if_same_then_else)
|
||||
flags = NONE
|
||||
end
|
||||
|
||||
if bit32.btest(flags, WATCHING) then
|
||||
notify(sub)
|
||||
end
|
||||
if bit32.btest(flags, WATCHING) then
|
||||
notify(sub)
|
||||
end
|
||||
|
||||
if bit32.btest(flags, MUTABLE) then
|
||||
local subSubs = sub.subs
|
||||
if subSubs then
|
||||
link = subSubs
|
||||
if subSubs.nextSub then
|
||||
stack = { value = next, prev = stack }
|
||||
next = link.nextSub
|
||||
end
|
||||
continue
|
||||
if bit32.btest(flags, MUTABLE) then
|
||||
local subSubs = sub.subs
|
||||
if subSubs then
|
||||
link = subSubs
|
||||
local nextSub = link.nextSub
|
||||
if nextSub then
|
||||
stack = { value = next, prev = stack }
|
||||
next = nextSub
|
||||
end
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -214,6 +209,7 @@ local function createReactiveSystem(props: {
|
|||
end
|
||||
|
||||
local function startTracking(sub: ReactiveNode): ()
|
||||
currentVersion += 1
|
||||
sub.depsTail = nil
|
||||
sub.flags = bit32.bor(bit32.band(sub.flags, bit32.bnot(bit32.bor(RECURSED, DIRTY, PENDING))), RECURSED_CHECK)
|
||||
end
|
||||
|
|
@ -230,10 +226,10 @@ local function createReactiveSystem(props: {
|
|||
local function shallowPropagate(link: Link): ()
|
||||
repeat
|
||||
local sub = link.sub
|
||||
local subFlags = sub.flags
|
||||
if bit32.band(subFlags, bit32.bor(PENDING, DIRTY)) == PENDING then
|
||||
sub.flags = bit32.bor(subFlags, DIRTY)
|
||||
if bit32.btest(subFlags, WATCHING) then
|
||||
local flags = sub.flags
|
||||
if bit32.band(flags, bit32.bor(PENDING, DIRTY)) == PENDING then
|
||||
sub.flags = bit32.bor(flags, DIRTY)
|
||||
if bit32.btest(flags, WATCHING) then
|
||||
notify(sub)
|
||||
end
|
||||
end
|
||||
|
|
@ -244,16 +240,15 @@ local function createReactiveSystem(props: {
|
|||
local function checkDirty(link: Link, sub: ReactiveNode): boolean
|
||||
local stack: Stack<Link>?
|
||||
local checkDepth = 0
|
||||
local dirty = false
|
||||
|
||||
repeat
|
||||
local dep = link.dep
|
||||
local depFlags = dep.flags
|
||||
|
||||
local dirty = false
|
||||
local flags = dep.flags
|
||||
|
||||
if bit32.btest(sub.flags, DIRTY) then
|
||||
dirty = true
|
||||
elseif bit32.band(depFlags, bit32.bor(MUTABLE, DIRTY)) == bit32.bor(MUTABLE, DIRTY) then
|
||||
elseif bit32.band(flags, bit32.bor(MUTABLE, DIRTY)) == bit32.bor(MUTABLE, DIRTY) then
|
||||
if update(dep) then
|
||||
local subs = dep.subs :: Link
|
||||
if subs.nextSub then
|
||||
|
|
@ -261,7 +256,7 @@ local function createReactiveSystem(props: {
|
|||
end
|
||||
dirty = true
|
||||
end
|
||||
elseif bit32.band(depFlags, bit32.bor(MUTABLE, PENDING)) == bit32.bor(MUTABLE, PENDING) then
|
||||
elseif bit32.band(flags, bit32.bor(MUTABLE, PENDING)) == bit32.bor(MUTABLE, PENDING) then
|
||||
if link.nextSub or link.prevSub then
|
||||
stack = { value = link, prev = stack }
|
||||
end
|
||||
|
|
@ -271,9 +266,12 @@ local function createReactiveSystem(props: {
|
|||
continue
|
||||
end
|
||||
|
||||
if not dirty and link.nextDep then
|
||||
link = link.nextDep
|
||||
continue
|
||||
if not dirty then
|
||||
local nextDep = link.nextDep
|
||||
if nextDep then
|
||||
link = nextDep
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
local continueTop = false
|
||||
|
|
@ -296,16 +294,17 @@ local function createReactiveSystem(props: {
|
|||
sub = link.sub
|
||||
continue
|
||||
end
|
||||
dirty = false
|
||||
else
|
||||
sub.flags = bit32.band(sub.flags, bit32.bnot(PENDING))
|
||||
end
|
||||
sub = link.sub
|
||||
if link.nextDep then
|
||||
link = link.nextDep
|
||||
local nextDep = link.nextDep
|
||||
if nextDep then
|
||||
link = nextDep
|
||||
continueTop = true
|
||||
break
|
||||
end
|
||||
dirty = false
|
||||
end
|
||||
|
||||
if continueTop then
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
local core = require("./core")
|
||||
|
||||
local setCurrentWatching = core.setCurrentWatching
|
||||
local UNTRACKED = core.ReactiveFlags.Untracked
|
||||
local getCurrentSub = core.getCurrentSub
|
||||
|
||||
local function handleUntracked(prevWatching: boolean, ok: boolean, ...)
|
||||
setCurrentWatching(prevWatching)
|
||||
local function handleUntracked(sub: core.ReactiveNode?, ok: boolean, ...)
|
||||
if sub then
|
||||
sub.flags = bit32.band(sub.flags, bit32.bnot(UNTRACKED))
|
||||
end
|
||||
if ok then
|
||||
return ...
|
||||
else
|
||||
|
|
@ -12,7 +15,13 @@ local function handleUntracked(prevWatching: boolean, ok: boolean, ...)
|
|||
end
|
||||
|
||||
local function untracked<Args..., Results...>(callback: (Args...) -> Results..., ...: Args...): Results...
|
||||
return handleUntracked(setCurrentWatching(false), pcall(callback, ...))
|
||||
local sub = getCurrentSub()
|
||||
if not sub or bit32.btest(sub.flags, UNTRACKED) then
|
||||
return callback(...)
|
||||
else
|
||||
sub.flags = bit32.bor(sub.flags, UNTRACKED)
|
||||
end
|
||||
return handleUntracked(sub, pcall(callback, ...))
|
||||
end
|
||||
|
||||
return untracked
|
||||
|
|
|
|||
10
rokit.toml
10
rokit.toml
|
|
@ -6,8 +6,8 @@
|
|||
[tools]
|
||||
wally = "UpliftGames/wally@0.3.2"
|
||||
rojo = "rojo-rbx/rojo@7.5.1"
|
||||
stylua = "johnnymorganz/stylua@2.1.0"
|
||||
selene = "Kampfkarren/selene@0.28.0"
|
||||
luau-lsp = "johnnymorganz/luau-lsp@1.51.0"
|
||||
lune = "lune-org/lune@0.9.4"
|
||||
lute = "luau-lang/lute@0.1.0-nightly.20250701"
|
||||
stylua = "johnnymorganz/stylua@2.2.0"
|
||||
selene = "Kampfkarren/selene@0.29.0"
|
||||
luau-lsp = "johnnymorganz/luau-lsp@1.53.5"
|
||||
lune = "lune-org/lune@0.10.2"
|
||||
lute = "luau-lang/lute@0.1.0-nightly.20250923"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
std = "selene_defs"
|
||||
exclude = ["**/node_modules/**"]
|
||||
exclude = ["*/node_modules/*"]
|
||||
|
||||
[lints]
|
||||
global_usage = "allow"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue