diff --git a/src/index.js b/src/index.js index 2db9148a..44dbdcbf 100644 --- a/src/index.js +++ b/src/index.js @@ -26,5 +26,8 @@ export { getState, getStates, readStatesAsMap, - writeStates + writeStates, + readDeleteSet, + writeDeleteSet, + createDeleteSetFromStructStore } from './internals.js' diff --git a/src/structs/AbstractItem.js b/src/structs/AbstractItem.js index eb6e6367..c5be85f2 100644 --- a/src/structs/AbstractItem.js +++ b/src/structs/AbstractItem.js @@ -12,7 +12,7 @@ import { addToDeleteSet, ItemDeleted, findRootTypeKey, - ID, AbstractType, Y, Transaction // eslint-disable-line + StructStore, ID, AbstractType, Y, Transaction // eslint-disable-line } from '../internals.js' import * as error from 'lib0/error.js' @@ -24,16 +24,23 @@ import * as binary from 'lib0/binary.js' /** * Split leftItem into two items - * @param {Transaction} transaction + * @param {StructStore} store * @param {AbstractItem} leftItem * @param {number} diff * @return {AbstractItem} */ -export const splitItem = (transaction, leftItem, diff) => { +export const splitItem = (store, leftItem, diff) => { const id = leftItem.id // create rightItem - const rightItem = leftItem.copy(createID(id.client, id.clock + diff), leftItem, leftItem.rightOrigin, leftItem.parent, leftItem.parentSub) - rightItem.right = leftItem.right + const rightItem = leftItem.copy( + createID(id.client, id.clock + diff), + leftItem, + leftItem.lastId, + leftItem.right, + leftItem.rightOrigin, + leftItem.parent, + leftItem.parentSub + ) if (leftItem.deleted) { rightItem.deleted = true } @@ -43,20 +50,7 @@ export const splitItem = (transaction, leftItem, diff) => { if (rightItem.right !== null) { rightItem.right.left = rightItem } - // update all origins to the right - // search all relevant items to the right and update origin - // if origin is not it foundOrigins, we don't have to search any longer - const foundOrigins = new Set() - foundOrigins.add(leftItem) - let o = rightItem.right - while (o !== null && foundOrigins.has(o.origin)) { - if (o.origin === leftItem) { - o.origin = rightItem - } - foundOrigins.add(o) - o = o.right - } - return leftItem.splitAt(transaction, diff) + return rightItem } /** @@ -66,11 +60,13 @@ export class AbstractItem extends AbstractStruct { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType | null} parent * @param {string | null} parentSub */ - constructor (id, left, right, parent, parentSub) { + constructor (id, left, origin, right, rightOrigin, parent, parentSub) { if (left !== null) { parent = left.parent parentSub = left.parentSub @@ -83,10 +79,10 @@ export class AbstractItem extends AbstractStruct { super(id) /** * The item that was originally to the left of this item. - * @type {AbstractItem | null} + * @type {ID | null} * @readonly */ - this.origin = left + this.origin = origin /** * The item that is currently to the left of this item. * @type {AbstractItem | null} @@ -100,9 +96,9 @@ export class AbstractItem extends AbstractStruct { /** * The item that was originally to the right of this item. * @readonly - * @type {AbstractItem | null} + * @type {ID | null} */ - this.rightOrigin = right + this.rightOrigin = rightOrigin /** * The parent type. * @type {AbstractType} @@ -264,13 +260,15 @@ export class AbstractItem extends AbstractStruct { * Creates an Item with the same effect as this Item (without position effect) * * @param {ID} id - * @param {AbstractItem|null} left - * @param {AbstractItem|null} right + * @param {AbstractItem | null} left + * @param {ID | null} origin + * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent - * @param {string|null} parentSub + * @param {string | null} parentSub * @return {AbstractItem} */ - copy (id, left, right, parent, parentSub) { + copy (id, left, origin, right, rightOrigin, parent, parentSub) { throw new Error('unimplemented') } @@ -329,7 +327,7 @@ export class AbstractItem extends AbstractStruct { right = right._right } } - this.redone = this.copy(nextID(transaction), left, right, parent, this.parentSub) + this.redone = this.copy(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, this.parentSub) this.redone.integrate(transaction) return true } @@ -374,11 +372,11 @@ export class AbstractItem extends AbstractStruct { * * This method should only be cally by StructStore. * - * @param {Transaction} transaction + * @param {StructStore} store * @param {number} diff * @return {AbstractItem} */ - splitAt (transaction, diff) { + splitAt (store, diff) { throw new Error('unimplemented') } @@ -412,9 +410,18 @@ export class AbstractItem extends AbstractStruct { * @return {GC|ItemDeleted} */ gc (y) { - const r = this.parent._item !== null && this.parent._item.deleted - ? new GC(this.id, this.length) - : new ItemDeleted(this.id, this.left, this.right, this.parent, this.parentSub, this.length) + let r + if (this.parent._item !== null && this.parent._item.deleted) { + r = new GC(this.id, this.length) + } else { + r = new ItemDeleted(this.id, this.left, this.origin, this.right, this.rightOrigin, this.parent, this.parentSub, this.length) + if (r.left !== null) { + r.left.right = r + } + if (r.right !== null) { + r.right.left = r + } + } replaceStruct(y.store, this, r) return r } @@ -445,13 +452,13 @@ export class AbstractItem extends AbstractStruct { encoding.writeUint8(encoder, info) if (this.origin !== null) { if (offset === 0) { - writeID(encoder, this.origin.lastId) + writeID(encoder, this.origin) } else { writeID(encoder, createID(this.id.client, this.id.clock + offset - 1)) } } if (this.rightOrigin !== null) { - writeID(encoder, this.rightOrigin.id) + writeID(encoder, this.rightOrigin) } if (this.origin === null && this.rightOrigin === null) { const parent = this.parent diff --git a/src/structs/GC.js b/src/structs/GC.js index cf6173d7..f2951b45 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -34,6 +34,8 @@ export class GC extends AbstractStruct { return this._len } + delete () {} + /** * @param {AbstractStruct} right * @return {boolean} diff --git a/src/structs/ItemBinary.js b/src/structs/ItemBinary.js index b4b8f342..b790746f 100644 --- a/src/structs/ItemBinary.js +++ b/src/structs/ItemBinary.js @@ -24,13 +24,15 @@ export class ItemBinary extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {ArrayBuffer} content */ - constructor (id, left, right, parent, parentSub, content) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) { + super(id, left, origin, right, rightOrigin, parent, parentSub) this.content = content } getContent () { @@ -39,12 +41,14 @@ export class ItemBinary extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemBinary(id, left, right, parent, parentSub, this.content) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemBinary(id, left, origin, right, rightOrigin, parent, parentSub, this.content) } /** * @param {encoding.Encoder} encoder @@ -94,8 +98,10 @@ export class ItemBinaryRef extends AbstractItemRef { return new ItemBinary( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.content diff --git a/src/structs/ItemDeleted.js b/src/structs/ItemDeleted.js index a7b4a354..b4d32603 100644 --- a/src/structs/ItemDeleted.js +++ b/src/structs/ItemDeleted.js @@ -12,6 +12,7 @@ import { getItemType, changeItemRefOffset, GC, + compareIDs, Transaction, ID, AbstractType // eslint-disable-line } from '../internals.js' @@ -24,13 +25,15 @@ export class ItemDeleted extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {number} length */ - constructor (id, left, right, parent, parentSub, length) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, length) { + super(id, left, origin, right, rightOrigin, parent, parentSub) this._len = length this.deleted = true } @@ -40,19 +43,21 @@ export class ItemDeleted extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemDeleted(id, left, right, parent, parentSub, this.length) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemDeleted(id, left, origin, right, rightOrigin, parent, parentSub, this.length) } /** * @param {ItemDeleted} right * @return {boolean} */ mergeWith (right) { - if (right.origin === this && this.right === right) { + if (compareIDs(right.origin, this.lastId) && this.right === right) { this._len += right.length return true } @@ -113,8 +118,10 @@ export class ItemDeletedRef extends AbstractItemRef { return new ItemDeleted( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.len diff --git a/src/structs/ItemEmbed.js b/src/structs/ItemEmbed.js index f9648fa0..8b39a564 100644 --- a/src/structs/ItemEmbed.js +++ b/src/structs/ItemEmbed.js @@ -22,24 +22,28 @@ export class ItemEmbed extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {Object} embed */ - constructor (id, left, right, parent, parentSub, embed) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, embed) { + super(id, left, origin, right, rightOrigin, parent, parentSub) this.embed = embed } /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemEmbed(id, left, right, parent, parentSub, this.embed) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemEmbed(id, left, origin, right, rightOrigin, parent, parentSub, this.embed) } /** * @param {encoding.Encoder} encoder @@ -89,8 +93,10 @@ export class ItemEmbedRef extends AbstractItemRef { return new ItemEmbed( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.embed diff --git a/src/structs/ItemFormat.js b/src/structs/ItemFormat.js index f699266d..e2618abc 100644 --- a/src/structs/ItemFormat.js +++ b/src/structs/ItemFormat.js @@ -22,26 +22,30 @@ export class ItemFormat extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {string} key * @param {any} value */ - constructor (id, left, right, parent, parentSub, key, value) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, key, value) { + super(id, left, origin, right, rightOrigin, parent, parentSub) this.key = key this.value = value } /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemFormat(id, left, right, parent, parentSub, this.key, this.value) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemFormat(id, left, origin, right, rightOrigin, parent, parentSub, this.key, this.value) } get countable () { return false @@ -96,8 +100,10 @@ export class ItemFormatRef extends AbstractItemRef { return new ItemFormat( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.key, diff --git a/src/structs/ItemJSON.js b/src/structs/ItemJSON.js index e5f2ceec..b927b4b8 100644 --- a/src/structs/ItemJSON.js +++ b/src/structs/ItemJSON.js @@ -10,9 +10,10 @@ import { getItemType, splitItem, changeItemRefOffset, + compareIDs, GC, ItemDeleted, - Transaction, ID, AbstractType // eslint-disable-line + StructStore, Transaction, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -24,13 +25,15 @@ export class ItemJSON extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {Array} content */ - constructor (id, left, right, parent, parentSub, content) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) { + super(id, left, origin, right, rightOrigin, parent, parentSub) /** * @type {Array} */ @@ -39,12 +42,14 @@ export class ItemJSON extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemJSON(id, left, right, parent, parentSub, this.content) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemJSON(id, left, origin, right, rightOrigin, parent, parentSub, this.content) } get length () { return this.content.length @@ -53,15 +58,15 @@ export class ItemJSON extends AbstractItem { return this.content } /** - * @param {Transaction} transaction + * @param {StructStore} store * @param {number} diff */ - splitAt (transaction, diff) { + splitAt (store, diff) { /** * @type {ItemJSON} */ // @ts-ignore - const right = splitItem(transaction, this, diff) + const right = splitItem(this, diff) right.content = this.content.splice(diff) return right } @@ -70,7 +75,7 @@ export class ItemJSON extends AbstractItem { * @return {boolean} */ mergeWith (right) { - if (right.origin === this && this.right === right) { + if (compareIDs(right.origin, this.lastId) && this.right === right) { this.content = this.content.concat(right.content) return true } @@ -144,8 +149,10 @@ export class ItemJSONRef extends AbstractItemRef { } return new ItemJSON( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.content diff --git a/src/structs/ItemString.js b/src/structs/ItemString.js index e71ea692..532e243d 100644 --- a/src/structs/ItemString.js +++ b/src/structs/ItemString.js @@ -9,27 +9,30 @@ import { getItemType, splitItem, changeItemRefOffset, + compareIDs, ItemDeleted, GC, - Transaction, ID, AbstractType // eslint-disable-line + StructStore, Transaction, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' import * as decoding from 'lib0/decoding.js' export const structStringRefNumber = 6 - +// TODO: we can probably try to omit rightOrigin. We can just use .right export class ItemString extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {string} string */ - constructor (id, left, right, parent, parentSub, string) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, string) { + super(id, left, origin, right, rightOrigin, parent, parentSub) /** * @type {string} */ @@ -38,12 +41,14 @@ export class ItemString extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub */ - copy (id, left, right, parent, parentSub) { - return new ItemString(id, left, right, parent, parentSub, this.string) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemString(id, left, origin, right, rightOrigin, parent, parentSub, this.string) } getContent () { return this.string.split('') @@ -52,16 +57,16 @@ export class ItemString extends AbstractItem { return this.string.length } /** - * @param {Transaction} transaction + * @param {StructStore} store * @param {number} diff * @return {ItemString} */ - splitAt (transaction, diff) { + splitAt (store, diff) { /** * @type {ItemString} */ // @ts-ignore - const right = splitItem(transaction, this, diff) + const right = splitItem(store, this, diff) right.string = this.string.slice(diff) this.string = this.string.slice(0, diff) return right @@ -71,7 +76,7 @@ export class ItemString extends AbstractItem { * @return {boolean} */ mergeWith (right) { - if (right.origin === this && this.right === right) { + if (compareIDs(right.origin, this.lastId) && this.right === right) { this.string += right.string return true } @@ -132,8 +137,10 @@ export class ItemStringRef extends AbstractItemRef { return new ItemString( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.string diff --git a/src/structs/ItemType.js b/src/structs/ItemType.js index 4fc9e6c3..69bb3fa3 100644 --- a/src/structs/ItemType.js +++ b/src/structs/ItemType.js @@ -61,13 +61,15 @@ export class ItemType extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @param {AbstractType} type */ - constructor (id, left, right, parent, parentSub, type) { - super(id, left, right, parent, parentSub) + constructor (id, left, origin, right, rightOrigin, parent, parentSub, type) { + super(id, left, origin, right, rightOrigin, parent, parentSub) this.type = type } @@ -77,13 +79,15 @@ export class ItemType extends AbstractItem { /** * @param {ID} id * @param {AbstractItem | null} left + * @param {ID | null} origin * @param {AbstractItem | null} right + * @param {ID | null} rightOrigin * @param {AbstractType} parent * @param {string | null} parentSub * @return {AbstractItem} TODO, returns itemtype */ - copy (id, left, right, parent, parentSub) { - return new ItemType(id, left, right, parent, parentSub, this.type._copy()) + copy (id, left, origin, right, rightOrigin, parent, parentSub) { + return new ItemType(id, left, origin, right, rightOrigin, parent, parentSub, this.type._copy()) } /** * @param {Transaction} transaction @@ -189,10 +193,13 @@ export class ItemTypeRef extends AbstractItemRef { parent = y.get(this.parentYKey) } + // TODO: we can probably only feed AbstractType with origins return new ItemType( this.id, - this.left === null ? null : getItemCleanEnd(store, transaction, this.left), - this.right === null ? null : getItemCleanStart(store, transaction, this.right), + this.left === null ? null : getItemCleanEnd(store, this.left), + this.left, + this.right === null ? null : getItemCleanStart(store, this.right), + this.right, parent, this.parentSub, this.type diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index a3342fbd..63770a59 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -335,7 +335,7 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, let jsonContent = [] const packJsonContent = () => { if (jsonContent.length > 0) { - const item = new ItemJSON(nextID(transaction), left, right, parent, null, jsonContent) + const item = new ItemJSON(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, jsonContent) item.integrate(transaction) jsonContent = [] } @@ -353,11 +353,11 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, switch (c.constructor) { case ArrayBuffer: // @ts-ignore c is definitely an ArrayBuffer - new ItemBinary(nextID(transaction), left, right, parent, null, c).integrate(transaction) + new ItemBinary(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, c).integrate(transaction) break default: if (c instanceof AbstractType) { - new ItemType(nextID(transaction), left, right, parent, null, c).integrate(transaction) + new ItemType(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, c).integrate(transaction) } else { throw new Error('Unexpected content type in insert operation') } @@ -383,7 +383,7 @@ export const typeArrayInsertGenerics = (transaction, parent, index, content) => if (index <= n.length) { if (index < n.length) { // insert in-between - getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index)) + getItemCleanStart(transaction.y.store, createID(n.id.client, n.id.clock + index)) } break } @@ -405,7 +405,7 @@ export const typeArrayDelete = (transaction, parent, index, length) => { if (!n.deleted && n.countable) { if (index <= n.length) { if (index < n.length) { - n = getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index)) + n = getItemCleanStart(transaction.y.store, createID(n.id.client, n.id.clock + index)) } break } @@ -415,7 +415,7 @@ export const typeArrayDelete = (transaction, parent, index, length) => { while (length > 0 && n !== null) { if (!n.deleted) { if (length < n.length) { - getItemCleanEnd(transaction.y.store, transaction, createID(n.id.client, n.id.clock + length)) + getItemCleanEnd(transaction.y.store, createID(n.id.client, n.id.clock + length)) } n.delete(transaction) length -= n.length @@ -445,7 +445,7 @@ export const typeMapDelete = (transaction, parent, key) => { export const typeMapSet = (transaction, parent, key, value) => { const right = parent._map.get(key) || null if (value == null) { - new ItemJSON(nextID(transaction), null, right, parent, key, [value]).integrate(transaction) + new ItemJSON(nextID(transaction), null, null, right, right === null ? null : right.id, parent, key, [value]).integrate(transaction) return } switch (value.constructor) { @@ -453,14 +453,14 @@ export const typeMapSet = (transaction, parent, key, value) => { case Object: case Array: case String: - new ItemJSON(nextID(transaction), null, right, parent, key, [value]).integrate(transaction) + new ItemJSON(nextID(transaction), null, null, right, right === null ? null : right.id, parent, key, [value]).integrate(transaction) break case ArrayBuffer: - new ItemBinary(nextID(transaction), null, right, parent, key, value).integrate(transaction) + new ItemBinary(nextID(transaction), null, null, right, right === null ? null : right.id, parent, key, value).integrate(transaction) break default: if (value instanceof AbstractType) { - new ItemType(nextID(transaction), null, right, parent, key, value).integrate(transaction) + new ItemType(nextID(transaction), null, null, right, right === null ? null : right.id, parent, key, value).integrate(transaction) } else { throw new Error('Unexpected content type') } diff --git a/src/types/YText.js b/src/types/YText.js index cc018b98..4482c9ca 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -36,7 +36,7 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co case ItemString: if (!right.deleted) { if (count < right.length) { - right = getItemCleanStart(store, transaction, createID(right.id.client, right.id.clock + count)) + right = getItemCleanStart(store, createID(right.id.client, right.id.clock + count)) left = right.left count = 0 } else { @@ -102,7 +102,7 @@ const insertNegatedAttributes = (transaction, parent, left, right, negatedAttrib right = right.right } for (let [key, val] of negatedAttributes) { - left = new ItemFormat(nextID(transaction), left, right, parent, null, key, val) + left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val) left.integrate(transaction) } return {left, right} @@ -171,7 +171,7 @@ const insertAttributes = (transaction, parent, left, right, currentAttributes, a if (currentVal !== val) { // save negated attribute (set null if currentVal undefined) negatedAttributes.set(key, currentVal || null) - left = new ItemFormat(nextID(transaction), left, right, parent, null, key, val) + left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val) left.integrate(transaction) } } @@ -199,9 +199,9 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a const insertPos = insertAttributes(transaction, parent, minPos.left, minPos.right, currentAttributes, attributes) // insert content if (text.constructor === String) { - left = new ItemString(nextID(transaction), insertPos.left, insertPos.right, parent, null, text) + left = new ItemString(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, text) } else { - left = new ItemEmbed(nextID(transaction), insertPos.left, insertPos.right, parent, null, text) + left = new ItemEmbed(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, text) } left.integrate(transaction) return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes) @@ -249,7 +249,7 @@ const formatText = (transaction, parent, left, right, currentAttributes, length, case ItemEmbed: case ItemString: if (length < right.length) { - getItemCleanStart(transaction.y.store, transaction, createID(right.id.client, right.id.clock + length)) + getItemCleanStart(transaction.y.store, createID(right.id.client, right.id.clock + length)) } length -= right.length break @@ -282,7 +282,7 @@ const deleteText = (transaction, parent, left, right, currentAttributes, length) case ItemEmbed: case ItemString: if (length < right.length) { - getItemCleanStart(transaction.y.store, transaction, createID(right.id.client, right.id.clock + length)) + getItemCleanStart(transaction.y.store, createID(right.id.client, right.id.clock + length)) } length -= right.length right.delete(transaction) diff --git a/src/utils/DeleteSet.js b/src/utils/DeleteSet.js index dd3d8493..c82e1d99 100644 --- a/src/utils/DeleteSet.js +++ b/src/utils/DeleteSet.js @@ -60,8 +60,10 @@ export const findIndexDS = (dis, clock) => { return midindex } left = midindex - } else { + } else if (right !== midindex) { right = midindex + } else { + break } } return null @@ -125,14 +127,16 @@ export const createDeleteSetFromStructStore = ss => { const dsitems = [] for (let i = 0; i < structs.length; i++) { const struct = structs[i] - const clock = struct.id.clock - let len = struct.length - if (i + 1 < structs.length) { - for (let next = structs[i + 1]; i + 1 < structs.length && next.id.clock === clock + len; i++) { - len += next.length + if (struct.deleted) { + const clock = struct.id.clock + let len = struct.length + if (i + 1 < structs.length) { + for (let next = structs[i + 1]; i + 1 < structs.length && next.id.clock === clock + len; i++) { + len += next.length + } } + dsitems.push(new DeleteItem(clock, len)) } - dsitems.push(new DeleteItem(clock, len)) } if (dsitems.length > 0) { ds.clients.set(client, dsitems) @@ -172,7 +176,7 @@ export const readDeleteSet = (decoder, ss, transaction) => { for (let i = 0; i < len; i++) { const clock = decoding.readVarUint(decoder) const len = decoding.readVarUint(decoder) - getItemRange(ss, transaction, client, clock, len).forEach(struct => struct.delete(transaction)) + getItemRange(ss, client, clock, len).forEach(struct => struct.delete(transaction)) } } } diff --git a/src/utils/ID.js b/src/utils/ID.js index 114f7c47..133fb449 100644 --- a/src/utils/ID.js +++ b/src/utils/ID.js @@ -50,8 +50,8 @@ export class ID { } /** - * @param {ID} a - * @param {ID} b + * @param {ID | null} a + * @param {ID | null} b * @return {boolean} */ export const compareIDs = (a, b) => a === b || (a !== null && b !== null && a.client === b.client && a.clock === b.clock) diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 14c18f00..b07c417d 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -129,13 +129,12 @@ export const getItemType = (store, id) => find(store, id) /** * Expects that id is actually in store. This function throws or is an infinite loop otherwise. * @param {StructStore} store - * @param {Transaction} transaction * @param {ID} id * @return {AbstractItem} * * @private */ -export const getItemCleanStart = (store, transaction, id) => { +export const getItemCleanStart = (store, id) => { /** * @type {Array} */ @@ -147,7 +146,7 @@ export const getItemCleanStart = (store, transaction, id) => { */ let struct = structs[index] if (struct.id.clock < id.clock) { - struct = struct.splitAt(transaction, id.clock - struct.id.clock) + struct = struct.splitAt(store, id.clock - struct.id.clock) structs.splice(index, 0, struct) } return struct @@ -156,13 +155,12 @@ export const getItemCleanStart = (store, transaction, id) => { /** * Expects that id is actually in store. This function throws or is an infinite loop otherwise. * @param {StructStore} store - * @param {Transaction} transaction * @param {ID} id * @return {AbstractItem} * * @private */ -export const getItemCleanEnd = (store, transaction, id) => { +export const getItemCleanEnd = (store, id) => { /** * @type {Array} */ @@ -171,7 +169,7 @@ export const getItemCleanEnd = (store, transaction, id) => { const index = findIndexSS(structs, id.clock) const struct = structs[index] if (id.clock !== struct.id.clock + struct.length - 1) { - structs.splice(index, 0, struct.splitAt(transaction, id.clock - struct.id.clock + 1)) + structs.splice(index, 0, struct.splitAt(store, id.clock - struct.id.clock + 1)) } return struct } @@ -179,7 +177,6 @@ export const getItemCleanEnd = (store, transaction, id) => { /** * Expects that id is actually in store. This function throws or is an infinite loop otherwise. * @param {StructStore} store - * @param {Transaction} transaction * @param {number} client * @param {number} clock * @param {number} len @@ -187,7 +184,7 @@ export const getItemCleanEnd = (store, transaction, id) => { * * @private */ -export const getItemRange = (store, transaction, client, clock, len) => { +export const getItemRange = (store, client, clock, len) => { /** * @type {Array} */ @@ -196,17 +193,24 @@ export const getItemRange = (store, transaction, client, clock, len) => { let index = findIndexSS(structs, clock) let struct = structs[index] let range = [] - if (struct.id.clock < clock) { - struct = struct.splitAt(transaction, clock - struct.id.clock) - structs.splice(index, 0, struct) - } - while (struct.id.clock + struct.length <= clock + len) { + if (struct.id.clock <= clock) { + if (struct.id.clock < clock) { + struct = struct.splitAt(store, clock - struct.id.clock) + structs.splice(index, 0, struct) + } range.push(struct) - struct = structs[++index] } - if (clock < struct.id.clock + struct.length) { - structs.splice(index, 0, struct.splitAt(transaction, clock + len - struct.id.clock)) - range.push(struct) + index++ + while (index < structs.length) { + struct = structs[index++] + if (struct.id.clock < clock + len) { + range.push(struct) + } else { + break + } + } + if (struct.id.clock < clock + len && struct.id.clock + struct.length > clock + len) { + structs.splice(index, 0, struct.splitAt(store, clock + len - struct.id.clock)) } return range } diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index f446429d..66a04fb7 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -86,8 +86,8 @@ export class Transaction { get updateMessage () { if (this._updateMessage === null) { const encoder = encoding.createEncoder() - writeStructsFromTransaction(encoder, this) sortAndMergeDeleteSet(this.deleteSet) + writeStructsFromTransaction(encoder, this) writeDeleteSet(encoder, this.deleteSet) this._updateMessage = encoder } diff --git a/src/utils/structEncoding.js b/src/utils/structEncoding.js index b5980b4d..887f9e49 100644 --- a/src/utils/structEncoding.js +++ b/src/utils/structEncoding.js @@ -140,12 +140,13 @@ export const readStructs = (decoder, transaction, store) => { */ const stack = [] const localState = getStates(store) + let lastStructReader = null for (let i = 0; i < clientbeforeState; i++) { const nextID = readID(decoder) const decoderPos = decoder.pos + decoding.readUint32(decoder) - const structReaderDecoder = decoding.clone(decoder, decoderPos) - const numberOfStructs = decoding.readVarUint(structReaderDecoder) - structReaders.set(nextID.client, createStructReaderIterator(structReaderDecoder, numberOfStructs, nextID, localState.get(nextID.client) || 0)) + lastStructReader = decoding.clone(decoder, decoderPos) + const numberOfStructs = decoding.readVarUint(lastStructReader) + structReaders.set(nextID.client, createStructReaderIterator(lastStructReader, numberOfStructs, nextID, localState.get(nextID.client) || 0)) } for (const it of structReaders.values()) { // todo try for in of it @@ -172,4 +173,8 @@ export const readStructs = (decoder, transaction, store) => { } } } + // if we read some structs, this points to the end of the transaction + if (lastStructReader !== null) { + decoder.pos = lastStructReader.pos + } } diff --git a/tests/testHelper.js b/tests/testHelper.js index d25187b2..4baddabe 100644 --- a/tests/testHelper.js +++ b/tests/testHelper.js @@ -321,8 +321,8 @@ export const compareStructStores = (ss1, ss2) => { !(s2 instanceof AbstractItem) || !compareItemIDs(s1.left, s2.left) || !compareItemIDs(s1.right, s2.right) || - !compareItemIDs(s1.origin, s2.origin) || - !compareItemIDs(s1.rightOrigin, s2.rightOrigin) || + !Y.compareIDs(s1.origin, s2.origin) || + !Y.compareIDs(s1.rightOrigin, s2.rightOrigin) || s1.parentSub !== s2.parentSub ) { t.fail('Items dont match')