Improve memory usage by omitting the ItemRef step and directly applying the Item
This commit is contained in:
		
							parent
							
								
									0a0098fdfb
								
							
						
					
					
						commit
						5293ab4df1
					
				@ -39,39 +39,9 @@ export class AbstractStruct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					   * @param {Transaction} transaction
 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  integrate (transaction) {
 | 
					 | 
				
			||||||
    throw error.methodUnimplemented()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class AbstractStructRef {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @param {ID} id
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  constructor (id) {
 | 
					 | 
				
			||||||
    this.id = id
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @type {Array<ID>}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    this._missing = []
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					 | 
				
			||||||
   * @return {Array<ID|null>}
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  getMissing (transaction) {
 | 
					 | 
				
			||||||
    return this._missing
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					 | 
				
			||||||
   * @param {StructStore} store
 | 
					 | 
				
			||||||
   * @param {number} offset
 | 
					   * @param {number} offset
 | 
				
			||||||
   * @return {AbstractStruct}
 | 
					 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  toStruct (transaction, store, offset) {
 | 
					  integrate (transaction, offset) {
 | 
				
			||||||
    throw error.methodUnimplemented()
 | 
					    throw error.methodUnimplemented()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AbstractStructRef,
 | 
					 | 
				
			||||||
  AbstractStruct,
 | 
					  AbstractStruct,
 | 
				
			||||||
  addStruct,
 | 
					  addStruct,
 | 
				
			||||||
  StructStore, Transaction, ID // eslint-disable-line
 | 
					  StructStore, Transaction, ID // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as decoding from 'lib0/decoding.js'
 | 
					 | 
				
			||||||
import * as encoding from 'lib0/encoding.js'
 | 
					import * as encoding from 'lib0/encoding.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const structGCRefNumber = 0
 | 
					export const structGCRefNumber = 0
 | 
				
			||||||
@ -37,8 +35,13 @@ export class GC extends AbstractStruct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					   * @param {Transaction} transaction
 | 
				
			||||||
 | 
					   * @param {number} offset
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  integrate (transaction) {
 | 
					  integrate (transaction, offset) {
 | 
				
			||||||
 | 
					    if (offset > 0) {
 | 
				
			||||||
 | 
					      this.id.clock += offset
 | 
				
			||||||
 | 
					      this.length -= offset
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    addStruct(transaction.doc.store, this)
 | 
					    addStruct(transaction.doc.store, this)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,39 +53,13 @@ export class GC extends AbstractStruct {
 | 
				
			|||||||
    encoding.writeUint8(encoder, structGCRefNumber)
 | 
					    encoding.writeUint8(encoder, structGCRefNumber)
 | 
				
			||||||
    encoding.writeVarUint(encoder, this.length - offset)
 | 
					    encoding.writeVarUint(encoder, this.length - offset)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @private
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class GCRef extends AbstractStructRef {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @param {decoding.Decoder} decoder
 | 
					 | 
				
			||||||
   * @param {ID} id
 | 
					 | 
				
			||||||
   * @param {number} info
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  constructor (decoder, id, info) {
 | 
					 | 
				
			||||||
    super(id)
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @type {number}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    this.length = decoding.readVarUint(decoder)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					   * @param {Transaction} transaction
 | 
				
			||||||
   * @param {StructStore} store
 | 
					   * @param {StructStore} store
 | 
				
			||||||
   * @param {number} offset
 | 
					   * @return {null | ID}
 | 
				
			||||||
   * @return {GC}
 | 
					 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  toStruct (transaction, store, offset) {
 | 
					  getMissing (transaction, store) {
 | 
				
			||||||
    if (offset > 0) {
 | 
					    return null
 | 
				
			||||||
      this.id.clock += offset
 | 
					 | 
				
			||||||
      this.length -= offset
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return new GC(
 | 
					 | 
				
			||||||
      this.id,
 | 
					 | 
				
			||||||
      this.length
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import {
 | 
				
			|||||||
  writeID,
 | 
					  writeID,
 | 
				
			||||||
  GC,
 | 
					  GC,
 | 
				
			||||||
  getState,
 | 
					  getState,
 | 
				
			||||||
  AbstractStructRef,
 | 
					 | 
				
			||||||
  AbstractStruct,
 | 
					  AbstractStruct,
 | 
				
			||||||
  replaceStruct,
 | 
					  replaceStruct,
 | 
				
			||||||
  addStruct,
 | 
					  addStruct,
 | 
				
			||||||
@ -24,7 +23,7 @@ import {
 | 
				
			|||||||
  readContentFormat,
 | 
					  readContentFormat,
 | 
				
			||||||
  readContentType,
 | 
					  readContentType,
 | 
				
			||||||
  addChangedTypeToTransaction,
 | 
					  addChangedTypeToTransaction,
 | 
				
			||||||
  ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
 | 
					  Doc, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as error from 'lib0/error.js'
 | 
					import * as error from 'lib0/error.js'
 | 
				
			||||||
@ -73,7 +72,7 @@ export const followRedone = (store, id) => {
 | 
				
			|||||||
export const keepItem = (item, keep) => {
 | 
					export const keepItem = (item, keep) => {
 | 
				
			||||||
  while (item !== null && item.keep !== keep) {
 | 
					  while (item !== null && item.keep !== keep) {
 | 
				
			||||||
    item.keep = keep
 | 
					    item.keep = keep
 | 
				
			||||||
    item = item.parent._item
 | 
					    item = /** @type {AbstractType<any>} */ (item.parent)._item
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,7 +118,7 @@ export const splitItem = (transaction, leftItem, diff) => {
 | 
				
			|||||||
  transaction._mergeStructs.push(rightItem)
 | 
					  transaction._mergeStructs.push(rightItem)
 | 
				
			||||||
  // update parent._map
 | 
					  // update parent._map
 | 
				
			||||||
  if (rightItem.parentSub !== null && rightItem.right === null) {
 | 
					  if (rightItem.parentSub !== null && rightItem.right === null) {
 | 
				
			||||||
    rightItem.parent._map.set(rightItem.parentSub, rightItem)
 | 
					    /** @type {AbstractType<any>} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  leftItem.length = diff
 | 
					  leftItem.length = diff
 | 
				
			||||||
  return rightItem
 | 
					  return rightItem
 | 
				
			||||||
@ -144,7 +143,7 @@ export const redoItem = (transaction, item, redoitems) => {
 | 
				
			|||||||
  if (redone !== null) {
 | 
					  if (redone !== null) {
 | 
				
			||||||
    return getItemCleanStart(transaction, redone)
 | 
					    return getItemCleanStart(transaction, redone)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  let parentItem = item.parent._item
 | 
					  let parentItem = /** @type {AbstractType<any>} */ (item.parent)._item
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @type {Item|null}
 | 
					   * @type {Item|null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@ -169,7 +168,7 @@ export const redoItem = (transaction, item, redoitems) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (left.right !== null) {
 | 
					    if (left.right !== null) {
 | 
				
			||||||
      left = /** @type {Item} */ (item.parent._map.get(item.parentSub))
 | 
					      left = /** @type {Item} */ (/** @type {AbstractType<any>} */ (item.parent)._map.get(item.parentSub))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    right = null
 | 
					    right = null
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -191,10 +190,10 @@ export const redoItem = (transaction, item, redoitems) => {
 | 
				
			|||||||
       */
 | 
					       */
 | 
				
			||||||
      let leftTrace = left
 | 
					      let leftTrace = left
 | 
				
			||||||
      // trace redone until parent matches
 | 
					      // trace redone until parent matches
 | 
				
			||||||
      while (leftTrace !== null && leftTrace.parent._item !== parentItem) {
 | 
					      while (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item !== parentItem) {
 | 
				
			||||||
        leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
 | 
					        leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (leftTrace !== null && leftTrace.parent._item === parentItem) {
 | 
					      if (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item === parentItem) {
 | 
				
			||||||
        left = leftTrace
 | 
					        left = leftTrace
 | 
				
			||||||
        break
 | 
					        break
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -206,10 +205,10 @@ export const redoItem = (transaction, item, redoitems) => {
 | 
				
			|||||||
       */
 | 
					       */
 | 
				
			||||||
      let rightTrace = right
 | 
					      let rightTrace = right
 | 
				
			||||||
      // trace redone until parent matches
 | 
					      // trace redone until parent matches
 | 
				
			||||||
      while (rightTrace !== null && rightTrace.parent._item !== parentItem) {
 | 
					      while (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item !== parentItem) {
 | 
				
			||||||
        rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
 | 
					        rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (rightTrace !== null && rightTrace.parent._item === parentItem) {
 | 
					      if (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item === parentItem) {
 | 
				
			||||||
        right = rightTrace
 | 
					        right = rightTrace
 | 
				
			||||||
        break
 | 
					        break
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -228,7 +227,7 @@ export const redoItem = (transaction, item, redoitems) => {
 | 
				
			|||||||
  )
 | 
					  )
 | 
				
			||||||
  item.redone = nextId
 | 
					  item.redone = nextId
 | 
				
			||||||
  keepItem(redoneItem, true)
 | 
					  keepItem(redoneItem, true)
 | 
				
			||||||
  redoneItem.integrate(transaction)
 | 
					  redoneItem.integrate(transaction, 0)
 | 
				
			||||||
  return redoneItem
 | 
					  return redoneItem
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -242,7 +241,7 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
   * @param {ID | null} origin
 | 
					   * @param {ID | null} origin
 | 
				
			||||||
   * @param {Item | null} right
 | 
					   * @param {Item | null} right
 | 
				
			||||||
   * @param {ID | null} rightOrigin
 | 
					   * @param {ID | null} rightOrigin
 | 
				
			||||||
   * @param {AbstractType<any>} parent
 | 
					   * @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 {AbstractContent} content
 | 
					   * @param {AbstractContent} content
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@ -251,7 +250,6 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The item that was originally to the left of this item.
 | 
					     * The item that was originally to the left of this item.
 | 
				
			||||||
     * @type {ID | null}
 | 
					     * @type {ID | null}
 | 
				
			||||||
     * @readonly
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.origin = origin
 | 
					    this.origin = origin
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -266,14 +264,11 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
    this.right = right
 | 
					    this.right = right
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The item that was originally to the right of this item.
 | 
					     * The item that was originally to the right of this item.
 | 
				
			||||||
     * @readonly
 | 
					 | 
				
			||||||
     * @type {ID | null}
 | 
					     * @type {ID | null}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.rightOrigin = rightOrigin
 | 
					    this.rightOrigin = rightOrigin
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The parent type.
 | 
					     * @type {AbstractType<any>|ID|null}
 | 
				
			||||||
     * @type {AbstractType<any>}
 | 
					 | 
				
			||||||
     * @readonly
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.parent = parent
 | 
					    this.parent = parent
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -282,7 +277,6 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
     * to insert this item. If `parentSub = null` type._start is the list in
 | 
					     * to insert this item. If `parentSub = null` type._start is the list in
 | 
				
			||||||
     * which to insert to. Otherwise it is `parent._map`.
 | 
					     * which to insert to. Otherwise it is `parent._map`.
 | 
				
			||||||
     * @type {String | null}
 | 
					     * @type {String | null}
 | 
				
			||||||
     * @readonly
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.parentSub = parentSub
 | 
					    this.parentSub = parentSub
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -311,17 +305,86 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
 | 
					   * Return missing ids, or define missing items and return null.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					   * @param {Transaction} transaction
 | 
				
			||||||
 | 
					   * @param {StructStore} store
 | 
				
			||||||
 | 
					   * @return {null | ID}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  integrate (transaction) {
 | 
					  getMissing (transaction, store) {
 | 
				
			||||||
 | 
					    const origin = this.origin
 | 
				
			||||||
 | 
					    const rightOrigin = this.rightOrigin
 | 
				
			||||||
 | 
					    const parent = /** @type {ID} */ (this.parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (origin && origin.clock >= getState(store, origin.client)) {
 | 
				
			||||||
 | 
					      return this.origin
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (rightOrigin && rightOrigin.clock >= getState(store, rightOrigin.client)) {
 | 
				
			||||||
 | 
					      return this.rightOrigin
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (parent && parent.constructor === ID && parent.clock >= getState(store, parent.client)) {
 | 
				
			||||||
 | 
					      return parent
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We have all missing ids, now find the items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (origin) {
 | 
				
			||||||
 | 
					      this.left = getItemCleanEnd(transaction, store, origin)
 | 
				
			||||||
 | 
					      this.origin = this.left.lastId
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (rightOrigin) {
 | 
				
			||||||
 | 
					      this.right = getItemCleanStart(transaction, rightOrigin)
 | 
				
			||||||
 | 
					      this.rightOrigin = this.right.id
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (parent && parent.constructor === ID) {
 | 
				
			||||||
 | 
					      if (parent.clock < getState(store, parent.client)) {
 | 
				
			||||||
 | 
					        const parentItem = getItem(store, parent)
 | 
				
			||||||
 | 
					        if (parentItem.constructor === GC) {
 | 
				
			||||||
 | 
					          this.parent = null
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          this.parent = /** @type {ContentType} */ (parentItem.content).type
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return parent
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // only set item if this shouldn't be garbage collected
 | 
				
			||||||
 | 
					    if (!this.parent) {
 | 
				
			||||||
 | 
					      if (this.left && this.left.constructor === Item) {
 | 
				
			||||||
 | 
					        this.parent = this.left.parent
 | 
				
			||||||
 | 
					        this.parentSub = this.left.parentSub
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.right && this.right.constructor === Item) {
 | 
				
			||||||
 | 
					        this.parent = this.right.parent
 | 
				
			||||||
 | 
					        this.parentSub = this.right.parentSub
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @param {Transaction} transaction
 | 
				
			||||||
 | 
					   * @param {number} offset
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  integrate (transaction, offset) {
 | 
				
			||||||
    const store = transaction.doc.store
 | 
					    const store = transaction.doc.store
 | 
				
			||||||
    const parent = this.parent
 | 
					    if (offset > 0) {
 | 
				
			||||||
 | 
					      this.id.clock += offset
 | 
				
			||||||
 | 
					      this.left = getItemCleanEnd(transaction, store, createID(this.id.client, this.id.clock - 1))
 | 
				
			||||||
 | 
					      this.origin = this.left.lastId
 | 
				
			||||||
 | 
					      this.content = this.content.splice(offset)
 | 
				
			||||||
 | 
					      this.length -= offset
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    const parentSub = this.parentSub
 | 
					    const parentSub = this.parentSub
 | 
				
			||||||
    const length = this.length
 | 
					    const length = this.length
 | 
				
			||||||
 | 
					    const parent = /** @type {AbstractType<any>|null} */ (this.parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (parent) {
 | 
				
			||||||
      /**
 | 
					      /**
 | 
				
			||||||
       * @type {Item|null}
 | 
					       * @type {Item|null}
 | 
				
			||||||
       */
 | 
					       */
 | 
				
			||||||
      let left = this.left
 | 
					      let left = this.left
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      /**
 | 
					      /**
 | 
				
			||||||
       * @type {Item|null}
 | 
					       * @type {Item|null}
 | 
				
			||||||
       */
 | 
					       */
 | 
				
			||||||
@ -338,6 +401,7 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
        o = parent._start
 | 
					        o = parent._start
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // TODO: use something like DeleteSet here (a tree implementation would be best)
 | 
					      // TODO: use something like DeleteSet here (a tree implementation would be best)
 | 
				
			||||||
 | 
					      // @todo use global set definitions
 | 
				
			||||||
      /**
 | 
					      /**
 | 
				
			||||||
       * @type {Set<Item>}
 | 
					       * @type {Set<Item>}
 | 
				
			||||||
       */
 | 
					       */
 | 
				
			||||||
@ -410,6 +474,10 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
        // delete if parent is deleted or if this is not the current attribute value of parent
 | 
					        // delete if parent is deleted or if this is not the current attribute value of parent
 | 
				
			||||||
        this.delete(transaction)
 | 
					        this.delete(transaction)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // parent is not defined. Integrate GC struct instead
 | 
				
			||||||
 | 
					      new GC(this.id, this.length).integrate(transaction, 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -481,7 +549,7 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  delete (transaction) {
 | 
					  delete (transaction) {
 | 
				
			||||||
    if (!this.deleted) {
 | 
					    if (!this.deleted) {
 | 
				
			||||||
      const parent = this.parent
 | 
					      const parent = /** @type {AbstractType<any>} */ (this.parent)
 | 
				
			||||||
      // adjust the length of parent
 | 
					      // adjust the length of parent
 | 
				
			||||||
      if (this.countable && this.parentSub === null) {
 | 
					      if (this.countable && this.parentSub === null) {
 | 
				
			||||||
        parent._length -= this.length
 | 
					        parent._length -= this.length
 | 
				
			||||||
@ -534,7 +602,7 @@ export class Item extends AbstractStruct {
 | 
				
			|||||||
      writeID(encoder, rightOrigin)
 | 
					      writeID(encoder, rightOrigin)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (origin === null && rightOrigin === null) {
 | 
					    if (origin === null && rightOrigin === null) {
 | 
				
			||||||
      const parent = this.parent
 | 
					      const parent = /** @type {AbstractType<any>} */ (this.parent)
 | 
				
			||||||
      const parentItem = parent._item
 | 
					      const parentItem = parent._item
 | 
				
			||||||
      if (parentItem === null) {
 | 
					      if (parentItem === null) {
 | 
				
			||||||
        // parent type on y._map
 | 
					        // parent type on y._map
 | 
				
			||||||
@ -670,26 +738,22 @@ export class AbstractContent {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @private
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class ItemRef extends AbstractStructRef {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
 * @param {decoding.Decoder} decoder
 | 
					 * @param {decoding.Decoder} decoder
 | 
				
			||||||
 * @param {ID} id
 | 
					 * @param {ID} id
 | 
				
			||||||
 * @param {number} info
 | 
					 * @param {number} info
 | 
				
			||||||
 | 
					 * @param {Doc} doc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
  constructor (decoder, id, info) {
 | 
					export const readItem = (decoder, id, info, doc) => {
 | 
				
			||||||
    super(id)
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * The item that was originally to the left of this item.
 | 
					   * The item that was originally to the left of this item.
 | 
				
			||||||
   * @type {ID | null}
 | 
					   * @type {ID | null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.left = (info & binary.BIT8) === binary.BIT8 ? readID(decoder) : null
 | 
					  const origin = (info & binary.BIT8) === binary.BIT8 ? readID(decoder) : null
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * The item that was originally to the right of this item.
 | 
					   * The item that was originally to the right of this item.
 | 
				
			||||||
   * @type {ID | null}
 | 
					   * @type {ID | null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.right = (info & binary.BIT7) === binary.BIT7 ? readID(decoder) : null
 | 
					  const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? readID(decoder) : null
 | 
				
			||||||
  const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
 | 
					  const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
 | 
				
			||||||
  const hasParentYKey = canCopyParentInfo ? decoding.readVarUint(decoder) === 1 : false
 | 
					  const hasParentYKey = canCopyParentInfo ? decoding.readVarUint(decoder) === 1 : false
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -698,12 +762,12 @@ export class ItemRef extends AbstractStructRef {
 | 
				
			|||||||
   * It indicates how we store/retrieve parent from `y.share`
 | 
					   * It indicates how we store/retrieve parent from `y.share`
 | 
				
			||||||
   * @type {string|null}
 | 
					   * @type {string|null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.parentYKey = canCopyParentInfo && hasParentYKey ? decoding.readVarString(decoder) : null
 | 
					  const parentYKey = canCopyParentInfo && hasParentYKey ? decoding.readVarString(decoder) : null
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * The parent type.
 | 
					   * The parent type.
 | 
				
			||||||
     * @type {ID | null}
 | 
					   * @type {ID | AbstractType<any> | null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.parent = canCopyParentInfo && !hasParentYKey ? readID(decoder) : null
 | 
					  const parent = canCopyParentInfo && !hasParentYKey ? readID(decoder) : (parentYKey ? doc.get(parentYKey) : null)
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * If the parent refers to this item with some kind of key (e.g. YMap, the
 | 
					   * If the parent refers to this item with some kind of key (e.g. YMap, the
 | 
				
			||||||
   * key is specified here. The key is then used to refer to the list in which
 | 
					   * key is specified here. The key is then used to refer to the list in which
 | 
				
			||||||
@ -711,81 +775,12 @@ export class ItemRef extends AbstractStructRef {
 | 
				
			|||||||
   * which to insert to. Otherwise it is `parent._map`.
 | 
					   * which to insert to. Otherwise it is `parent._map`.
 | 
				
			||||||
   * @type {String | null}
 | 
					   * @type {String | null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.parentSub = canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoding.readVarString(decoder) : null
 | 
					  const parentSub = canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoding.readVarString(decoder) : null
 | 
				
			||||||
    const missing = this._missing
 | 
					
 | 
				
			||||||
    // Only add items to missing if they don't preceed this item (indicating that it has already been added).
 | 
					 | 
				
			||||||
    // @todo Creating missing items could be done outside this constructor
 | 
					 | 
				
			||||||
    if (this.left !== null && this.left.client !== id.client) {
 | 
					 | 
				
			||||||
      missing.push(this.left)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (this.right !== null && this.right.client !== id.client) {
 | 
					 | 
				
			||||||
      missing.push(this.right)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (this.parent !== null) {
 | 
					 | 
				
			||||||
      missing.push(this.parent)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @type {AbstractContent}
 | 
					   * @type {AbstractContent}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    this.content = readItemContent(decoder, info)
 | 
					  const content = readItemContent(decoder, info)
 | 
				
			||||||
    this.length = this.content.getLength()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  return new Item(id, null, origin, null, rightOrigin, parent, parentSub, content)
 | 
				
			||||||
   * @param {Transaction} transaction
 | 
					 | 
				
			||||||
   * @param {StructStore} store
 | 
					 | 
				
			||||||
   * @param {number} offset
 | 
					 | 
				
			||||||
   * @return {Item|GC}
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  toStruct (transaction, store, offset) {
 | 
					 | 
				
			||||||
    if (offset > 0) {
 | 
					 | 
				
			||||||
      this.id.clock += offset
 | 
					 | 
				
			||||||
      this.left = createID(this.id.client, this.id.clock - 1)
 | 
					 | 
				
			||||||
      this.content = this.content.splice(offset)
 | 
					 | 
				
			||||||
      this.length -= offset
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const left = this.left === null ? null : getItemCleanEnd(transaction, store, this.left)
 | 
					 | 
				
			||||||
    const right = this.right === null ? null : getItemCleanStart(transaction, this.right)
 | 
					 | 
				
			||||||
    const parentId = this.parent
 | 
					 | 
				
			||||||
    let parent = null
 | 
					 | 
				
			||||||
    let parentSub = this.parentSub
 | 
					 | 
				
			||||||
    if (parentId !== null) {
 | 
					 | 
				
			||||||
      const parentItem = getItem(store, parentId)
 | 
					 | 
				
			||||||
      // Edge case: toStruct is called with an offset > 0. In this case left is defined.
 | 
					 | 
				
			||||||
      // Depending in which order structs arrive, left may be GC'd and the parent not
 | 
					 | 
				
			||||||
      // deleted. This is why we check if left is GC'd. Strictly we don't have
 | 
					 | 
				
			||||||
      // to check if right is GC'd, but we will in case we run into future issues
 | 
					 | 
				
			||||||
      if (!parentItem.deleted && (left === null || left.constructor !== GC) && (right === null || right.constructor !== GC)) {
 | 
					 | 
				
			||||||
        parent = /** @type {ContentType} */ (parentItem.content).type
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (this.parentYKey !== null) {
 | 
					 | 
				
			||||||
      parent = transaction.doc.get(this.parentYKey)
 | 
					 | 
				
			||||||
    } else if (left !== null) {
 | 
					 | 
				
			||||||
      if (left.constructor !== GC) {
 | 
					 | 
				
			||||||
        parent = left.parent
 | 
					 | 
				
			||||||
        parentSub = left.parentSub
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (right !== null) {
 | 
					 | 
				
			||||||
      if (right.constructor !== GC) {
 | 
					 | 
				
			||||||
        parent = right.parent
 | 
					 | 
				
			||||||
        parentSub = right.parentSub
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw error.unexpectedCase()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return parent === null
 | 
					 | 
				
			||||||
      ? new GC(this.id, this.length)
 | 
					 | 
				
			||||||
      : new Item(
 | 
					 | 
				
			||||||
        this.id,
 | 
					 | 
				
			||||||
        left,
 | 
					 | 
				
			||||||
        left && left.lastId,
 | 
					 | 
				
			||||||
        right,
 | 
					 | 
				
			||||||
        right && right.id,
 | 
					 | 
				
			||||||
        parent,
 | 
					 | 
				
			||||||
        parentSub,
 | 
					 | 
				
			||||||
        this.content
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ export const callTypeObservers = (type, transaction, event) => {
 | 
				
			|||||||
    if (type._item === null) {
 | 
					    if (type._item === null) {
 | 
				
			||||||
      break
 | 
					      break
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    type = type._item.parent
 | 
					    type = /** @type {AbstractType<any>} */ (type._item.parent)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  callEventHandlerListeners(changedType._eH, event, transaction)
 | 
					  callEventHandlerListeners(changedType._eH, event, transaction)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -386,7 +386,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
 | 
				
			|||||||
  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, new ContentAny(jsonContent))
 | 
				
			||||||
      left.integrate(transaction)
 | 
					      left.integrate(transaction, 0)
 | 
				
			||||||
      jsonContent = []
 | 
					      jsonContent = []
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -405,12 +405,12 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
 | 
				
			|||||||
          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, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
 | 
				
			||||||
            left.integrate(transaction)
 | 
					            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, new ContentType(c))
 | 
				
			||||||
              left.integrate(transaction)
 | 
					              left.integrate(transaction, 0)
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              throw new Error('Unexpected content type in insert operation')
 | 
					              throw new Error('Unexpected content type in insert operation')
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -537,7 +537,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)
 | 
					  new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -153,7 +153,7 @@ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes
 | 
				
			|||||||
  const ownClientId = doc.clientID
 | 
					  const ownClientId = doc.clientID
 | 
				
			||||||
  for (const [key, val] of negatedAttributes) {
 | 
					  for (const [key, val] of negatedAttributes) {
 | 
				
			||||||
    left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
 | 
					    left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
 | 
				
			||||||
    left.integrate(transaction)
 | 
					    left.integrate(transaction, 0)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  currPos.left = left
 | 
					  currPos.left = left
 | 
				
			||||||
  currPos.right = right
 | 
					  currPos.right = right
 | 
				
			||||||
@ -228,7 +228,7 @@ const insertAttributes = (transaction, parent, currPos, currentAttributes, attri
 | 
				
			|||||||
      negatedAttributes.set(key, currentVal)
 | 
					      negatedAttributes.set(key, currentVal)
 | 
				
			||||||
      const { left, right } = currPos
 | 
					      const { left, right } = currPos
 | 
				
			||||||
      currPos.left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
 | 
					      currPos.left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
 | 
				
			||||||
      currPos.left.integrate(transaction)
 | 
					      currPos.left.integrate(transaction, 0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return negatedAttributes
 | 
					  return negatedAttributes
 | 
				
			||||||
@ -259,7 +259,7 @@ const insertText = (transaction, parent, currPos, currentAttributes, text, attri
 | 
				
			|||||||
  const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : new ContentEmbed(text)
 | 
					  const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : new ContentEmbed(text)
 | 
				
			||||||
  const { left, right } = currPos
 | 
					  const { left, right } = currPos
 | 
				
			||||||
  currPos.left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
 | 
					  currPos.left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
 | 
				
			||||||
  currPos.left.integrate(transaction)
 | 
					  currPos.left.integrate(transaction, 0)
 | 
				
			||||||
  return insertNegatedAttributes(transaction, parent, currPos, negatedAttributes)
 | 
					  return insertNegatedAttributes(transaction, parent, currPos, negatedAttributes)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -320,7 +320,7 @@ const formatText = (transaction, parent, currPos, currentAttributes, length, att
 | 
				
			|||||||
      newlines += '\n'
 | 
					      newlines += '\n'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentString(newlines))
 | 
					    left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentString(newlines))
 | 
				
			||||||
    left.integrate(transaction)
 | 
					    left.integrate(transaction, 0)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  currPos.left = left
 | 
					  currPos.left = left
 | 
				
			||||||
  currPos.right = right
 | 
					  currPos.right = right
 | 
				
			||||||
 | 
				
			|||||||
@ -97,7 +97,7 @@ export class YXmlTreeWalker {
 | 
				
			|||||||
            } else if (n.parent === this._root) {
 | 
					            } else if (n.parent === this._root) {
 | 
				
			||||||
              n = null
 | 
					              n = null
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              n = n.parent._item
 | 
					              n = /** @type {AbstractType<any>} */ (n.parent)._item
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -227,7 +227,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
 | 
				
			|||||||
    if (!(right instanceof Item)) {
 | 
					    if (!(right instanceof Item)) {
 | 
				
			||||||
      return null
 | 
					      return null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    type = right.parent
 | 
					    type = /** @type {AbstractType<any>} */ (right.parent)
 | 
				
			||||||
    if (type._item === null || !type._item.deleted) {
 | 
					    if (type._item === null || !type._item.deleted) {
 | 
				
			||||||
      index = right.deleted || !right.countable ? 0 : res.diff
 | 
					      index = right.deleted || !right.countable ? 0 : res.diff
 | 
				
			||||||
      let n = right.left
 | 
					      let n = right.left
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  GC,
 | 
					  GC,
 | 
				
			||||||
  splitItem,
 | 
					  splitItem,
 | 
				
			||||||
  AbstractStruct, GCRef, ItemRef, Transaction, ID, Item // eslint-disable-line
 | 
					  AbstractStruct, Transaction, ID, Item // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as math from 'lib0/math.js'
 | 
					import * as math from 'lib0/math.js'
 | 
				
			||||||
@ -21,13 +21,13 @@ export class StructStore {
 | 
				
			|||||||
     * We could shift the array of refs instead, but shift is incredible
 | 
					     * We could shift the array of refs instead, but shift is incredible
 | 
				
			||||||
     * slow in Chrome for arrays with more than 100k elements
 | 
					     * slow in Chrome for arrays with more than 100k elements
 | 
				
			||||||
     * @see tryResumePendingStructRefs
 | 
					     * @see tryResumePendingStructRefs
 | 
				
			||||||
     * @type {Map<number,{i:number,refs:Array<GCRef|ItemRef>}>}
 | 
					     * @type {Map<number,{i:number,refs:Array<GC|Item>}>}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.pendingClientsStructRefs = new Map()
 | 
					    this.pendingClientsStructRefs = new Map()
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Stack of pending structs waiting for struct dependencies
 | 
					     * Stack of pending structs waiting for struct dependencies
 | 
				
			||||||
     * Maximum length of stack is structReaders.size
 | 
					     * Maximum length of stack is structReaders.size
 | 
				
			||||||
     * @type {Array<GCRef|ItemRef>}
 | 
					     * @type {Array<GC|Item>}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.pendingStack = []
 | 
					    this.pendingStack = []
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -129,7 +129,10 @@ export const findIndexSS = (structs, clock) => {
 | 
				
			|||||||
  if (mid.id.clock === clock) {
 | 
					  if (mid.id.clock === clock) {
 | 
				
			||||||
    return right
 | 
					    return right
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  let midindex = math.floor((clock / (midclock + mid.length)) * right) // pivoting the search
 | 
					  // @todo does it even make sense to pivot the search?
 | 
				
			||||||
 | 
					  // If a good split misses, it might actually increase the time to find the correct item.
 | 
				
			||||||
 | 
					  // Currently, the only advantage is that search with pivoting might find the item on the first try.
 | 
				
			||||||
 | 
					  let midindex = math.floor((clock / (midclock + mid.length - 1)) * right) // pivoting the search
 | 
				
			||||||
  while (left <= right) {
 | 
					  while (left <= right) {
 | 
				
			||||||
    mid = structs[midindex]
 | 
					    mid = structs[midindex]
 | 
				
			||||||
    midclock = mid.id.clock
 | 
					    midclock = mid.id.clock
 | 
				
			||||||
 | 
				
			|||||||
@ -156,8 +156,8 @@ const tryToMergeWithLeft = (structs, pos) => {
 | 
				
			|||||||
  if (left.deleted === right.deleted && left.constructor === right.constructor) {
 | 
					  if (left.deleted === right.deleted && left.constructor === right.constructor) {
 | 
				
			||||||
    if (left.mergeWith(right)) {
 | 
					    if (left.mergeWith(right)) {
 | 
				
			||||||
      structs.splice(pos, 1)
 | 
					      structs.splice(pos, 1)
 | 
				
			||||||
      if (right instanceof Item && right.parentSub !== null && right.parent._map.get(right.parentSub) === right) {
 | 
					      if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType<any>} */ (right.parent)._map.get(right.parentSub) === right) {
 | 
				
			||||||
        right.parent._map.set(right.parentSub, /** @type {Item} */ (left))
 | 
					        /** @type {AbstractType<any>} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -211,7 +211,7 @@ const getPathTo = (parent, child) => {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // parent is array-ish
 | 
					      // parent is array-ish
 | 
				
			||||||
      let i = 0
 | 
					      let i = 0
 | 
				
			||||||
      let c = child._item.parent._start
 | 
					      let c = /** @type {AbstractType<any>} */ (child._item.parent)._start
 | 
				
			||||||
      while (c !== child._item && c !== null) {
 | 
					      while (c !== child._item && c !== null) {
 | 
				
			||||||
        if (!c.deleted) {
 | 
					        if (!c.deleted) {
 | 
				
			||||||
          i++
 | 
					          i++
 | 
				
			||||||
@ -220,7 +220,7 @@ const getPathTo = (parent, child) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      path.unshift(i)
 | 
					      path.unshift(i)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    child = child._item.parent
 | 
					    child = /** @type {AbstractType<any>} */ (child._item.parent)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return path
 | 
					  return path
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,8 +16,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  findIndexSS,
 | 
					  findIndexSS,
 | 
				
			||||||
  GCRef,
 | 
					 | 
				
			||||||
  ItemRef,
 | 
					 | 
				
			||||||
  writeID,
 | 
					  writeID,
 | 
				
			||||||
  readID,
 | 
					  readID,
 | 
				
			||||||
  getState,
 | 
					  getState,
 | 
				
			||||||
@ -27,6 +25,7 @@ import {
 | 
				
			|||||||
  writeDeleteSet,
 | 
					  writeDeleteSet,
 | 
				
			||||||
  createDeleteSetFromStructStore,
 | 
					  createDeleteSetFromStructStore,
 | 
				
			||||||
  transact,
 | 
					  transact,
 | 
				
			||||||
 | 
					  readItem,
 | 
				
			||||||
  Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line
 | 
					  Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,7 +79,9 @@ export const writeClientsStructs = (encoder, store, _sm) => {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
  // write # states that were updated
 | 
					  // write # states that were updated
 | 
				
			||||||
  encoding.writeVarUint(encoder, sm.size)
 | 
					  encoding.writeVarUint(encoder, sm.size)
 | 
				
			||||||
  sm.forEach((clock, client) => {
 | 
					  // Write items with higher client ids first
 | 
				
			||||||
 | 
					  // This heavily improves the conflict algorithm.
 | 
				
			||||||
 | 
					  Array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    writeStructs(encoder, store.clients.get(client), client, clock)
 | 
					    writeStructs(encoder, store.clients.get(client), client, clock)
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
@ -88,13 +89,14 @@ export const writeClientsStructs = (encoder, store, _sm) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {decoding.Decoder} decoder The decoder object to read data from.
 | 
					 * @param {decoding.Decoder} decoder The decoder object to read data from.
 | 
				
			||||||
 * @param {Map<number,Array<GCRef|ItemRef>>} clientRefs
 | 
					 * @param {Map<number,Array<GC|Item>>} clientRefs
 | 
				
			||||||
 * @return {Map<number,Array<GCRef|ItemRef>>}
 | 
					 * @param {Doc} doc
 | 
				
			||||||
 | 
					 * @return {Map<number,Array<GC|Item>>}
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @private
 | 
					 * @private
 | 
				
			||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const readClientsStructRefs = (decoder, clientRefs) => {
 | 
					export const readClientsStructRefs = (decoder, clientRefs, doc) => {
 | 
				
			||||||
  const numOfStateUpdates = decoding.readVarUint(decoder)
 | 
					  const numOfStateUpdates = decoding.readVarUint(decoder)
 | 
				
			||||||
  for (let i = 0; i < numOfStateUpdates; i++) {
 | 
					  for (let i = 0; i < numOfStateUpdates; i++) {
 | 
				
			||||||
    const numberOfStructs = decoding.readVarUint(decoder)
 | 
					    const numberOfStructs = decoding.readVarUint(decoder)
 | 
				
			||||||
@ -102,15 +104,16 @@ export const readClientsStructRefs = (decoder, clientRefs) => {
 | 
				
			|||||||
    const nextIdClient = nextID.client
 | 
					    const nextIdClient = nextID.client
 | 
				
			||||||
    let nextIdClock = nextID.clock
 | 
					    let nextIdClock = nextID.clock
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @type {Array<GCRef|ItemRef>}
 | 
					     * @type {Array<GC|Item>}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    const refs = []
 | 
					    const refs = []
 | 
				
			||||||
    clientRefs.set(nextIdClient, refs)
 | 
					    clientRefs.set(nextIdClient, refs)
 | 
				
			||||||
    for (let i = 0; i < numberOfStructs; i++) {
 | 
					    for (let i = 0; i < numberOfStructs; i++) {
 | 
				
			||||||
      const info = decoding.readUint8(decoder)
 | 
					      const info = decoding.readUint8(decoder)
 | 
				
			||||||
      const ref = (binary.BITS5 & info) === 0 ? new GCRef(decoder, createID(nextIdClient, nextIdClock), info) : new ItemRef(decoder, createID(nextIdClient, nextIdClock), info)
 | 
					      const id = createID(nextIdClient, nextIdClock)
 | 
				
			||||||
      refs.push(ref)
 | 
					      const struct = (binary.BITS5 & info) === 0 ? new GC(id, decoding.readVarUint(decoder)) : readItem(decoder, id, info, doc)
 | 
				
			||||||
      nextIdClock += ref.length
 | 
					      refs.push(struct)
 | 
				
			||||||
 | 
					      nextIdClock += struct.length
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return clientRefs
 | 
					  return clientRefs
 | 
				
			||||||
@ -155,12 +158,12 @@ const resumeStructIntegration = (transaction, store) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const ref = stack[stack.length - 1]
 | 
					    const ref = stack[stack.length - 1]
 | 
				
			||||||
    const m = ref._missing
 | 
					 | 
				
			||||||
    const refID = ref.id
 | 
					    const refID = ref.id
 | 
				
			||||||
    const client = refID.client
 | 
					    const client = refID.client
 | 
				
			||||||
    const refClock = refID.clock
 | 
					    const refClock = refID.clock
 | 
				
			||||||
    const localClock = getState(store, client)
 | 
					    const localClock = getState(store, client)
 | 
				
			||||||
    const offset = refClock < localClock ? localClock - refClock : 0
 | 
					    const offset = refClock < localClock ? localClock - refClock : 0
 | 
				
			||||||
 | 
					    const missing = ref.getMissing(transaction, store)
 | 
				
			||||||
    if (refClock + offset !== localClock) {
 | 
					    if (refClock + offset !== localClock) {
 | 
				
			||||||
      // A previous message from this client is missing
 | 
					      // A previous message from this client is missing
 | 
				
			||||||
      // check if there is a pending structRef with a smaller clock and switch them
 | 
					      // check if there is a pending structRef with a smaller clock and switch them
 | 
				
			||||||
@ -180,9 +183,7 @@ const resumeStructIntegration = (transaction, store) => {
 | 
				
			|||||||
      // wait until missing struct is available
 | 
					      // wait until missing struct is available
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    while (m.length > 0) {
 | 
					    if (missing) {
 | 
				
			||||||
      const missing = m[m.length - 1]
 | 
					 | 
				
			||||||
      if (getState(store, missing.client) <= missing.clock) {
 | 
					 | 
				
			||||||
      const client = missing.client
 | 
					      const client = missing.client
 | 
				
			||||||
      // get the struct reader that has the missing struct
 | 
					      // get the struct reader that has the missing struct
 | 
				
			||||||
      const structRefs = clientsStructRefs.get(client)
 | 
					      const structRefs = clientsStructRefs.get(client)
 | 
				
			||||||
@ -194,13 +195,9 @@ const resumeStructIntegration = (transaction, store) => {
 | 
				
			|||||||
      if (structRefs.i === structRefs.refs.length) {
 | 
					      if (structRefs.i === structRefs.refs.length) {
 | 
				
			||||||
        clientsStructRefs.delete(client)
 | 
					        clientsStructRefs.delete(client)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
        break
 | 
					    } else {
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ref._missing.pop()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (m.length === 0) {
 | 
					 | 
				
			||||||
      if (offset < ref.length) {
 | 
					      if (offset < ref.length) {
 | 
				
			||||||
        ref.toStruct(transaction, store, offset).integrate(transaction)
 | 
					        ref.integrate(transaction, offset)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      stack.pop()
 | 
					      stack.pop()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -233,7 +230,7 @@ export const writeStructsFromTransaction = (encoder, transaction) => writeClient
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {StructStore} store
 | 
					 * @param {StructStore} store
 | 
				
			||||||
 * @param {Map<number, Array<GCRef|ItemRef>>} clientsStructsRefs
 | 
					 * @param {Map<number, Array<GC|Item>>} clientsStructsRefs
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @private
 | 
					 * @private
 | 
				
			||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
@ -270,7 +267,7 @@ const mergeReadStructsIntoPendingReads = (store, clientsStructsRefs) => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const readStructs = (decoder, transaction, store) => {
 | 
					export const readStructs = (decoder, transaction, store) => {
 | 
				
			||||||
  const clientsStructRefs = new Map()
 | 
					  const clientsStructRefs = new Map()
 | 
				
			||||||
  readClientsStructRefs(decoder, clientsStructRefs)
 | 
					  readClientsStructRefs(decoder, clientsStructRefs, transaction.doc)
 | 
				
			||||||
  mergeReadStructsIntoPendingReads(store, clientsStructRefs)
 | 
					  mergeReadStructsIntoPendingReads(store, clientsStructRefs)
 | 
				
			||||||
  resumeStructIntegration(transaction, store)
 | 
					  resumeStructIntegration(transaction, store)
 | 
				
			||||||
  tryResumePendingDeleteReaders(transaction, store)
 | 
					  tryResumePendingDeleteReaders(transaction, store)
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ export const isParentOf = (parent, child) => {
 | 
				
			|||||||
    if (child.parent === parent) {
 | 
					    if (child.parent === parent) {
 | 
				
			||||||
      return true
 | 
					      return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    child = child.parent._item
 | 
					    child = /** @type {AbstractType<any>} */ (child.parent)._item
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return false
 | 
					  return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -454,7 +454,7 @@ const mapTransactions = [
 | 
				
			|||||||
 * @param {t.TestCase} tc
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const testRepeatGeneratingYmapTests10 = tc => {
 | 
					export const testRepeatGeneratingYmapTests10 = tc => {
 | 
				
			||||||
  applyRandomTests(tc, mapTransactions, 10)
 | 
					  applyRandomTests(tc, mapTransactions, 3)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -209,16 +209,28 @@ export const testFormattingRemovedInMidText = tc => {
 | 
				
			|||||||
 * @param {t.TestCase} tc
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const testLargeFragmentedDocument = tc => {
 | 
					export const testLargeFragmentedDocument = tc => {
 | 
				
			||||||
  const { text0, testConnector } = init(tc, { users: 2 })
 | 
					  const itemsToInsert = 1000000
 | 
				
			||||||
  // @ts-ignore
 | 
					  let update = /** @type {any} */ (null)
 | 
				
			||||||
  text0.doc.transact(() => {
 | 
					  ;(() => {
 | 
				
			||||||
    for (let i = 0; i < 1000000; i++) {
 | 
					    const doc1 = new Y.Doc()
 | 
				
			||||||
 | 
					    const text0 = doc1.getText('txt')
 | 
				
			||||||
 | 
					    t.measureTime(`time to insert ${itemsToInsert}`, () => {
 | 
				
			||||||
 | 
					      doc1.transact(() => {
 | 
				
			||||||
 | 
					        for (let i = 0; i < itemsToInsert; i++) {
 | 
				
			||||||
          text0.insert(0, '0')
 | 
					          text0.insert(0, '0')
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
  t.measureTime('time to apply', () => {
 | 
					 | 
				
			||||||
    testConnector.flushAllMessages()
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    t.measureTime('time to encode', () => {
 | 
				
			||||||
 | 
					      update = Y.encodeStateAsUpdate(doc1)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })()
 | 
				
			||||||
 | 
					  ;(() => {
 | 
				
			||||||
 | 
					    const doc2 = new Y.Doc()
 | 
				
			||||||
 | 
					    t.measureTime('time to apply', () => {
 | 
				
			||||||
 | 
					      Y.applyUpdate(doc2, update)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RANDOM TESTS
 | 
					// RANDOM TESTS
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user