@tldraw/state
Version:
tldraw infinite canvas SDK (state).
140 lines (139 loc) • 3.97 kB
JavaScript
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