Implement experimental new encoder 🚀
This commit is contained in:
408
src/utils/UpdateEncoder.js
Normal file
408
src/utils/UpdateEncoder.js
Normal file
@@ -0,0 +1,408 @@
|
||||
|
||||
import * as error from 'lib0/error.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
|
||||
import {
|
||||
ID // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
export class AbstractDSEncoder {
|
||||
constructor () {
|
||||
this.restEncoder = encoding.createEncoder()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Uint8Array}
|
||||
*/
|
||||
toUint8Array () {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the ds value to 0.
|
||||
* The v2 encoder uses this information to reset the initial diff value.
|
||||
*/
|
||||
resetDsCurVal () { }
|
||||
|
||||
/**
|
||||
* @param {number} clock
|
||||
*/
|
||||
writeDsClock (clock) { }
|
||||
|
||||
/**
|
||||
* @param {number} len
|
||||
*/
|
||||
writeDsLen (len) { }
|
||||
}
|
||||
|
||||
export class AbstractUpdateEncoder extends AbstractDSEncoder {
|
||||
/**
|
||||
* @return {Uint8Array}
|
||||
*/
|
||||
toUint8Array () {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeLeftID (id) { }
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeRightID (id) { }
|
||||
|
||||
/**
|
||||
* Use writeClient and writeClock instead of writeID if possible.
|
||||
* @param {number} client
|
||||
*/
|
||||
writeClient (client) { }
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeInfo (info) { }
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
*/
|
||||
writeString (s) { }
|
||||
|
||||
/**
|
||||
* @param {boolean} isYKey
|
||||
*/
|
||||
writeParentInfo (isYKey) { }
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeTypeRef (info) { }
|
||||
|
||||
/**
|
||||
* Write len of a struct - well suited for Opt RLE encoder.
|
||||
*
|
||||
* @param {number} len
|
||||
*/
|
||||
writeLen (len) { }
|
||||
|
||||
/**
|
||||
* @param {any} any
|
||||
*/
|
||||
writeAny (any) { }
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} buf
|
||||
*/
|
||||
writeBuf (buf) { }
|
||||
|
||||
/**
|
||||
* @param {any} embed
|
||||
*/
|
||||
writeJSON (embed) { }
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
writeKey (key) { }
|
||||
}
|
||||
|
||||
export class DSEncoderV1 {
|
||||
constructor () {
|
||||
this.restEncoder = new encoding.Encoder()
|
||||
}
|
||||
|
||||
toUint8Array () {
|
||||
return encoding.toUint8Array(this.restEncoder)
|
||||
}
|
||||
|
||||
resetDsCurVal () {
|
||||
// nop
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} clock
|
||||
*/
|
||||
writeDsClock (clock) {
|
||||
encoding.writeVarUint(this.restEncoder, clock)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} len
|
||||
*/
|
||||
writeDsLen (len) {
|
||||
encoding.writeVarUint(this.restEncoder, len)
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateEncoderV1 extends DSEncoderV1 {
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeLeftID (id) {
|
||||
encoding.writeVarUint(this.restEncoder, id.client)
|
||||
encoding.writeVarUint(this.restEncoder, id.clock)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeRightID (id) {
|
||||
encoding.writeVarUint(this.restEncoder, id.client)
|
||||
encoding.writeVarUint(this.restEncoder, id.clock)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use writeClient and writeClock instead of writeID if possible.
|
||||
* @param {number} client
|
||||
*/
|
||||
writeClient (client) {
|
||||
encoding.writeVarUint(this.restEncoder, client)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeInfo (info) {
|
||||
encoding.writeUint8(this.restEncoder, info)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
*/
|
||||
writeString (s) {
|
||||
encoding.writeVarString(this.restEncoder, s)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} isYKey
|
||||
*/
|
||||
writeParentInfo (isYKey) {
|
||||
encoding.writeVarUint(this.restEncoder, isYKey ? 1 : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeTypeRef (info) {
|
||||
encoding.writeVarUint(this.restEncoder, info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write len of a struct - well suited for Opt RLE encoder.
|
||||
*
|
||||
* @param {number} len
|
||||
*/
|
||||
writeLen (len) {
|
||||
encoding.writeVarUint(this.restEncoder, len)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} any
|
||||
*/
|
||||
writeAny (any) {
|
||||
encoding.writeAny(this.restEncoder, any)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} buf
|
||||
*/
|
||||
writeBuf (buf) {
|
||||
encoding.writeVarUint8Array(this.restEncoder, buf)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} embed
|
||||
*/
|
||||
writeJSON (embed) {
|
||||
encoding.writeVarString(this.restEncoder, JSON.stringify(embed))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
writeKey (key) {
|
||||
encoding.writeVarString(this.restEncoder, key)
|
||||
}
|
||||
}
|
||||
|
||||
export class DSEncoderV2 {
|
||||
constructor () {
|
||||
this.restEncoder = new encoding.Encoder() // encodes all the rest / non-optimized
|
||||
this.dsCurrVal = 0
|
||||
}
|
||||
|
||||
toUint8Array () {
|
||||
return encoding.toUint8Array(this.restEncoder)
|
||||
}
|
||||
|
||||
resetDsCurVal () {
|
||||
this.dsCurrVal = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} clock
|
||||
*/
|
||||
writeDsClock (clock) {
|
||||
const diff = clock - this.dsCurrVal
|
||||
this.dsCurrVal = clock
|
||||
encoding.writeVarUint(this.restEncoder, diff)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} len
|
||||
*/
|
||||
writeDsLen (len) {
|
||||
if (len === 0) {
|
||||
error.unexpectedCase()
|
||||
}
|
||||
encoding.writeVarUint(this.restEncoder, len - 1)
|
||||
this.dsCurrVal += len
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateEncoderV2 extends DSEncoderV2 {
|
||||
constructor () {
|
||||
super()
|
||||
/**
|
||||
* @type {Map<string,number>}
|
||||
*/
|
||||
this.keyMap = new Map()
|
||||
/**
|
||||
* Refers to the next uniqe key-identifier to me used.
|
||||
* See writeKey method for more information.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.keyClock = 0
|
||||
this.keyClockEncoder = new encoding.IntDiffOptRleEncoder()
|
||||
this.clientEncoder = new encoding.UintOptRleEncoder()
|
||||
this.leftClockEncoder = new encoding.IntDiffOptRleEncoder()
|
||||
this.rightClockEncoder = new encoding.IntDiffOptRleEncoder()
|
||||
this.infoEncoder = new encoding.RleEncoder(encoding.writeUint8)
|
||||
this.stringEncoder = new encoding.StringEncoder()
|
||||
this.parentInfoEncoder = new encoding.RleEncoder(encoding.writeUint8)
|
||||
this.typeRefEncoder = new encoding.UintOptRleEncoder()
|
||||
this.lenEncoder = new encoding.UintOptRleEncoder()
|
||||
}
|
||||
|
||||
toUint8Array () {
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeUint8(encoder, 0) // this is a feature flag that we might use in the future
|
||||
encoding.writeVarUint8Array(encoder, this.keyClockEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, this.clientEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.infoEncoder))
|
||||
encoding.writeVarUint8Array(encoder, this.stringEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.parentInfoEncoder))
|
||||
encoding.writeVarUint8Array(encoder, this.typeRefEncoder.toUint8Array())
|
||||
encoding.writeVarUint8Array(encoder, this.lenEncoder.toUint8Array())
|
||||
// @note The rest encoder is appended! (note the missing var)
|
||||
encoding.writeUint8Array(encoder, encoding.toUint8Array(this.restEncoder))
|
||||
return encoding.toUint8Array(encoder)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeLeftID (id) {
|
||||
this.clientEncoder.write(id.client)
|
||||
this.leftClockEncoder.write(id.clock)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
*/
|
||||
writeRightID (id) {
|
||||
this.clientEncoder.write(id.client)
|
||||
this.rightClockEncoder.write(id.clock)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} client
|
||||
*/
|
||||
writeClient (client) {
|
||||
this.clientEncoder.write(client)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeInfo (info) {
|
||||
this.infoEncoder.write(info)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
*/
|
||||
writeString (s) {
|
||||
this.stringEncoder.write(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} isYKey
|
||||
*/
|
||||
writeParentInfo (isYKey) {
|
||||
this.parentInfoEncoder.write(isYKey ? 1 : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} info An unsigned 8-bit integer
|
||||
*/
|
||||
writeTypeRef (info) {
|
||||
this.typeRefEncoder.write(info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write len of a struct - well suited for Opt RLE encoder.
|
||||
*
|
||||
* @param {number} len
|
||||
*/
|
||||
writeLen (len) {
|
||||
this.lenEncoder.write(len)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} any
|
||||
*/
|
||||
writeAny (any) {
|
||||
encoding.writeAny(this.restEncoder, any)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} buf
|
||||
*/
|
||||
writeBuf (buf) {
|
||||
encoding.writeVarUint8Array(this.restEncoder, buf)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mainly here for legacy purposes.
|
||||
*
|
||||
* Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder.
|
||||
*
|
||||
* @param {any} embed
|
||||
*/
|
||||
writeJSON (embed) {
|
||||
encoding.writeAny(this.restEncoder, embed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Property keys are often reused. For example, in y-prosemirror the key `bold` might
|
||||
* occur very often. For a 3d application, the key `position` might occur very often.
|
||||
*
|
||||
* We cache these keys in a Map and refer to them via a unique number.
|
||||
*
|
||||
* @param {string} key
|
||||
*/
|
||||
writeKey (key) {
|
||||
const clock = this.keyMap.get(key)
|
||||
if (clock === undefined) {
|
||||
this.keyClockEncoder.write(this.keyClock++)
|
||||
this.stringEncoder.write(key)
|
||||
} else {
|
||||
this.keyClockEncoder.write(this.keyClock++)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user