diff --git a/src/structs/ContentType.js b/src/structs/ContentType.js index 96a731de..e9c11de1 100644 --- a/src/structs/ContentType.js +++ b/src/structs/ContentType.js @@ -108,7 +108,7 @@ export class ContentType { while (item !== null) { if (!item.deleted) { item.delete(transaction) - } else { + } else if (item.id.clock < (transaction.beforeState.get(item.id.client) || 0)) { // This will be gc'd later and we want to merge it if possible // We try to merge all deleted items after each transaction, // but we have no knowledge about that this needs to be merged @@ -120,7 +120,7 @@ export class ContentType { this.type._map.forEach(item => { if (!item.deleted) { item.delete(transaction) - } else { + } else if (item.id.clock < (transaction.beforeState.get(item.id.client) || 0)) { // same as above transaction._mergeStructs.push(item) } diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 35940595..5b93369f 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -166,18 +166,29 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => { /** * @param {Array} structs * @param {number} pos + * @return {number} # of merged structs */ -const tryToMergeWithLeft = (structs, pos) => { - const left = structs[pos - 1] - const right = structs[pos] - if (left.deleted === right.deleted && left.constructor === right.constructor) { - if (left.mergeWith(right)) { - structs.splice(pos, 1) - if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType} */ (right.parent)._map.get(right.parentSub) === right) { - /** @type {AbstractType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left)) +const tryToMergeWithLefts = (structs, pos) => { + let right = structs[pos] + let left = structs[pos - 1] + let i = pos + for (; i > 0; right = left, left = structs[--i - 1]) { + if (left.deleted === right.deleted && left.constructor === right.constructor) { + if (left.mergeWith(right)) { + if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType} */ (right.parent)._map.get(right.parentSub) === right) { + /** @type {AbstractType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left)) + } + continue } } + break } + const merged = pos - i + if (merged) { + // remove all merged structs from the array + structs.splice(pos + 1 - merged, merged) + } + return merged } /** @@ -224,9 +235,9 @@ const tryMergeDeleteSet = (ds, store) => { for ( let si = mostRightIndexToCheck, struct = structs[si]; si > 0 && struct.id.clock >= deleteItem.clock; - struct = structs[--si] + struct = structs[si] ) { - tryToMergeWithLeft(structs, si) + si -= 1 + tryToMergeWithLefts(structs, si) } } }) @@ -318,23 +329,25 @@ const cleanupTransactions = (transactionCleanups, i) => { const structs = /** @type {Array} */ (store.clients.get(client)) // we iterate from right to left so we can safely remove entries const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1) - for (let i = structs.length - 1; i >= firstChangePos; i--) { - tryToMergeWithLeft(structs, i) + for (let i = structs.length - 1; i >= firstChangePos;) { + i -= 1 + tryToMergeWithLefts(structs, i) } } }) // try to merge mergeStructs // @todo: it makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left // but at the moment DS does not handle duplicates - for (let i = 0; i < mergeStructs.length; i++) { + for (let i = mergeStructs.length - 1; i >= 0; i--) { const { client, clock } = mergeStructs[i].id const structs = /** @type {Array} */ (store.clients.get(client)) const replacedStructPos = findIndexSS(structs, clock) if (replacedStructPos + 1 < structs.length) { - tryToMergeWithLeft(structs, replacedStructPos + 1) + if (tryToMergeWithLefts(structs, replacedStructPos + 1) > 1) { + continue // no need to perform next check, both are already merged + } } if (replacedStructPos > 0) { - tryToMergeWithLeft(structs, replacedStructPos) + tryToMergeWithLefts(structs, replacedStructPos) } } if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) {