Open dead-claudia opened 1 month ago
Use case dried up for me, but it'd still be nice to have regardless, since it's such a simple method to implement.
I'm trying to understand this use case but I don't think I quite have my head around what you're (hypothetically/previously) trying to achieve. Can I try to rephrase the pieces and see if I'm making any progress getting towards the page you're on?
Reading a dirty signal. Presumably a Computed (since States can't be dirty, and Watchers can't be read). We deliberately didn't expose a way to read dirty or "stale" values from a Computed without rerunning it. But, AIUI, we do want to be in a world where cleaning a Computed has no side effects; this is the underlying motivation behind #77.
Blocking a watcher from getting further notifications, without detaching it. As far as I can tell this is already the behavior of watchers -- they get one notification until they're "reset" with watch()
, even if multiple signals they're watching are dirtied. (@littledan this does sound like a case for not making any other Watcher method implicitly re-arm the watcher.)
unwatch
those signals, doesn't fire its notify callback if they change, but can still be notified again by other signals it's watching?(And, maybe more philosophically, but I think it's germane here:)
To me the goal (which I think we haven't yet quite achieved) of the whole Watcher idea is to provide basically a "disembodied Computed": it has no value, it has no function to run, the system doesn't memoize anything for it, it has no readers downstream of it... but upstream it has the same affordances as a Computed does internally, and it tries to expose them to userspace. So it has a notion of "is it dirty" (has its notification fired), of "becoming clean again" (if you re-arm it with watch
), of checking which of its dependencies might have changed (getPending
/ notifiedBy
) and perhaps which have changed for real (this one is missing in the current API).
I think we sort have have two direction to go from there:
I don't think we have the canonical use cases or correctness criteria nailed down for Watchers yet, but -- if there's going to be such a thing -- this sort of "interface node" exposing all the relevant graph concepts we're already using to implement Computeds seems like one good start for discovering them.
@shaylew My original use case was this: reattempt to render a subtree while it's dirty, and retry synchronously, rather than having it re-schedule against the watcher.
This can be accomplished using something like the following pattern:
const wasBlocked = watcher.isBlocked
// Keeps the watcher callback from being invoked
watcher.isBlocked = true
try {
while (signal.isDirty) {
renderSubtree(signal.get())
}
} finally {
watcher.isBlocked = wasBlocked
}
I found an alternate design not reliant on this, so my use case no longer exists. But the operation is trivial, so I'm still behind it in principle.
Would you want the watcher callback to be invoked immediately when unblocked, or to just not get notifications from any dependencies dirtied while it was blocked until/unless those dependencies are cleaned and then dirtied a second time?
Watchers already have a dirty bit that tracks whether their notify
fired since they were last armed, and they already don't fire again unless they were explicitly re-armed. So it really seems like someone should be able to build isBlocked
on top of that, by no-oping notifications while blocked and then (if the watcher was armed when it became blocked) re-arming with .watch()
when unblocked.
Would you want the watcher callback to be invoked immediately when unblocked, or to just not get notifications from any dependencies dirtied while it was blocked until/unless those dependencies are cleaned and then dirtied a second time?
@shaylew The idea is that the watcher would not get called if dependencies change during that span. And after being unblocked, it would continue to not be called up until a watch
ed dependency is updated, in which it'd be called as normal.
Watchers already have a dirty bit that tracks whether their
notify
fired since they were last armed, and they already don't fire again unless they were explicitly re-armed. So it really seems like someone should be able to buildisBlocked
on top of that, by no-oping notifications while blocked and then (if the watcher was armed when it became blocked) re-arming with.watch()
when unblocked.
Reusing/exposing the dirty bit for that is the idea, yes. It'd also provide for a clearer story on how to re-arm the watcher after its notify
scheduler executes.
So, I ran into a very niche need: I need to read a watched signal, but I need to read it repeatedly while it's dirty (related: #129) and, once it's clean, I need to then re-watch it.
For correctness reasons, I need it to not call
Signal.subtle.{un,}watched
hooks during this, and as it's in a very hot path, I need this very optimized.This could be solved enough for my needs very simply: direct
watcher.block()
andwatcher.unblock()
methods. (The latter's proposed in #178.) It's just one field set, and so it's O(1) instead of O(tree depth). I do also needwatcher.block()
to return a boolean, so I can avoid callingwatcher.unblock()
prematurely (in case of recursive call).