UNPKG

@tldraw/state

Version:

tldraw infinite canvas SDK (state).

140 lines (139 loc) 3.97 kB
import { ArraySet } from "./ArraySet.mjs"; import { startCapturingParents, stopCapturingParents } from "./capture.mjs"; import { GLOBAL_START_EPOCH } from "./constants.mjs"; import { attach, detach, haveParentsChanged, singleton } from "./helpers.mjs"; import { getGlobalEpoch } from "./transactions.mjs"; class __EffectScheduler__ { constructor(name, runEffect, options) { this.name = name; this.runEffect = runEffect; this._scheduleEffect = options?.scheduleEffect; } _isActivelyListening = false; /** * Whether this scheduler is attached and actively listening to its parents. * @public */ // eslint-disable-next-line no-restricted-syntax get isActivelyListening() { return this._isActivelyListening; } /** @internal */ lastTraversedEpoch = GLOBAL_START_EPOCH; lastReactedEpoch = GLOBAL_START_EPOCH; _scheduleCount = 0; __debug_ancestor_epochs__ = null; /** * The number of times this effect has been scheduled. * @public */ // eslint-disable-next-line no-restricted-syntax get scheduleCount() { return this._scheduleCount; } /** @internal */ parentSet = new ArraySet(); /** @internal */ parentEpochs = []; /** @internal */ parents = []; _scheduleEffect; /** @internal */ maybeScheduleEffect() { if (!this._isActivelyListening) return; if (this.lastReactedEpoch === getGlobalEpoch()) return; if (this.parents.length && !haveParentsChanged(this)) { this.lastReactedEpoch = getGlobalEpoch(); return; } this.scheduleEffect(); } /** @internal */ scheduleEffect() { this._scheduleCount++; if (this._scheduleEffect) { this._scheduleEffect(this.maybeExecute); } else { this.execute(); } } /** @internal */ // eslint-disable-next-line local/prefer-class-methods maybeExecute = () => { if (!this._isActivelyListening) return; this.execute(); }; /** * Makes this scheduler become 'actively listening' to its parents. * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls. * If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`. * @public */ attach() { this._isActivelyListening = true; for (let i = 0, n = this.parents.length; i < n; i++) { attach(this.parents[i], this); } } /** * Makes this scheduler stop 'actively listening' to its parents. * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again. */ detach() { this._isActivelyListening = false; for (let i = 0, n = this.parents.length; i < n; i++) { detach(this.parents[i], this); } } /** * Executes the effect immediately and returns the result. * @returns The result of the effect. */ execute() { try { startCapturingParents(this); const currentEpoch = getGlobalEpoch(); const result = this.runEffect(this.lastReactedEpoch); this.lastReactedEpoch = currentEpoch; return result; } finally { stopCapturingParents(); } } } const EffectScheduler = singleton( "EffectScheduler", () => __EffectScheduler__ ); function react(name, fn, options) { const scheduler = new EffectScheduler(name, fn, options); scheduler.attach(); scheduler.scheduleEffect(); return () => { scheduler.detach(); }; } function reactor(name, fn, options) { const scheduler = new EffectScheduler(name, fn, options); return { scheduler, start: (options2) => { const force = options2?.force ?? false; scheduler.attach(); if (force) { scheduler.scheduleEffect(); } else { scheduler.maybeScheduleEffect(); } }, stop: () => { scheduler.detach(); } }; } export { EffectScheduler, react, reactor }; //# sourceMappingURL=EffectScheduler.mjs.map