implement snapshotContainsUpdate
This commit is contained in:
		
							parent
							
								
									5db1eed181
								
							
						
					
					
						commit
						719858201a
					
				| @ -92,7 +92,9 @@ export { | |||||||
|   convertUpdateFormatV2ToV1, |   convertUpdateFormatV2ToV1, | ||||||
|   obfuscateUpdate, |   obfuscateUpdate, | ||||||
|   obfuscateUpdateV2, |   obfuscateUpdateV2, | ||||||
|   UpdateEncoderV1 |   UpdateEncoderV1, | ||||||
|  |   equalDeleteSets, | ||||||
|  |   snapshotContainsUpdate | ||||||
| } from './internals.js' | } from './internals.js' | ||||||
| 
 | 
 | ||||||
| const glo = /** @type {any} */ (typeof globalThis !== 'undefined' | const glo = /** @type {any} */ (typeof globalThis !== 'undefined' | ||||||
|  | |||||||
| @ -328,3 +328,23 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => { | |||||||
|   } |   } | ||||||
|   return null |   return null | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {DeleteSet} ds1 | ||||||
|  |  * @param {DeleteSet} ds2 | ||||||
|  |  */ | ||||||
|  | export const equalDeleteSets = (ds1, ds2) => { | ||||||
|  |   if (ds1.clients.size !== ds2.clients.size) return false | ||||||
|  |   ds1.clients.forEach((deleteItems1, client) => { | ||||||
|  |     const deleteItems2 = /** @type {Array<import('../internals.js').DeleteItem>} */ (ds2.clients.get(client)) | ||||||
|  |     if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false | ||||||
|  |     for (let i = 0; i < deleteItems1.length; i++) { | ||||||
|  |       const di1 = deleteItems1[i] | ||||||
|  |       const di2 = deleteItems2[i] | ||||||
|  |       if (di1.clock !== di2.clock || di1.len !== di2.len) { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |   return true | ||||||
|  | } | ||||||
|  | |||||||
| @ -15,7 +15,10 @@ import { | |||||||
|   findIndexSS, |   findIndexSS, | ||||||
|   UpdateEncoderV2, |   UpdateEncoderV2, | ||||||
|   applyUpdateV2, |   applyUpdateV2, | ||||||
|   DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
 |   LazyStructReader, | ||||||
|  |   equalDeleteSets, | ||||||
|  |   UpdateDecoderV1, UpdateDecoderV2, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item, // eslint-disable-line
 | ||||||
|  |   mergeDeleteSets | ||||||
| } from '../internals.js' | } from '../internals.js' | ||||||
| 
 | 
 | ||||||
| import * as map from 'lib0/map' | import * as map from 'lib0/map' | ||||||
| @ -147,7 +150,7 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => { | |||||||
|         getItemCleanStart(transaction, createID(client, clock)) |         getItemCleanStart(transaction, createID(client, clock)) | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     iterateDeletedStructs(transaction, snapshot.ds, item => {}) |     iterateDeletedStructs(transaction, snapshot.ds, _item => {}) | ||||||
|     meta.add(snapshot) |     meta.add(snapshot) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -207,3 +210,28 @@ export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) = | |||||||
|   applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot') |   applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot') | ||||||
|   return newDoc |   return newDoc | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {Snapshot} snapshot | ||||||
|  |  * @param {Uint8Array} update | ||||||
|  |  * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder] | ||||||
|  |  */ | ||||||
|  | export const snapshotContainsUpdateV2 = (snapshot, update, YDecoder = UpdateDecoderV2) => { | ||||||
|  |   const structs = [] | ||||||
|  |   const updateDecoder = new YDecoder(decoding.createDecoder(update)) | ||||||
|  |   const lazyDecoder = new LazyStructReader(updateDecoder, false) | ||||||
|  |   for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) { | ||||||
|  |     structs.push(curr) | ||||||
|  |     if ((snapshot.sv.get(curr.id.client) || 0) < curr.id.clock + curr.length) { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   const mergedDS = mergeDeleteSets([snapshot.ds, readDeleteSet(updateDecoder)]) | ||||||
|  |   return equalDeleteSets(snapshot.ds, mergedDS) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {Snapshot} snapshot | ||||||
|  |  * @param {Uint8Array} update | ||||||
|  |  */ | ||||||
|  | export const snapshotContainsUpdate = (snapshot, update) => snapshotContainsUpdateV2(snapshot, update, UpdateDecoderV1) | ||||||
|  | |||||||
| @ -88,7 +88,7 @@ export const writeClientsStructs = (encoder, store, _sm) => { | |||||||
|       sm.set(client, clock) |       sm.set(client, clock) | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|   getStateVector(store).forEach((clock, client) => { |   getStateVector(store).forEach((_clock, client) => { | ||||||
|     if (!_sm.has(client)) { |     if (!_sm.has(client)) { | ||||||
|       sm.set(client, 0) |       sm.set(client, 0) | ||||||
|     } |     } | ||||||
| @ -98,8 +98,7 @@ export const writeClientsStructs = (encoder, store, _sm) => { | |||||||
|   // Write items with higher client ids first
 |   // Write items with higher client ids first
 | ||||||
|   // This heavily improves the conflict algorithm.
 |   // This heavily improves the conflict algorithm.
 | ||||||
|   array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { |   array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { | ||||||
|     // @ts-ignore
 |     writeStructs(encoder, /** @type {Array<GC|Item>} */ (store.clients.get(client)), client, clock) | ||||||
|     writeStructs(encoder, store.clients.get(client), client, clock) |  | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,9 +3,9 @@ import * as t from 'lib0/testing' | |||||||
| import { init } from './testHelper.js' | import { init } from './testHelper.js' | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testBasic = tc => { | export const testBasic = _tc => { | ||||||
|   const ydoc = new Y.Doc({ gc: false }) |   const ydoc = new Y.Doc({ gc: false }) | ||||||
|   ydoc.getText().insert(0, 'world!') |   ydoc.getText().insert(0, 'world!') | ||||||
|   const snapshot = Y.snapshot(ydoc) |   const snapshot = Y.snapshot(ydoc) | ||||||
| @ -15,9 +15,9 @@ export const testBasic = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testBasicRestoreSnapshot = tc => { | export const testBasicRestoreSnapshot = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, ['hello']) |   doc.getArray('array').insert(0, ['hello']) | ||||||
|   const snap = Y.snapshot(doc) |   const snap = Y.snapshot(doc) | ||||||
| @ -30,9 +30,9 @@ export const testBasicRestoreSnapshot = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testEmptyRestoreSnapshot = tc => { | export const testEmptyRestoreSnapshot = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   const snap = Y.snapshot(doc) |   const snap = Y.snapshot(doc) | ||||||
|   snap.sv.set(9999, 0) |   snap.sv.set(9999, 0) | ||||||
| @ -50,9 +50,9 @@ export const testEmptyRestoreSnapshot = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testRestoreSnapshotWithSubType = tc => { | export const testRestoreSnapshotWithSubType = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, [new Y.Map()]) |   doc.getArray('array').insert(0, [new Y.Map()]) | ||||||
|   const subMap = doc.getArray('array').get(0) |   const subMap = doc.getArray('array').get(0) | ||||||
| @ -73,9 +73,9 @@ export const testRestoreSnapshotWithSubType = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testRestoreDeletedItem1 = tc => { | export const testRestoreDeletedItem1 = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, ['item1', 'item2']) |   doc.getArray('array').insert(0, ['item1', 'item2']) | ||||||
| 
 | 
 | ||||||
| @ -89,9 +89,9 @@ export const testRestoreDeletedItem1 = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testRestoreLeftItem = tc => { | export const testRestoreLeftItem = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, ['item1']) |   doc.getArray('array').insert(0, ['item1']) | ||||||
|   doc.getMap('map').set('test', 1) |   doc.getMap('map').set('test', 1) | ||||||
| @ -107,9 +107,9 @@ export const testRestoreLeftItem = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testDeletedItemsBase = tc => { | export const testDeletedItemsBase = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, ['item1']) |   doc.getArray('array').insert(0, ['item1']) | ||||||
|   doc.getArray('array').delete(0) |   doc.getArray('array').delete(0) | ||||||
| @ -123,9 +123,9 @@ export const testDeletedItemsBase = tc => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {t.TestCase} tc |  * @param {t.TestCase} _tc | ||||||
|  */ |  */ | ||||||
| export const testDeletedItems2 = tc => { | export const testDeletedItems2 = _tc => { | ||||||
|   const doc = new Y.Doc({ gc: false }) |   const doc = new Y.Doc({ gc: false }) | ||||||
|   doc.getArray('array').insert(0, ['item1', 'item2', 'item3']) |   doc.getArray('array').insert(0, ['item1', 'item2', 'item3']) | ||||||
|   doc.getArray('array').delete(1) |   doc.getArray('array').delete(1) | ||||||
| @ -181,3 +181,28 @@ export const testDependentChanges = tc => { | |||||||
|   const docRestored1 = Y.createDocFromSnapshot(array1.doc, snap) |   const docRestored1 = Y.createDocFromSnapshot(array1.doc, snap) | ||||||
|   t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1']) |   t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1']) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {t.TestCase} _tc | ||||||
|  |  */ | ||||||
|  | export const testContainsUpdate = _tc => { | ||||||
|  |   const ydoc = new Y.Doc() | ||||||
|  |   /** | ||||||
|  |    * @type {Array<Uint8Array>} | ||||||
|  |    */ | ||||||
|  |   const updates = [] | ||||||
|  |   ydoc.on('update', update => { | ||||||
|  |     updates.push(update) | ||||||
|  |   }) | ||||||
|  |   const yarr = ydoc.getArray() | ||||||
|  |   const snapshot1 = Y.snapshot(ydoc) | ||||||
|  |   yarr.insert(0, [1]) | ||||||
|  |   const snapshot2 = Y.snapshot(ydoc) | ||||||
|  |   yarr.delete(0, 1) | ||||||
|  |   const snapshotFinal = Y.snapshot(ydoc) | ||||||
|  |   t.assert(!Y.snapshotContainsUpdate(snapshot1, updates[0])) | ||||||
|  |   t.assert(!Y.snapshotContainsUpdate(snapshot2, updates[1])) | ||||||
|  |   t.assert(Y.snapshotContainsUpdate(snapshot2, updates[0])) | ||||||
|  |   t.assert(Y.snapshotContainsUpdate(snapshotFinal, updates[0])) | ||||||
|  |   t.assert(Y.snapshotContainsUpdate(snapshotFinal, updates[1])) | ||||||
|  | } | ||||||
|  | |||||||
| @ -356,8 +356,9 @@ export const compare = users => { | |||||||
|       return true |       return true | ||||||
|     }) |     }) | ||||||
|     t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])) |     t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])) | ||||||
|     compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store)) |     Y.equalDeleteSets(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store)) | ||||||
|     compareStructStores(users[i].store, users[i + 1].store) |     compareStructStores(users[i].store, users[i + 1].store) | ||||||
|  |     t.compare(Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1]))) | ||||||
|   } |   } | ||||||
|   users.map(u => u.destroy()) |   users.map(u => u.destroy()) | ||||||
| } | } | ||||||
| @ -412,25 +413,6 @@ export const compareStructStores = (ss1, ss2) => { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * @param {import('../src/internals.js').DeleteSet} ds1 |  | ||||||
|  * @param {import('../src/internals.js').DeleteSet} ds2 |  | ||||||
|  */ |  | ||||||
| export const compareDS = (ds1, ds2) => { |  | ||||||
|   t.assert(ds1.clients.size === ds2.clients.size) |  | ||||||
|   ds1.clients.forEach((deleteItems1, client) => { |  | ||||||
|     const deleteItems2 = /** @type {Array<import('../src/internals.js').DeleteItem>} */ (ds2.clients.get(client)) |  | ||||||
|     t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length) |  | ||||||
|     for (let i = 0; i < deleteItems1.length; i++) { |  | ||||||
|       const di1 = deleteItems1[i] |  | ||||||
|       const di2 = deleteItems2[i] |  | ||||||
|       if (di1.clock !== di2.clock || di1.len !== di2.len) { |  | ||||||
|         t.fail('DeleteSets dont match') |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * @template T |  * @template T | ||||||
|  * @callback InitTestObjectCallback |  * @callback InitTestObjectCallback | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user