diff --git a/src/utils/updates.js b/src/utils/updates.js index b3aec8d6..6b54d83d 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -149,33 +149,39 @@ export const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, */ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YDecoder = UpdateDecoderV2) => { const encoder = new YEncoder() - const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update)), true) + const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update)), false) let curr = updateDecoder.curr if (curr !== null) { - let size = 1 + let size = 0 let currClient = curr.id.client - let currClock = curr.id.clock - let stopCounting = false + let currClock = 0 + let stopCounting = curr.id.clock !== 0 // must start at 0 for (; curr !== null; curr = updateDecoder.next()) { - if (currClient !== curr.id.client) { - size++ - // We found a new client - // write what we have to the encoder - encoding.writeVarUint(encoder.restEncoder, currClient) - encoding.writeVarUint(encoder.restEncoder, currClock) - currClient = curr.id.client - stopCounting = false - } + // we ignore skips if (curr.constructor === Skip) { stopCounting = true } if (!stopCounting) { currClock = curr.id.clock + curr.length } + if (currClient !== curr.id.client) { + if (currClock !== 0) { + size++ + // We found a new client + // write what we have to the encoder + encoding.writeVarUint(encoder.restEncoder, currClient) + encoding.writeVarUint(encoder.restEncoder, currClock) + } + currClient = curr.id.client + stopCounting = false + } } // write what we have - encoding.writeVarUint(encoder.restEncoder, currClient) - encoding.writeVarUint(encoder.restEncoder, currClock) + if (currClock !== 0) { + size++ + encoding.writeVarUint(encoder.restEncoder, currClient) + encoding.writeVarUint(encoder.restEncoder, currClock) + } // prepend the size of the state vector const enc = encoding.createEncoder() encoding.writeVarUint(enc, size) diff --git a/tests/encoding.tests.js b/tests/encoding.tests.js index 207f13d5..6a191009 100644 --- a/tests/encoding.tests.js +++ b/tests/encoding.tests.js @@ -18,6 +18,8 @@ import { applyUpdate } from '../src/internals.js' +import * as Y from '../src/index.js' + /** * @param {t.TestCase} tc */ @@ -62,3 +64,45 @@ export const testPermanentUserData = async tc => { const pd3 = new PermanentUserData(ydoc3) pd3.setUserMapping(ydoc3, ydoc3.clientID, 'user a') } + +/** + * Reported here: https://github.com/yjs/yjs/issues/308 + * @param {t.TestCase} tc + */ +export const testDiffStateVectorOfUpdateIsEmpty = tc => { + const ydoc = new Y.Doc() + /** + * @type {null | Uint8Array} + */ + let sv = /* any */ (null) + ydoc.getText().insert(0, 'a') + ydoc.on('update', update => { + sv = Y.encodeStateVectorFromUpdate(update) + }) + // should produce an update with an empty state vector (because previous ops are missing) + ydoc.getText().insert(0, 'a') + t.assert(sv !== null && sv.byteLength === 1 && sv[0] === 0) +} + +/** + * Reported here: https://github.com/yjs/yjs/issues/308 + * @param {t.TestCase} tc + */ +export const testDiffStateVectorOfUpdateIgnoresSkips = tc => { + const ydoc = new Y.Doc() + /** + * @type {Array} + */ + const updates = [] + ydoc.on('update', update => { + updates.push(update) + }) + ydoc.getText().insert(0, 'a') + ydoc.getText().insert(0, 'b') + ydoc.getText().insert(0, 'c') + const update13 = Y.mergeUpdates([updates[0], updates[2]]) + const sv = Y.encodeStateVectorFromUpdate(update13) + const state = Y.decodeStateVector(sv) + t.assert(state.get(ydoc.clientID) === 1) + t.assert(state.size === 1) +} diff --git a/tests/updates.tests.js b/tests/updates.tests.js index d9f8743d..924dd220 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -166,9 +166,7 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { const targetSV = Y.encodeStateVectorFromUpdateV2(Y.mergeUpdatesV2(updates.slice(0, j))) const diffed = enc.diffUpdate(mergedUpdates, targetSV) const diffedMeta = enc.parseUpdateMeta(diffed) - const decDiffedSV = Y.decodeStateVector(enc.encodeStateVectorFromUpdate(diffed)) t.compare(partMeta, diffedMeta) - t.compare(decDiffedSV, partMeta.to) { // We can'd do the following // - t.compare(diffed, mergedDeletes)