@tldraw/state
Version:
tldraw infinite canvas SDK (state).
149 lines (148 loc) • 4.71 kB
JavaScript
import { isComputed } from "./Computed.mjs";
import { attach, detach, singleton } from "./helpers.mjs";
class CaptureStackFrame {
constructor(below, child) {
this.below = below;
this.child = child;
}
offset = 0;
maybeRemoved;
}
const inst = singleton("capture", () => ({ stack: null }));
function unsafe__withoutCapture(fn) {
const oldStack = inst.stack;
inst.stack = null;
try {
return fn();
} finally {
inst.stack = oldStack;
}
}
function startCapturingParents(child) {
inst.stack = new CaptureStackFrame(inst.stack, child);
if (child.__debug_ancestor_epochs__) {
const previousAncestorEpochs = child.__debug_ancestor_epochs__;
child.__debug_ancestor_epochs__ = null;
for (const p of child.parents) {
p.__unsafe__getWithoutCapture(true);
}
logChangedAncestors(child, previousAncestorEpochs);
}
child.parentSet.clear();
}
function stopCapturingParents() {
const frame = inst.stack;
inst.stack = frame.below;
if (frame.offset < frame.child.parents.length) {
for (let i = frame.offset; i < frame.child.parents.length; i++) {
const maybeRemovedParent = frame.child.parents[i];
if (!frame.child.parentSet.has(maybeRemovedParent)) {
detach(maybeRemovedParent, frame.child);
}
}
frame.child.parents.length = frame.offset;
frame.child.parentEpochs.length = frame.offset;
}
if (frame.maybeRemoved) {
for (let i = 0; i < frame.maybeRemoved.length; i++) {
const maybeRemovedParent = frame.maybeRemoved[i];
if (!frame.child.parentSet.has(maybeRemovedParent)) {
detach(maybeRemovedParent, frame.child);
}
}
}
if (frame.child.__debug_ancestor_epochs__) {
captureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__);
}
}
function maybeCaptureParent(p) {
if (inst.stack) {
const wasCapturedAlready = inst.stack.child.parentSet.has(p);
if (wasCapturedAlready) {
return;
}
inst.stack.child.parentSet.add(p);
if (inst.stack.child.isActivelyListening) {
attach(p, inst.stack.child);
}
if (inst.stack.offset < inst.stack.child.parents.length) {
const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset];
if (maybeRemovedParent !== p) {
if (!inst.stack.maybeRemoved) {
inst.stack.maybeRemoved = [maybeRemovedParent];
} else {
inst.stack.maybeRemoved.push(maybeRemovedParent);
}
}
}
inst.stack.child.parents[inst.stack.offset] = p;
inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch;
inst.stack.offset++;
}
}
function whyAmIRunning() {
const child = inst.stack?.child;
if (!child) {
throw new Error("whyAmIRunning() called outside of a reactive context");
}
child.__debug_ancestor_epochs__ = /* @__PURE__ */ new Map();
}
function captureAncestorEpochs(child, ancestorEpochs) {
for (let i = 0; i < child.parents.length; i++) {
const parent = child.parents[i];
const epoch = child.parentEpochs[i];
ancestorEpochs.set(parent, epoch);
if (isComputed(parent)) {
captureAncestorEpochs(parent, ancestorEpochs);
}
}
return ancestorEpochs;
}
function collectChangedAncestors(child, ancestorEpochs) {
const changeTree = {};
for (let i = 0; i < child.parents.length; i++) {
const parent = child.parents[i];
if (!ancestorEpochs.has(parent)) {
continue;
}
const prevEpoch = ancestorEpochs.get(parent);
const currentEpoch = parent.lastChangedEpoch;
if (currentEpoch !== prevEpoch) {
if (isComputed(parent)) {
changeTree[parent.name] = collectChangedAncestors(parent, ancestorEpochs);
} else {
changeTree[parent.name] = null;
}
}
}
return changeTree;
}
function logChangedAncestors(child, ancestorEpochs) {
const changeTree = collectChangedAncestors(child, ancestorEpochs);
if (Object.keys(changeTree).length === 0) {
console.log(`Effect(${child.name}) was executed manually.`);
return;
}
let str = isComputed(child) ? `Computed(${child.name}) is recomputing because:` : `Effect(${child.name}) is executing because:`;
function logParent(tree, indent) {
const indentStr = "\n" + " ".repeat(indent) + "\u21B3 ";
for (const [name, val] of Object.entries(tree)) {
if (val) {
str += `${indentStr}Computed(${name}) changed`;
logParent(val, indent + 2);
} else {
str += `${indentStr}Atom(${name}) changed`;
}
}
}
logParent(changeTree, 1);
console.log(str);
}
export {
maybeCaptureParent,
startCapturingParents,
stopCapturingParents,
unsafe__withoutCapture,
whyAmIRunning
};
//# sourceMappingURL=capture.mjs.map