splitting an item must always happen inside a transaction, because we always need to check if we can merge it back
This commit is contained in:
parent
9fe47e98d5
commit
2ef11a5344
@ -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<YEvent>?,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
|
||||
|
@ -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()
|
||||
}
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<AbstractItem>}
|
||||
*/
|
||||
@ -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<AbstractItem>}
|
||||
*/
|
||||
@ -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
|
||||
}
|
||||
|
@ -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))) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -306,6 +306,8 @@ const arrayTransactions = [
|
||||
}
|
||||
]
|
||||
|
||||
// TODO: http://127.0.0.1:3443/?filter=\[22/&seed=1943600076
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
|
@ -348,7 +348,7 @@ const mapTransactions = [
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testRepeatGeneratingYmapTests10 = tc => {
|
||||
applyRandomTests(tc, mapTransactions, 4)
|
||||
applyRandomTests(tc, mapTransactions, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user