You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
we synchronously fan out and find all the reactor EffectScheduler instances that are out-of-date with the changed Atoms. We "notify" each EffectScheduler that it may need to change by calling reactor.maybeScheduleEffect()
Inside EffectScheduler, maybeScheduleEffect causes us to synchronously re-compute its dependency parent Computeds.
Finally, if any parent computed actually changed after recompute, then the EffectScheduler will schedule the effect for execution. The default "schedule" for effects to also run synchronously, but a user may provide a schedule function to defer it for later.
At Notion, we've noticed that recomputing Computed can take a substantial amount of event loop runtime, even if the result computation doesn't change. Because of this, we also schedule and throttle re-computation (step 2) using a queue and requestAnimationFrame. Would there be any correctness problem in deferring almost everything in maybeScheduleEffect() to an arbitrary later time, like we allow for maybeExecute()? I don't think that would cause any correctness issues.
The most naive change to allow consumers to defer step 2 would be to add scheduleRecompute?: (fn: () => void) => void option to EffectScheduler, and enqueue maybeScheduleEffect calls with that callback.. But I think there's a more interesting way to think about effects and scheduling, especially in light of the effort in #53.
The great thing about a logical clock derivation system is that reading a value is guaranteed to be consistent, no matter how much local or wall-clock time passes between the change of a state and when you perform a read. That means the change notification mechanism of Signia has no bearing on the correctness of Signia's computations.
Because of that property, I think it would be low-risk to move the notification behavior from a single traverse implementation in transaction.ts to a polymorphic method on Child called .notify(). The default implementation of that method on Computed and EffectScheduler would implement the same behavior of traverse as of today:
By making notify polymorphic, we can implement subclasses of both Computed and EffectScheduler that can arbitrarily accelerate or defer both the scheduling and algorithm for notifying their subgraph. To enable communication between participants, we could add an argument to notify like notify(ChangeSet) or something that especially complex participants could use as a WeakMap key / token or something.
The "push" Computed can be implemebed as a subclass that redefines notify to immediately do node.__unsafe__getWithoutCapture() etc.
The text was updated successfully, but these errors were encountered:
I see the value and I think it would work well for EffectScheduler.
Computed is a bit trickier
Making traverse polymorphic could impact perf since it is used in such hot loops. But it could also be fine 🤷♀️
It would bake in the current graph traversal approach and might make it difficult to add perf improvements or new features over time, e.g. I don't think push nodes would work implemented this way, they required a few extra core changes, and even then they weren't working 100% correctly according to the fuzz tests.
I'd be happy to merge a PR enabling this for EffectScheduler via an extra callback arg! That feels like a minimal and sensible change that provides a lot of extra leverage.
Currently when we end a transaction:
reactor.maybeScheduleEffect()
maybeScheduleEffect
causes us to synchronously re-compute its dependency parentComputed
s.At Notion, we've noticed that recomputing
Computed
can take a substantial amount of event loop runtime, even if the result computation doesn't change. Because of this, we also schedule and throttle re-computation (step 2) using a queue andrequestAnimationFrame
. Would there be any correctness problem in deferring almost everything inmaybeScheduleEffect()
to an arbitrary later time, like we allow formaybeExecute()
? I don't think that would cause any correctness issues.The most naive change to allow consumers to defer step 2 would be to add
scheduleRecompute?: (fn: () => void) => void
option to EffectScheduler, and enqueuemaybeScheduleEffect
calls with that callback.. But I think there's a more interesting way to think about effects and scheduling, especially in light of the effort in #53.The great thing about a logical clock derivation system is that reading a value is guaranteed to be consistent, no matter how much local or wall-clock time passes between the change of a state and when you perform a read. That means the change notification mechanism of Signia has no bearing on the correctness of Signia's computations.
Because of that property, I think it would be low-risk to move the notification behavior from a single
traverse
implementation intransaction.ts
to a polymorphic method onChild
called.notify()
. The default implementation of that method onComputed
andEffectScheduler
would implement the same behavior oftraverse
as of today:By making
notify
polymorphic, we can implement subclasses of bothComputed
andEffectScheduler
that can arbitrarily accelerate or defer both the scheduling and algorithm for notifying their subgraph. To enable communication between participants, we could add an argument tonotify
likenotify(ChangeSet)
or something that especially complex participants could use as a WeakMap key / token or something.The "push" Computed can be implemebed as a subclass that redefines
notify
to immediately donode.__unsafe__getWithoutCapture()
etc.The text was updated successfully, but these errors were encountered: