diff --git a/src/structs/AbstractItem.js b/src/structs/AbstractItem.js index 502e9767..0b1f873e 100644 --- a/src/structs/AbstractItem.js +++ b/src/structs/AbstractItem.js @@ -29,11 +29,12 @@ import * as binary from 'lib0/binary.js' /** * Split leftItem into two items + * @param {Transaction} transaction * @param {AbstractItem} leftItem * @param {number} diff * @return {AbstractItem} */ -export const splitItem = (leftItem, diff) => { +export const splitItem = (transaction, leftItem, diff) => { const id = leftItem.id // create rightItem const rightItem = leftItem.copy( @@ -54,6 +55,8 @@ export const splitItem = (leftItem, diff) => { if (rightItem.right !== null) { rightItem.right.left = rightItem } + // right is more specific. + transaction._replacedItems.add(rightItem) return rightItem } @@ -357,10 +360,11 @@ export class AbstractItem extends AbstractStruct { * * This method should only be cally by StructStore. * + * @param {Transaction} transaction * @param {number} diff * @return {AbstractItem} */ - splitAt (diff) { + splitAt (transaction, diff) { throw new Error('unimplemented') } @@ -549,7 +553,7 @@ export const changeItemRefOffset = (item, offset) => { * Outsourcing some of the logic of computing the item params from a received struct. * If parent === null, it is expected to gc the read struct. Otherwise apply it. * - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {ID|null} leftid * @param {ID|null} rightid @@ -558,13 +562,9 @@ export const changeItemRefOffset = (item, offset) => { * @param {string|null} parentYKey * @return {{left:AbstractItem?,right:AbstractItem?,parent:AbstractType?,parentSub:string?}} */ -export const computeItemParams = (y, store, leftid, rightid, parentid, parentSub, parentYKey) => { - const left = leftid === null ? null : getItemCleanEnd(store, leftid) - if (left !== null && left.constructor !== GC && left.right !== null && left.right.id.client === left.id.client && left.right.id.clock === left.id.clock + left.length) { - // we split a merged op, we may need to merge it again after the transaction - y.transaction._replacedItems.add(left) - } - const right = rightid === null ? null : getItemCleanStart(store, rightid) +export const computeItemParams = (transaction, store, leftid, rightid, parentid, parentSub, parentYKey) => { + const left = leftid === null ? null : getItemCleanEnd(transaction, store, leftid) + const right = rightid === null ? null : getItemCleanStart(transaction, store, rightid) let parent = null if (parentid !== null) { const parentItem = getItemType(store, parentid) @@ -576,7 +576,7 @@ export const computeItemParams = (y, store, leftid, rightid, parentid, parentSub parent = parentItem.type } } else if (parentYKey !== null) { - parent = y.get(parentYKey) + parent = transaction.y.get(parentYKey) } else if (left !== null) { if (left.constructor !== GC) { parent = left.parent diff --git a/src/structs/AbstractStruct.js b/src/structs/AbstractStruct.js index 83e7ed60..acceb870 100644 --- a/src/structs/AbstractStruct.js +++ b/src/structs/AbstractStruct.js @@ -76,12 +76,12 @@ export class AbstractRef { return this._missing } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {AbstractStruct} */ - toStruct (y, store, offset) { + toStruct (transaction, store, offset) { throw error.methodUnimplemented() } /** diff --git a/src/structs/GC.js b/src/structs/GC.js index fb5693d1..c96f7b6c 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -82,12 +82,12 @@ export class GCRef extends AbstractRef { ] } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {GC} */ - toStruct (y, store, offset) { + toStruct (transaction, store, offset) { if (offset > 0) { // @ts-ignore this.id = createID(this.id.client, this.id.clock + offset) diff --git a/src/structs/ItemBinary.js b/src/structs/ItemBinary.js index 645f2c9a..e4c19abd 100644 --- a/src/structs/ItemBinary.js +++ b/src/structs/ItemBinary.js @@ -7,7 +7,7 @@ import { AbstractItemRef, computeItemParams, GC, - StructStore, Y, AbstractType, ID, YEvent // eslint-disable-line + StructStore, Transaction, AbstractType, ID // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -69,13 +69,13 @@ export class ItemBinaryRef extends AbstractItemRef { this.content = decoding.readPayload(decoder) } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemBinary|GC} */ - toStruct (y, store, offset) { - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + toStruct (transaction, store, offset) { + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemBinary( diff --git a/src/structs/ItemDeleted.js b/src/structs/ItemDeleted.js index 7cd1e89f..d444145e 100644 --- a/src/structs/ItemDeleted.js +++ b/src/structs/ItemDeleted.js @@ -57,14 +57,15 @@ export class ItemDeleted extends AbstractItem { addToDeleteSet(transaction.deleteSet, this.id, this.length) } /** + * @param {Transaction} transaction * @param {number} diff */ - splitAt (diff) { + splitAt (transaction, diff) { /** * @type {ItemDeleted} */ // @ts-ignore - const right = splitItem(this, diff) + const right = splitItem(transaction, this, diff) right._len -= diff this._len = diff return right @@ -107,18 +108,18 @@ export class ItemDeletedRef extends AbstractItemRef { return this.len } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemDeleted|GC} */ - toStruct (y, store, offset) { + toStruct (transaction, store, offset) { if (offset > 0) { changeItemRefOffset(this, offset) this.len = this.len - offset } - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemDeleted( diff --git a/src/structs/ItemEmbed.js b/src/structs/ItemEmbed.js index 3f4a9719..24f7a81b 100644 --- a/src/structs/ItemEmbed.js +++ b/src/structs/ItemEmbed.js @@ -7,7 +7,7 @@ import { AbstractItemRef, computeItemParams, GC, - Y, StructStore, ID, AbstractType // eslint-disable-line + Transaction, StructStore, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -66,13 +66,13 @@ export class ItemEmbedRef extends AbstractItemRef { this.embed = JSON.parse(decoding.readVarString(decoder)) } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemEmbed|GC} */ - toStruct (y, store, offset) { - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + toStruct (transaction, store, offset) { + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemEmbed( diff --git a/src/structs/ItemFormat.js b/src/structs/ItemFormat.js index 244aa295..92da6570 100644 --- a/src/structs/ItemFormat.js +++ b/src/structs/ItemFormat.js @@ -7,7 +7,7 @@ import { AbstractItemRef, computeItemParams, GC, - Y, StructStore, ID, AbstractType // eslint-disable-line + Transaction, StructStore, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -73,13 +73,13 @@ export class ItemFormatRef extends AbstractItemRef { this.value = JSON.parse(decoding.readVarString(decoder)) } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemFormat|GC} */ - toStruct (y, store, offset) { - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + toStruct (transaction, store, offset) { + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemFormat( diff --git a/src/structs/ItemJSON.js b/src/structs/ItemJSON.js index aadb71e7..ceb0def3 100644 --- a/src/structs/ItemJSON.js +++ b/src/structs/ItemJSON.js @@ -9,7 +9,7 @@ import { splitItem, changeItemRefOffset, GC, - StructStore, Y, ID, AbstractType // eslint-disable-line + Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -54,14 +54,15 @@ export class ItemJSON extends AbstractItem { return this.content } /** + * @param {Transaction} transaction * @param {number} diff */ - splitAt (diff) { + splitAt (transaction, diff) { /** * @type {ItemJSON} */ // @ts-ignore - const right = splitItem(this, diff) + const right = splitItem(transaction, this, diff) right.content = this.content.splice(diff) return right } @@ -118,17 +119,17 @@ export class ItemJSONRef extends AbstractItemRef { return this.content.length } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemJSON|GC} */ - toStruct (y, store, offset) { + toStruct (transaction, store, offset) { if (offset > 0) { changeItemRefOffset(this, offset) this.content = this.content.slice(offset) } - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemJSON( diff --git a/src/structs/ItemString.js b/src/structs/ItemString.js index b0373c91..24e57f2f 100644 --- a/src/structs/ItemString.js +++ b/src/structs/ItemString.js @@ -8,7 +8,7 @@ import { splitItem, changeItemRefOffset, GC, - StructStore, Y, ID, AbstractType // eslint-disable-line + Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -53,15 +53,16 @@ export class ItemString extends AbstractItem { return this.string.length } /** + * @param {Transaction} transaction * @param {number} diff * @return {ItemString} */ - splitAt (diff) { + splitAt (transaction, diff) { /** * @type {ItemString} */ // @ts-ignore - const right = splitItem(this, diff) + const right = splitItem(transaction, this, diff) right.string = this.string.slice(diff) this.string = this.string.slice(0, diff) return right @@ -104,18 +105,18 @@ export class ItemStringRef extends AbstractItemRef { return this.string.length } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemString|GC} */ - toStruct (y, store, offset) { + toStruct (transaction, store, offset) { if (offset > 0) { changeItemRefOffset(this, offset) this.string = this.string.slice(offset) } - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemString( diff --git a/src/structs/ItemType.js b/src/structs/ItemType.js index 8d16016a..67c11e90 100644 --- a/src/structs/ItemType.js +++ b/src/structs/ItemType.js @@ -150,13 +150,13 @@ export class ItemTypeRef extends AbstractItemRef { this.type = typeRefs[typeRef](decoder) } /** - * @param {Y} y + * @param {Transaction} transaction * @param {StructStore} store * @param {number} offset * @return {ItemType|GC} */ - toStruct (y, store, offset) { - const { left, right, parent, parentSub } = computeItemParams(y, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) + toStruct (transaction, store, offset) { + const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey) return parent === null ? new GC(this.id, this.length) : new ItemType( diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index e39f36fe..76625ab1 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -381,7 +381,7 @@ export const typeArrayInsertGenerics = (transaction, parent, index, content) => if (index <= n.length) { if (index < n.length) { // insert in-between - getItemCleanStart(transaction.y.store, createID(n.id.client, n.id.clock + index)) + getItemCleanStart(transaction, 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 && index > 0) { - n = getItemCleanStart(transaction.y.store, createID(n.id.client, n.id.clock + index)) + n = getItemCleanStart(transaction, transaction.y.store, createID(n.id.client, n.id.clock + index)) } break } @@ -416,7 +416,7 @@ export const typeArrayDelete = (transaction, parent, index, length) => { while (length > 0 && n !== null) { if (!n.deleted) { if (length < n.length) { - getItemCleanStart(transaction.y.store, createID(n.id.client, n.id.clock + length)) + getItemCleanStart(transaction, transaction.y.store, createID(n.id.client, n.id.clock + length)) } n.delete(transaction) length -= n.length diff --git a/src/types/YText.js b/src/types/YText.js index 0c397960..aa0f8fb1 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -39,7 +39,7 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co if (!right.deleted) { if (count < right.length) { // split right - getItemCleanStart(store, createID(right.id.client, right.id.clock + count)) + getItemCleanStart(transaction, store, createID(right.id.client, right.id.clock + count)) } count -= right.length } @@ -251,7 +251,7 @@ const formatText = (transaction, parent, left, right, currentAttributes, length, case ItemEmbed: case ItemString: if (length < right.length) { - getItemCleanStart(transaction.y.store, createID(right.id.client, right.id.clock + length)) + getItemCleanStart(transaction, transaction.y.store, createID(right.id.client, right.id.clock + length)) } length -= right.length break @@ -284,7 +284,7 @@ const deleteText = (transaction, parent, left, right, currentAttributes, length) case ItemEmbed: case ItemString: if (length < right.length) { - getItemCleanStart(transaction.y.store, createID(right.id.client, right.id.clock + length)) + getItemCleanStart(transaction, 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 03c52c54..44417d19 100644 --- a/src/utils/DeleteSet.js +++ b/src/utils/DeleteSet.js @@ -193,7 +193,7 @@ export const readDeleteSet = (decoder, transaction, store) => { let struct = structs[index] // split the first item if necessary if (!struct.deleted && struct.id.clock < clock) { - structs.splice(index + 1, 0, struct.splitAt(clock - struct.id.clock)) + structs.splice(index + 1, 0, struct.splitAt(transaction, clock - struct.id.clock)) index++ // increase we now want to use the next struct } while (index < structs.length) { @@ -202,7 +202,7 @@ export const readDeleteSet = (decoder, transaction, store) => { if (struct.id.clock < clock + len) { if (!struct.deleted) { if (clock + len < struct.id.clock + struct.length) { - structs.splice(index, 0, struct.splitAt(clock + len - struct.id.clock)) + structs.splice(index, 0, struct.splitAt(transaction, clock + len - struct.id.clock)) } struct.delete(transaction) } diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index ce382e39..3061d5eb 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -2,7 +2,7 @@ import { GC, - AbstractRef, ID, ItemType, AbstractItem, AbstractStruct // eslint-disable-line + Transaction, AbstractRef, ID, ItemType, AbstractItem, AbstractStruct // eslint-disable-line } from '../internals.js' import * as math from 'lib0/math.js' @@ -167,13 +167,15 @@ 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 {Transaction} transaction * @param {StructStore} store * @param {ID} id * @return {AbstractItem} * * @private */ -export const getItemCleanStart = (store, id) => { +export const getItemCleanStart = (transaction, store, id) => { /** * @type {Array} */ @@ -185,7 +187,7 @@ export const getItemCleanStart = (store, id) => { */ let struct = structs[index] if (struct.id.clock < id.clock && struct.constructor !== GC) { - struct = struct.splitAt(id.clock - struct.id.clock) + struct = struct.splitAt(transaction, id.clock - struct.id.clock) structs.splice(index + 1, 0, struct) } return struct @@ -193,13 +195,15 @@ export const getItemCleanStart = (store, id) => { /** * Expects that id is actually in store. This function throws or is an infinite loop otherwise. + * + * @param {Transaction} transaction * @param {StructStore} store * @param {ID} id * @return {AbstractItem} * * @private */ -export const getItemCleanEnd = (store, id) => { +export const getItemCleanEnd = (transaction, store, id) => { /** * @type {Array} */ @@ -208,7 +212,7 @@ export const getItemCleanEnd = (store, id) => { const index = findIndexSS(structs, id.clock) const struct = structs[index] if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) { - structs.splice(index + 1, 0, struct.splitAt(id.clock - struct.id.clock + 1)) + structs.splice(index + 1, 0, struct.splitAt(transaction, id.clock - struct.id.clock + 1)) } return struct } diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 43a2625b..17441505 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -170,7 +170,7 @@ export const transact = (y, f) => { const deleteItem = deleteItems[di] for (let si = findIndexSS(structs, deleteItem.clock); si < structs.length; si++) { const struct = structs[si] - if (deleteItem.clock + deleteItem.len < struct.id.clock) { + if (deleteItem.clock + deleteItem.len <= struct.id.clock) { break } if (struct.deleted && struct instanceof AbstractItem && (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted))) { diff --git a/src/utils/structEncoding.js b/src/utils/structEncoding.js index 7bd220d1..1a800ab0 100644 --- a/src/utils/structEncoding.js +++ b/src/utils/structEncoding.js @@ -207,7 +207,7 @@ const resumeStructIntegration = (transaction, store) => { } if (m.length === 0) { if (offset < ref.length) { - ref.toStruct(transaction.y, store, offset).integrate(transaction) + ref.toStruct(transaction, store, offset).integrate(transaction) } stack.pop() } diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 3953a883..06c7d5e5 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -306,6 +306,8 @@ const arrayTransactions = [ } ] +// TODO: http://127.0.0.1:3443/?filter=\[22/&seed=1943600076 + /** * @param {t.TestCase} tc */ diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 5cfe0a2f..586a5f0d 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -348,7 +348,7 @@ const mapTransactions = [ * @param {t.TestCase} tc */ export const testRepeatGeneratingYmapTests10 = tc => { - applyRandomTests(tc, mapTransactions, 4) + applyRandomTests(tc, mapTransactions, 10) } /**