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) {
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)
}

View File

@ -166,18 +166,29 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
/**
* @param {Array<AbstractStruct>} 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<any>} */ (right.parent)._map.get(right.parentSub) === right) {
/** @type {AbstractType<any>} */ (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<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 (
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<GC|Item>} */ (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<GC|Item>} */ (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)) {