implement snapshotContainsUpdate
This commit is contained in:
		
							parent
							
								
									5db1eed181
								
							
						
					
					
						commit
						719858201a
					
				| @ -92,7 +92,9 @@ export { | ||||
|   convertUpdateFormatV2ToV1, | ||||
|   obfuscateUpdate, | ||||
|   obfuscateUpdateV2, | ||||
|   UpdateEncoderV1 | ||||
|   UpdateEncoderV1, | ||||
|   equalDeleteSets, | ||||
|   snapshotContainsUpdate | ||||
| } from './internals.js' | ||||
| 
 | ||||
| const glo = /** @type {any} */ (typeof globalThis !== 'undefined' | ||||
|  | ||||
| @ -328,3 +328,23 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => { | ||||
|   } | ||||
|   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, | ||||
|   UpdateEncoderV2, | ||||
|   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' | ||||
| 
 | ||||
| import * as map from 'lib0/map' | ||||
| @ -147,7 +150,7 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => { | ||||
|         getItemCleanStart(transaction, createID(client, clock)) | ||||
|       } | ||||
|     }) | ||||
|     iterateDeletedStructs(transaction, snapshot.ds, item => {}) | ||||
|     iterateDeletedStructs(transaction, snapshot.ds, _item => {}) | ||||
|     meta.add(snapshot) | ||||
|   } | ||||
| } | ||||
| @ -207,3 +210,28 @@ export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) = | ||||
|   applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot') | ||||
|   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) | ||||
|     } | ||||
|   }) | ||||
|   getStateVector(store).forEach((clock, client) => { | ||||
|   getStateVector(store).forEach((_clock, client) => { | ||||
|     if (!_sm.has(client)) { | ||||
|       sm.set(client, 0) | ||||
|     } | ||||
| @ -98,8 +98,7 @@ export const writeClientsStructs = (encoder, store, _sm) => { | ||||
|   // Write items with higher client ids first
 | ||||
|   // This heavily improves the conflict algorithm.
 | ||||
|   array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { | ||||
|     // @ts-ignore
 | ||||
|     writeStructs(encoder, store.clients.get(client), client, clock) | ||||
|     writeStructs(encoder, /** @type {Array<GC|Item>} */ (store.clients.get(client)), client, clock) | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -3,9 +3,9 @@ import * as t from 'lib0/testing' | ||||
| 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 }) | ||||
|   ydoc.getText().insert(0, 'world!') | ||||
|   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 }) | ||||
|   doc.getArray('array').insert(0, ['hello']) | ||||
|   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 snap = Y.snapshot(doc) | ||||
|   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 }) | ||||
|   doc.getArray('array').insert(0, [new Y.Map()]) | ||||
|   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 }) | ||||
|   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 }) | ||||
|   doc.getArray('array').insert(0, ['item1']) | ||||
|   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 }) | ||||
|   doc.getArray('array').insert(0, ['item1']) | ||||
|   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 }) | ||||
|   doc.getArray('array').insert(0, ['item1', 'item2', 'item3']) | ||||
|   doc.getArray('array').delete(1) | ||||
| @ -181,3 +181,28 @@ export const testDependentChanges = tc => { | ||||
|   const docRestored1 = Y.createDocFromSnapshot(array1.doc, snap) | ||||
|   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 | ||||
|     }) | ||||
|     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) | ||||
|     t.compare(Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1]))) | ||||
|   } | ||||
|   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 | ||||
|  * @callback InitTestObjectCallback | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user