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

View File

@ -643,7 +643,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
let jsonContent = [] let jsonContent = []
const packJsonContent = () => { const packJsonContent = () => {
if (jsonContent.length > 0) { 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) left.integrate(transaction, 0)
jsonContent = [] jsonContent = []
} }
@ -665,16 +665,16 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
switch (c.constructor) { switch (c.constructor) {
case Uint8Array: case Uint8Array:
case ArrayBuffer: 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) left.integrate(transaction, 0)
break break
case Doc: 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) left.integrate(transaction, 0)
break break
default: default:
if (c instanceof AbstractType) { 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) left.integrate(transaction, 0)
} else { } else {
throw new Error('Unexpected content type in insert operation') 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) => { negatedAttributes.forEach((val, key) => {
const left = currPos.left const left = currPos.left
const right = currPos.right 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) nextFormat.integrate(transaction, 0)
currPos.right = nextFormat currPos.right = nextFormat
currPos.forward() currPos.forward()
@ -232,7 +232,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
// save negated attribute (set null if currentVal undefined) // save negated attribute (set null if currentVal undefined)
negatedAttributes.set(key, currentVal) negatedAttributes.set(key, currentVal)
const { left, right } = currPos 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.right.integrate(transaction, 0)
currPos.forward() currPos.forward()
} }
@ -266,7 +266,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => {
if (parent._searchMarker) { if (parent._searchMarker) {
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength()) 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) right.integrate(transaction, 0)
currPos.right = right currPos.right = right
currPos.index = index currPos.index = index
@ -342,7 +342,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
for (; length > 0; length--) { for (; length > 0; length--) {
newlines += '\n' 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.right.integrate(transaction, 0)
currPos.forward() currPos.forward()
} }

View File

@ -46,6 +46,13 @@ export class UpdateDecoderV1 extends DSDecoderV1 {
return createID(decoding.readVarUint(this.restDecoder), decoding.readVarUint(this.restDecoder)) 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. * Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created. * 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.clientDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder))
this.leftClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder)) this.leftClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.rightClockDecoder = 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.infoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8)
this.stringDecoder = new decoding.StringDecoder(decoding.readVarUint8Array(decoder)) this.stringDecoder = new decoding.StringDecoder(decoding.readVarUint8Array(decoder))
this.parentInfoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8) 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 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. * Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created. * 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) 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. * Use writeClient and writeClock instead of writeID if possible.
* @param {number} client * @param {number} client
@ -177,6 +185,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
this.clientEncoder = new encoding.UintOptRleEncoder() this.clientEncoder = new encoding.UintOptRleEncoder()
this.leftClockEncoder = new encoding.IntDiffOptRleEncoder() this.leftClockEncoder = new encoding.IntDiffOptRleEncoder()
this.rightClockEncoder = new encoding.IntDiffOptRleEncoder() this.rightClockEncoder = new encoding.IntDiffOptRleEncoder()
this.redoneClockEncoder = new encoding.IntDiffOptRleEncoder()
this.infoEncoder = new encoding.RleEncoder(encoding.writeUint8) this.infoEncoder = new encoding.RleEncoder(encoding.writeUint8)
this.stringEncoder = new encoding.StringEncoder() this.stringEncoder = new encoding.StringEncoder()
this.parentInfoEncoder = new encoding.RleEncoder(encoding.writeUint8) 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.clientEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array()) encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array()) encoding.writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.redoneClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.infoEncoder)) encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.infoEncoder))
encoding.writeVarUint8Array(encoder, this.stringEncoder.toUint8Array()) encoding.writeVarUint8Array(encoder, this.stringEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.parentInfoEncoder)) encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.parentInfoEncoder))
@ -217,6 +227,14 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
this.rightClockEncoder.write(id.clock) this.rightClockEncoder.write(id.clock)
} }
/**
* @param {ID} id
*/
writeRedone (id) {
this.clientEncoder.write(id.client)
this.redoneClockEncoder.write(id.clock)
}
/** /**
* @param {number} client * @param {number} client
*/ */

View File

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

View File

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

View File

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

View File

@ -2079,7 +2079,7 @@ export const testBestCase = _tc => {
/** /**
* @type {Y.Item} * @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.push(n)
items[i] = n items[i] = n
n.right = prevItem n.right = prevItem