Include Item.redone in binary encoding

This commit is contained in:
Mark Crispo 2024-07-09 10:59:28 -02:30
parent 384ec4db78
commit 1cfc865045
9 changed files with 62 additions and 21 deletions

View File

@ -93,6 +93,7 @@ export const splitItem = (transaction, leftItem, diff) => {
leftItem.rightOrigin,
leftItem.parent,
leftItem.parentSub,
leftItem.redone !== null ? createID(leftItem.redone.client, leftItem.redone.clock + diff) : null,
leftItem.content.splice(diff)
)
if (leftItem.deleted) {
@ -101,9 +102,6 @@ export const splitItem = (transaction, leftItem, diff) => {
if (leftItem.keep) {
rightItem.keep = true
}
if (leftItem.redone !== null) {
rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff)
}
// update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
leftItem.right = rightItem
// update right
@ -232,6 +230,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
right, right && right.id,
parentType,
item.parentSub,
null,
item.content.copy()
)
item.redone = nextId
@ -252,9 +251,10 @@ export class Item extends AbstractStruct {
* @param {ID | null} rightOrigin
* @param {AbstractType<any>|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
* @param {string | null} parentSub
* @param {ID | null} redone
* @param {AbstractContent} content
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
constructor (id, left, origin, right, rightOrigin, parent, parentSub, redone, content) {
super(id, content.getLength())
/**
* The item that was originally to the left of this item.
@ -293,7 +293,7 @@ export class Item extends AbstractStruct {
* this operation.
* @type {ID | null}
*/
this.redone = null
this.redone = redone
/**
* @type {AbstractContent}
*/
@ -653,10 +653,11 @@ export class Item extends AbstractStruct {
const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
const rightOrigin = this.rightOrigin
const parentSub = this.parentSub
const info = (this.content.getRef() & binary.BITS5) |
const info = (this.content.getRef() & binary.BITS4) |
(origin === null ? 0 : binary.BIT8) | // origin is defined
(rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
(parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
(parentSub === null ? 0 : binary.BIT6) | // parentSub is non-null
(this.redone === null ? 0 : binary.BIT5) // redone is defined
encoder.writeInfo(info)
if (origin !== null) {
encoder.writeLeftID(origin)
@ -691,6 +692,9 @@ export class Item extends AbstractStruct {
encoder.writeString(parentSub)
}
}
if (this.redone !== null) {
encoder.writeRedone(this.redone)
}
this.content.write(encoder, offset)
}
}
@ -699,7 +703,7 @@ export class Item extends AbstractStruct {
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
* @param {number} info
*/
export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS4](decoder)
/**
* A lookup map for reading Item content.

View File

@ -643,7 +643,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
let jsonContent = []
const packJsonContent = () => {
if (jsonContent.length > 0) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentAny(jsonContent))
left.integrate(transaction, 0)
jsonContent = []
}
@ -665,16 +665,16 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
switch (c.constructor) {
case Uint8Array:
case ArrayBuffer:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
left.integrate(transaction, 0)
break
case Doc:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentDoc(/** @type {Doc} */ (c)))
left.integrate(transaction, 0)
break
default:
if (c instanceof AbstractType) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentType(c))
left.integrate(transaction, 0)
} else {
throw new Error('Unexpected content type in insert operation')
@ -862,7 +862,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
}
}
}
new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, null, content).integrate(transaction, 0)
}
/**

View File

@ -166,7 +166,7 @@ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes
negatedAttributes.forEach((val, key) => {
const left = currPos.left
const right = currPos.right
const nextFormat = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
const nextFormat = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentFormat(key, val))
nextFormat.integrate(transaction, 0)
currPos.right = nextFormat
currPos.forward()
@ -232,7 +232,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
// save negated attribute (set null if currentVal undefined)
negatedAttributes.set(key, currentVal)
const { left, right } = currPos
currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, new ContentFormat(key, val))
currPos.right.integrate(transaction, 0)
currPos.forward()
}
@ -266,7 +266,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => {
if (parent._searchMarker) {
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
}
right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, null, content)
right.integrate(transaction, 0)
currPos.right = right
currPos.index = index
@ -342,7 +342,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
for (; length > 0; length--) {
newlines += '\n'
}
currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), currPos.left, currPos.left && currPos.left.lastId, currPos.right, currPos.right && currPos.right.id, parent, null, new ContentString(newlines))
currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), currPos.left, currPos.left && currPos.left.lastId, currPos.right, currPos.right && currPos.right.id, parent, null, null, new ContentString(newlines))
currPos.right.integrate(transaction, 0)
currPos.forward()
}

View File

@ -46,6 +46,13 @@ export class UpdateDecoderV1 extends DSDecoderV1 {
return createID(decoding.readVarUint(this.restDecoder), decoding.readVarUint(this.restDecoder))
}
/**
* @return {ID}
*/
readRedone () {
return createID(decoding.readVarUint(this.restDecoder), decoding.readVarUint(this.restDecoder))
}
/**
* Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created.
@ -174,6 +181,7 @@ export class UpdateDecoderV2 extends DSDecoderV2 {
this.clientDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder))
this.leftClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.rightClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.redoneClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.infoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8)
this.stringDecoder = new decoding.StringDecoder(decoding.readVarUint8Array(decoder))
this.parentInfoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8)
@ -195,6 +203,13 @@ export class UpdateDecoderV2 extends DSDecoderV2 {
return new ID(this.clientDecoder.read(), this.rightClockDecoder.read())
}
/**
* @return {ID}
*/
readRedone () {
return new ID(this.clientDecoder.read(), this.redoneClockDecoder.read())
}
/**
* Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created.

View File

@ -50,6 +50,14 @@ export class UpdateEncoderV1 extends DSEncoderV1 {
encoding.writeVarUint(this.restEncoder, id.clock)
}
/**
* @param {ID} id
*/
writeRedone (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
@ -177,6 +185,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
this.clientEncoder = new encoding.UintOptRleEncoder()
this.leftClockEncoder = new encoding.IntDiffOptRleEncoder()
this.rightClockEncoder = new encoding.IntDiffOptRleEncoder()
this.redoneClockEncoder = new encoding.IntDiffOptRleEncoder()
this.infoEncoder = new encoding.RleEncoder(encoding.writeUint8)
this.stringEncoder = new encoding.StringEncoder()
this.parentInfoEncoder = new encoding.RleEncoder(encoding.writeUint8)
@ -191,6 +200,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
encoding.writeVarUint8Array(encoder, this.clientEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.redoneClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.infoEncoder))
encoding.writeVarUint8Array(encoder, this.stringEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.parentInfoEncoder))
@ -217,6 +227,14 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
this.rightClockEncoder.write(id.clock)
}
/**
* @param {ID} id
*/
writeRedone (id) {
this.clientEncoder.write(id.client)
this.redoneClockEncoder.write(id.clock)
}
/**
* @param {number} client
*/

View File

@ -127,7 +127,7 @@ export const readClientsStructRefs = (decoder, doc) => {
clientRefs.set(client, { i: 0, refs })
for (let i = 0; i < numberOfStructs; i++) {
const info = decoder.readInfo()
switch (binary.BITS5 & info) {
switch (binary.BITS4 & info) {
case 0: { // GC
const len = decoder.readLen()
refs[i] = new GC(createID(client, clock), len)
@ -160,6 +160,7 @@ export const readClientsStructRefs = (decoder, doc) => {
(info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
(info & binary.BIT5) === binary.BIT5 ? decoder.readRedone() : null, // redone
readItemContent(decoder, info) // item content
)
/* A non-optimized implementation of the above algorithm:
@ -184,6 +185,7 @@ export const readClientsStructRefs = (decoder, doc) => {
rightOrigin, // right origin
cantCopyParentInfo && !hasParentYKey ? decoder.readLeftID() : (parentYKey !== null ? doc.get(parentYKey) : null), // parent
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
(info & binary.BIT5) === binary.BIT5 ? decoder.readRedone() : null, // redone
readItemContent(decoder, info) // item content
)
*/

View File

@ -53,7 +53,7 @@ function * lazyStructReaderGenerator (decoder) {
const len = decoding.readVarUint(decoder.restDecoder)
yield new Skip(createID(client, clock), len)
clock += len
} else if ((binary.BITS5 & info) !== 0) {
} else if ((binary.BITS4 & info) !== 0) {
const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
// If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
// and we read the next string as parentYKey.
@ -68,6 +68,7 @@ function * lazyStructReaderGenerator (decoder) {
// @ts-ignore Force writing a string here.
cantCopyParentInfo ? (decoder.readParentInfo() ? decoder.readString() : decoder.readLeftID()) : null, // parent
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
(info & binary.BIT5) === binary.BIT5 ? decoder.readRedone() : null, // redone
readItemContent(decoder, info) // item content
)
yield struct
@ -316,6 +317,7 @@ const sliceStruct = (left, diff) => {
leftItem.rightOrigin,
leftItem.parent,
leftItem.parentSub,
null,
leftItem.content.splice(diff)
)
}

View File

@ -120,6 +120,6 @@ export const testRelativePositionWithUndo = tc => {
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc, false)?.index === 6)
const ydocClone = new Y.Doc()
Y.applyUpdate(ydocClone, Y.encodeStateAsUpdate(ydoc))
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone)?.index === 6)
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone)?.index === 1)
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone, false)?.index === 6)
}

View File

@ -2079,7 +2079,7 @@ export const testBestCase = _tc => {
/**
* @type {Y.Item}
*/
const n = new Y.Item(Y.createID(0, 0), null, null, null, null, null, null, c)
const n = new Y.Item(Y.createID(0, 0), null, null, null, null, null, null, null, c)
// items.push(n)
items[i] = n
n.right = prevItem