From 0c34216ed0b72ef060b3408fcb0bdd329bdaec81 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 21 Jun 2021 12:04:40 +0200 Subject: [PATCH] merge pending structs in v1 format --- src/utils/encoding.js | 21 ++++++++++--------- src/utils/updates.js | 30 +++++++++++++++++++++++++++ tests/updates.tests.js | 46 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 3506c05b..66536920 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -32,9 +32,11 @@ import { DSEncoderV2, DSDecoderV1, DSEncoderV1, + mergeUpdates, mergeUpdatesV2, Skip, diffUpdateV2, + convertUpdateFormatV2ToV1, DSDecoderV2, Doc, Transaction, GC, Item, StructStore // eslint-disable-line } from '../internals.js' @@ -523,15 +525,16 @@ export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8A writeStateAsUpdate(encoder, doc, targetStateVector) const updates = [encoder.toUint8Array()] // also add the pending updates (if there are any) - // @todo support diffirent encoders - if (encoder.constructor === UpdateEncoderV2) { - if (doc.store.pendingDs) { - updates.push(doc.store.pendingDs) - } - if (doc.store.pendingStructs) { - updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector)) - } - if (updates.length > 1) { + if (doc.store.pendingDs) { + updates.push(doc.store.pendingDs) + } + if (doc.store.pendingStructs) { + updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector)) + } + if (updates.length > 1) { + if (encoder.constructor === UpdateEncoderV1) { + return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update))) + } else if (encoder.constructor === UpdateEncoderV2) { return mergeUpdatesV2(updates) } } diff --git a/src/utils/updates.js b/src/utils/updates.js index 6b54d83d..5e28aae8 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -514,3 +514,33 @@ const finishLazyStructWriting = (lazyWriter) => { encoding.writeUint8Array(restEncoder, partStructs.restEncoder) } } + +/** + * @param {Uint8Array} update + * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} YDecoder + * @param {typeof UpdateEncoderV2 | typeof UpdateEncoderV1 } YEncoder + */ +export const convertUpdateFormat = (update, YDecoder, YEncoder) => { + const updateDecoder = new YDecoder(decoding.createDecoder(update)) + const lazyDecoder = new LazyStructReader(updateDecoder, false) + const updateEncoder = new YEncoder() + const lazyWriter = new LazyStructWriter(updateEncoder) + + for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) { + writeStructToLazyStructWriter(lazyWriter, curr, 0) + } + finishLazyStructWriting(lazyWriter) + const ds = readDeleteSet(updateDecoder) + writeDeleteSet(updateEncoder, ds) + return updateEncoder.toUint8Array() +} + +/** + * @param {Uint8Array} update + */ +export const convertUpdateFormatV1ToV2 = update => convertUpdateFormat(update, UpdateDecoderV1, UpdateEncoderV2) + +/** + * @param {Uint8Array} update + */ +export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, UpdateDecoderV2, UpdateEncoderV1) diff --git a/tests/updates.tests.js b/tests/updates.tests.js index 924dd220..9ae50440 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -240,5 +240,49 @@ export const testMergeUpdates2 = tc => { } /** - * @todo be able to apply Skip structs to Yjs docs + * @param {t.TestCase} tc */ +export const testMergePendingUpdates = tc => { + const yDoc = new Y.Doc() + /** + * @type {Array} + */ + const serverUpdates = [] + yDoc.on('update', (update, origin, c) => { + serverUpdates.splice(serverUpdates.length, 0, update) + }) + const yText = yDoc.getText('textBlock') + yText.applyDelta([{ insert: 'r' }]) + yText.applyDelta([{ insert: 'o' }]) + yText.applyDelta([{ insert: 'n' }]) + yText.applyDelta([{ insert: 'e' }]) + yText.applyDelta([{ insert: 'n' }]) + + const yDoc1 = new Y.Doc() + Y.applyUpdate(yDoc1, serverUpdates[0]) + const update1 = Y.encodeStateAsUpdate(yDoc1) + + const yDoc2 = new Y.Doc() + Y.applyUpdate(yDoc2, update1) + Y.applyUpdate(yDoc2, serverUpdates[1]) + const update2 = Y.encodeStateAsUpdate(yDoc2) + + const yDoc3 = new Y.Doc() + Y.applyUpdate(yDoc3, update2) + Y.applyUpdate(yDoc3, serverUpdates[3]) + const update3 = Y.encodeStateAsUpdate(yDoc3) + + const yDoc4 = new Y.Doc() + Y.applyUpdate(yDoc4, update3) + Y.applyUpdate(yDoc4, serverUpdates[2]) + const update4 = Y.encodeStateAsUpdate(yDoc4) + + const yDoc5 = new Y.Doc() + Y.applyUpdate(yDoc5, update4) + Y.applyUpdate(yDoc5, serverUpdates[4]) + // @ts-ignore + const update5 = Y.encodeStateAsUpdate(yDoc5) // eslint-disable-line + + const yText5 = yDoc5.getText('textBlock') + t.compareStrings(yText5.toString(), 'nenor') +}