bulk-merging structs - replaces #542, fixes #541

This commit is contained in:
Kevin Jahns 2023-07-17 14:29:54 +02:00
parent 90f2a06b5e
commit c77dedb68d
2 changed files with 30 additions and 17 deletions

View File

@ -108,7 +108,7 @@ export class ContentType {
while (item !== null) { while (item !== null) {
if (!item.deleted) { if (!item.deleted) {
item.delete(transaction) 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 // This will be gc'd later and we want to merge it if possible
// We try to merge all deleted items after each transaction, // We try to merge all deleted items after each transaction,
// but we have no knowledge about that this needs to be merged // but we have no knowledge about that this needs to be merged
@ -120,7 +120,7 @@ export class ContentType {
this.type._map.forEach(item => { this.type._map.forEach(item => {
if (!item.deleted) { if (!item.deleted) {
item.delete(transaction) item.delete(transaction)
} else { } else if (item.id.clock < (transaction.beforeState.get(item.id.client) || 0)) {
// same as above // same as above
transaction._mergeStructs.push(item) transaction._mergeStructs.push(item)
} }

View File

@ -166,18 +166,29 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
/** /**
* @param {Array<AbstractStruct>} structs * @param {Array<AbstractStruct>} structs
* @param {number} pos * @param {number} pos
* @return {number} # of merged structs
*/ */
const tryToMergeWithLeft = (structs, pos) => { const tryToMergeWithLefts = (structs, pos) => {
const left = structs[pos - 1] let right = structs[pos]
const right = structs[pos] let left = structs[pos - 1]
if (left.deleted === right.deleted && left.constructor === right.constructor) { let i = pos
if (left.mergeWith(right)) { for (; i > 0; right = left, left = structs[--i - 1]) {
structs.splice(pos, 1) if (left.deleted === right.deleted && left.constructor === right.constructor) {
if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType<any>} */ (right.parent)._map.get(right.parentSub) === right) { if (left.mergeWith(right)) {
/** @type {AbstractType<any>} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left)) if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType<any>} */ (right.parent)._map.get(right.parentSub) === right) {
/** @type {AbstractType<any>} */ (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 ( for (
let si = mostRightIndexToCheck, struct = structs[si]; let si = mostRightIndexToCheck, struct = structs[si];
si > 0 && struct.id.clock >= deleteItem.clock; 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<GC|Item>} */ (store.clients.get(client)) const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
// we iterate from right to left so we can safely remove entries // we iterate from right to left so we can safely remove entries
const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1) const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1)
for (let i = structs.length - 1; i >= firstChangePos; i--) { for (let i = structs.length - 1; i >= firstChangePos;) {
tryToMergeWithLeft(structs, i) i -= 1 + tryToMergeWithLefts(structs, i)
} }
} }
}) })
// try to merge mergeStructs // try to merge mergeStructs
// @todo: it makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left // @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 // 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 { client, clock } = mergeStructs[i].id
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client)) const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
const replacedStructPos = findIndexSS(structs, clock) const replacedStructPos = findIndexSS(structs, clock)
if (replacedStructPos + 1 < structs.length) { 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) { if (replacedStructPos > 0) {
tryToMergeWithLeft(structs, replacedStructPos) tryToMergeWithLefts(structs, replacedStructPos)
} }
} }
if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) { if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) {