@tldraw/state
Version:
tldraw infinite canvas SDK (state).
231 lines (230 loc) • 6.53 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var transactions_exports = {};
__export(transactions_exports, {
advanceGlobalEpoch: () => advanceGlobalEpoch,
atomDidChange: () => atomDidChange,
deferAsyncEffects: () => deferAsyncEffects,
getGlobalEpoch: () => getGlobalEpoch,
getIsReacting: () => getIsReacting,
getReactionEpoch: () => getReactionEpoch,
transact: () => transact,
transaction: () => transaction
});
module.exports = __toCommonJS(transactions_exports);
var import_EffectScheduler = require("./EffectScheduler");
var import_constants = require("./constants");
var import_helpers = require("./helpers");
class Transaction {
constructor(parent, isSync) {
this.parent = parent;
this.isSync = isSync;
}
asyncProcessCount = 0;
initialAtomValues = /* @__PURE__ */ new Map();
/**
* Get whether this transaction is a root (no parents).
*
* @public
*/
// eslint-disable-next-line no-restricted-syntax
get isRoot() {
return this.parent === null;
}
/**
* Commit the transaction's changes.
*
* @public
*/
commit() {
if (inst.globalIsReacting) {
for (const atom of this.initialAtomValues.keys()) {
traverseAtomForCleanup(atom);
}
} else if (this.isRoot) {
flushChanges(this.initialAtomValues.keys());
} else {
this.initialAtomValues.forEach((value, atom) => {
if (!this.parent.initialAtomValues.has(atom)) {
this.parent.initialAtomValues.set(atom, value);
}
});
}
}
/**
* Abort the transaction.
*
* @public
*/
abort() {
inst.globalEpoch++;
this.initialAtomValues.forEach((value, atom) => {
atom.set(value);
atom.historyBuffer?.clear();
});
this.commit();
}
}
const inst = (0, import_helpers.singleton)("transactions", () => ({
// The current epoch (global to all atoms).
globalEpoch: import_constants.GLOBAL_START_EPOCH + 1,
// Whether any transaction is reacting.
globalIsReacting: false,
currentTransaction: null,
cleanupReactors: null,
reactionEpoch: import_constants.GLOBAL_START_EPOCH + 1
}));
function getReactionEpoch() {
return inst.reactionEpoch;
}
function getGlobalEpoch() {
return inst.globalEpoch;
}
function getIsReacting() {
return inst.globalIsReacting;
}
function traverse(reactors, child) {
if (child.lastTraversedEpoch === inst.globalEpoch) {
return;
}
child.lastTraversedEpoch = inst.globalEpoch;
if (child instanceof import_EffectScheduler.EffectScheduler) {
reactors.add(child);
} else {
;
child.children.visit((c) => traverse(reactors, c));
}
}
function flushChanges(atoms) {
if (inst.globalIsReacting) {
throw new Error("flushChanges cannot be called during a reaction");
}
const outerTxn = inst.currentTransaction;
try {
inst.currentTransaction = null;
inst.globalIsReacting = true;
inst.reactionEpoch = inst.globalEpoch;
const reactors = /* @__PURE__ */ new Set();
for (const atom of atoms) {
atom.children.visit((child) => traverse(reactors, child));
}
for (const r of reactors) {
r.maybeScheduleEffect();
}
let updateDepth = 0;
while (inst.cleanupReactors?.size) {
if (updateDepth++ > 1e3) {
throw new Error("Reaction update depth limit exceeded");
}
const reactors2 = inst.cleanupReactors;
inst.cleanupReactors = null;
for (const r of reactors2) {
r.maybeScheduleEffect();
}
}
} finally {
inst.cleanupReactors = null;
inst.globalIsReacting = false;
inst.currentTransaction = outerTxn;
}
}
function atomDidChange(atom, previousValue) {
if (inst.currentTransaction) {
if (!inst.currentTransaction.initialAtomValues.has(atom)) {
inst.currentTransaction.initialAtomValues.set(atom, previousValue);
}
} else if (inst.globalIsReacting) {
traverseAtomForCleanup(atom);
} else {
flushChanges([atom]);
}
}
function traverseAtomForCleanup(atom) {
const rs = inst.cleanupReactors ??= /* @__PURE__ */ new Set();
atom.children.visit((child) => traverse(rs, child));
}
function advanceGlobalEpoch() {
inst.globalEpoch++;
}
function transaction(fn) {
const txn = new Transaction(inst.currentTransaction, true);
inst.currentTransaction = txn;
try {
let result = void 0;
let rollback = false;
try {
result = fn(() => rollback = true);
} catch (e) {
txn.abort();
throw e;
}
if (inst.currentTransaction !== txn) {
throw new Error("Transaction boundaries overlap");
}
if (rollback) {
txn.abort();
} else {
txn.commit();
}
return result;
} finally {
inst.currentTransaction = txn.parent;
}
}
function transact(fn) {
if (inst.currentTransaction) {
return fn();
}
return transaction(fn);
}
async function deferAsyncEffects(fn) {
if (inst.currentTransaction?.isSync) {
throw new Error("deferAsyncEffects cannot be called during a sync transaction");
}
while (inst.globalIsReacting) {
await new Promise((r) => queueMicrotask(() => r(null)));
}
const txn = inst.currentTransaction ?? new Transaction(null, false);
if (txn.isSync) throw new Error("deferAsyncEffects cannot be called during a sync transaction");
inst.currentTransaction = txn;
txn.asyncProcessCount++;
let result = void 0;
let error = void 0;
try {
result = await fn();
} catch (e) {
error = e ?? null;
}
if (--txn.asyncProcessCount > 0) {
if (typeof error !== "undefined") {
throw error;
} else {
return result;
}
}
inst.currentTransaction = null;
if (typeof error !== "undefined") {
txn.abort();
throw error;
} else {
txn.commit();
return result;
}
}
//# sourceMappingURL=transactions.js.map
;