UNPKG

@tldraw/state

Version:

tldraw infinite canvas SDK (state).

231 lines (230 loc) • 6.53 kB
"use strict"; 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