@y/y
Version:
Shared Editing Library
111 lines (105 loc) • 3.8 kB
JavaScript
import * as encoding from 'lib0/encoding'
import * as math from 'lib0/math'
import * as array from 'lib0/array'
import { findIndexSS } from './transaction-helpers.js'
import { Skip } from '../structs/Skip.js'
import { createID } from './ID.js'
import { writeIdSet } from './ids.js'
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {Array<GC|Item|Skip>} structs All structs by `client`
* @param {number} client
* @param {Array<IdRange>} idranges
*
* @function
*/
export const writeStructs = (encoder, structs, client, idranges) => {
let structsToWrite = 0 // this accounts for the skips
/**
* @type {Array<{ start: number, end: number, startClock: number, endClock: number }>}
*/
const indexRanges = []
const firstPossibleClock = structs[0].id.clock
const lastStruct = array.last(structs)
const lastPossibleClock = lastStruct.id.clock + lastStruct.length
idranges.forEach(idrange => {
const startClock = math.max(idrange.clock, firstPossibleClock)
const endClock = math.min(idrange.clock + idrange.len, lastPossibleClock)
if (startClock >= endClock) return // structs for this range do not exist
// inclusive start
const start = findIndexSS(structs, startClock)
// exclusive end
const end = findIndexSS(structs, endClock - 1) + 1
structsToWrite += end - start
indexRanges.push({
start,
end,
startClock,
endClock
})
})
structsToWrite += idranges.length - 1
// start writing with this clock. this is updated to the next clock that we expect to write
let clock = indexRanges[0].startClock
// write # encoded structs
encoding.writeVarUint(encoder.restEncoder, structsToWrite)
encoder.writeClient(client)
// write clock
encoding.writeVarUint(encoder.restEncoder, clock)
indexRanges.forEach(indexRange => {
const skipLen = indexRange.startClock - clock
if (skipLen > 0) {
new Skip(createID(client, clock), skipLen).write(encoder, 0)
clock += skipLen
}
for (let i = indexRange.start; i < indexRange.end; i++) {
const struct = structs[i]
const structEnd = struct.id.clock + struct.length
const offsetEnd = math.max(structEnd - indexRange.endClock, 0)
struct.write(encoder, clock - struct.id.clock, offsetEnd)
clock = structEnd - offsetEnd
}
})
}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {StructStore} store
* @param {IdSet} idset
*
* @todo at the moment this writes the full deleteset range
*
* @private
* @function
*/
export const writeStructsFromIdSet = (encoder, store, idset) => {
// write # states that were updated
encoding.writeVarUint(encoder.restEncoder, idset.clients.size)
// Write items with higher client ids first
// This heavily improves the conflict algorithm.
array.from(idset.clients.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, ids]) => {
const idRanges = ids.getIds()
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
writeStructs(encoder, structs, client, idRanges)
})
}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {Transaction} transaction
*
* @private
* @function
*/
export const writeStructsFromTransaction = (encoder, transaction) => writeStructsFromIdSet(encoder, transaction.doc.store, transaction.insertSet)
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {Transaction} transaction
* @return {boolean} Whether data was written.
*/
export const writeUpdateMessageFromTransaction = (encoder, transaction) => {
if (transaction.deleteSet.clients.size === 0 && transaction.insertSet.clients.size === 0) {
return false
}
writeStructsFromTransaction(encoder, transaction)
writeIdSet(encoder, transaction.deleteSet)
return true
}