From a6aedf00c5d20ef8e1eab7d5f482ba1cb4577b8a Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 10 Apr 2025 21:07:59 +0200 Subject: [PATCH] doc maintains ds --- src/index.js | 1 + src/structs/GC.js | 2 ++ src/utils/IdSet.js | 19 +++++++++++++++++++ src/utils/StructStore.js | 4 +++- src/utils/Transaction.js | 4 +++- tests/IdSet.tests.js | 20 +------------------- tests/testHelper.js | 23 +++++++++++++++++++++++ 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/index.js b/src/index.js index 54d0b0df..eba8727b 100644 --- a/src/index.js +++ b/src/index.js @@ -99,6 +99,7 @@ export { UpdateDecoderV2, snapshotContainsUpdate, // idset + IdSet, equalIdSets, createDeleteSetFromStructStore } from './internals.js' diff --git a/src/structs/GC.js b/src/structs/GC.js index 28e4e8a1..710751cb 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -2,6 +2,7 @@ import { AbstractStruct, addStruct, addStructToIdSet, + addToIdSet, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction // eslint-disable-line } from '../internals.js' @@ -38,6 +39,7 @@ export class GC extends AbstractStruct { this.id.clock += offset this.length -= offset } + addToIdSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) addStructToIdSet(transaction.insertSet, this) addStruct(transaction.doc.store, this) } diff --git a/src/utils/IdSet.js b/src/utils/IdSet.js index d05f8a53..6a24f8b3 100644 --- a/src/utils/IdSet.js +++ b/src/utils/IdSet.js @@ -189,6 +189,25 @@ export const mergeIdSets = idSets => { return merged } +/** + * @param {IdSet} dest + * @param {IdSet} src + */ +export const insertIntoIdSet = (dest, src) => { + src.clients.forEach((srcRanges, client) => { + const targetRanges = dest.clients.get(client) + if (targetRanges) { + array.appendTo(targetRanges.getIds(), srcRanges.getIds()) + targetRanges.sorted = false + } else { + const res = new IdRanges(srcRanges.getIds().slice()) + res.sorted = true + dest.clients.set(client, res) + } + }) +} + + /** * Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not * in `exclude`. diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 692743d7..fa4cd23c 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -1,7 +1,8 @@ import { GC, splitItem, - Transaction, ID, Item, DSDecoderV2 // eslint-disable-line + IdSet, + Transaction, ID, Item // eslint-disable-line } from '../internals.js' import * as math from 'lib0/math' @@ -13,6 +14,7 @@ export class StructStore { * @type {Map>} */ this.clients = new Map() + this.ds = new IdSet() /** * @type {null | { missing: Map, update: Uint8Array }} */ diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index d0cf019d..feebe5ba 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -10,7 +10,8 @@ import { generateNewClientId, createID, cleanupYTextAfterTransaction, - IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line + IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc, // eslint-disable-line + insertIntoIdSet } from '../internals.js' import * as error from 'lib0/error' @@ -307,6 +308,7 @@ const cleanupTransactions = (transactionCleanups, i) => { const store = doc.store const ds = transaction.deleteSet const mergeStructs = transaction._mergeStructs + insertIntoIdSet(store.ds, ds) try { doc.emit('beforeObserverCalls', [transaction, doc]) /** diff --git a/tests/IdSet.tests.js b/tests/IdSet.tests.js index f036d364..c9ca5617 100644 --- a/tests/IdSet.tests.js +++ b/tests/IdSet.tests.js @@ -2,6 +2,7 @@ import * as t from 'lib0/testing' import * as d from '../src/utils/IdSet.js' import * as prng from 'lib0/prng' import * as math from 'lib0/math' +import { compareIdSets } from './testHelper.js' /** * @param {Array<[number, number, number]>} ops @@ -14,25 +15,6 @@ const simpleConstructIdSet = ops => { return ds } -/** - * @param {d.IdSet} idSet1 - * @param {d.IdSet} idSet2 - */ -const compareIdSets = (idSet1, idSet2) => { - if (idSet1.clients.size !== idSet2.clients.size) return false - for (const [client, _items1] of idSet1.clients.entries()) { - const items1 = _items1.getIds() - const items2 = idSet2.clients.get(client)?.getIds() - t.assert(items2 !== undefined && items1.length === items2.length) - for (let i = 0; i < items1.length; i++) { - const di1 = items1[i] - const di2 = /** @type {Array} */ (items2)[i] - t.assert(di1.clock === di2.clock && di1.len === di2.len) - } - } - return true -} - /** * @param {t.TestCase} _tc */ diff --git a/tests/testHelper.js b/tests/testHelper.js index ad5d84ce..69533b0c 100644 --- a/tests/testHelper.js +++ b/tests/testHelper.js @@ -304,6 +304,26 @@ export const init = (tc, { users = 5 } = {}, initTestObject) => { return /** @type {any} */ (result) } +/** + * @param {Y.IdSet} idSet1 + * @param {Y.IdSet} idSet2 + */ +export const compareIdSets = (idSet1, idSet2) => { + if (idSet1.clients.size !== idSet2.clients.size) return false + for (const [client, _items1] of idSet1.clients.entries()) { + const items1 = _items1.getIds() + const items2 = idSet2.clients.get(client)?.getIds() + t.assert(items2 !== undefined && items1.length === items2.length) + for (let i = 0; i < items1.length; i++) { + const di1 = items1[i] + const di2 = /** @type {Array} */ (items2)[i] + t.assert(di1.clock === di2.clock && di1.len === di2.len) + } + } + return true +} + + /** * 1. reconnect and flush all * 2. user 0 gc @@ -366,6 +386,9 @@ export const compare = users => { compareStructStores(users[i].store, users[i + 1].store) t.compare(Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1]))) } + users.forEach(user => { + compareIdSets(user.store.ds, Y.createDeleteSetFromStructStore(user.store)) + }) users.map(u => u.destroy()) }