UNPKG

@eclipse-emfcloud/model-manager

Version:

Command-based model editing with undo/redo.

97 lines 4.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExclusiveExecutor = void 0; const model_logger_1 = require("@eclipse-emfcloud/model-logger"); const logger = (0, model_logger_1.getLogger)('model-manager/promise-util'); /** * Utility for execution of async functions that must be given mutually * exclusive access to some resource. Execution is chained: each async * call that is passed in for execution is deferred until the previous * has completed, whether normally or with an error. */ class ExclusiveExecutor { constructor() { /** * Awaitable results of the current pending exclusive operations. * Initially there's nothing pending. */ this.locks = []; } /** * Exclusively run the given asynchronous `operation`, returning its * eventual result. Operations can be executed in parallel only if they * affect different models and editing contexts. If modelIds is not * specified, the operation is assumed to potentially affect any model, * and will not run in parallel with any other operation. * * @param operation an asynchronous operation to run exclusively * @param contexts the editing contexts affected by the operation * @param modelIds the identifier of models modified by the operation * @returns the eventual result of the `operation` */ run(operation, contexts, modelIds) { const dependentLock = this.getDependentLock(contexts, modelIds); const result = dependentLock.pendingExclusiveOperation.then(() => operation()); // Don't propagate any error to the next caller in the chain const lockOperation = new Promise((resolve) => { result .catch((error) => { if (error instanceof Error) { logger.debug('Error occurred during command execution:', error.message); } else { logger.debug('Error occurred during command execution.', error); } }) .finally(resolve); }); const lock = { modelIds, contexts, pendingExclusiveOperation: lockOperation, }; this.locks.push(lock); // Remove the lock after execution lock.pendingExclusiveOperation.finally(() => { const lockIndex = this.locks.indexOf(lock); this.locks.splice(lockIndex, 1); }); return result; } getDependentLock(contexts, modelIds) { const waitForLocks = []; if (modelIds === undefined) { // If the model ids are missing, lock all models. This is typically // the case for undo/redo operations, which we shouldn't be running // in parallel. waitForLocks.push(...this.locks.map((lock) => lock.pendingExclusiveOperation)); } else { for (const lock of this.locks) { // If the editing contexts overlap, we need to wait for the previous operation. if (lock.contexts.some((context) => contexts.includes(context))) { waitForLocks.push(lock.pendingExclusiveOperation); } else { if (lock.modelIds === undefined) { // Wait for all operations that lock all models. waitForLocks.push(lock.pendingExclusiveOperation); } else { // If the model ids are present, only wait for overlapping locks. if (lock.modelIds.some((modelId) => modelIds.includes(modelId))) { waitForLocks.push(lock.pendingExclusiveOperation); } } } } } return { modelIds, contexts, pendingExclusiveOperation: Promise.allSettled(waitForLocks), }; } } exports.ExclusiveExecutor = ExclusiveExecutor; //# sourceMappingURL=promise-util.js.map