reuse item position references in Y.Text

This commit is contained in:
Kevin Jahns 2020-06-05 00:27:36 +02:00
parent a5a48d07f6
commit 0a0098fdfb
3 changed files with 53 additions and 56 deletions

View File

@ -438,7 +438,8 @@ export class Item extends AbstractStruct {
* Computes the last content address of this Item. * Computes the last content address of this Item.
*/ */
get lastId () { get lastId () {
return createID(this.id.client, this.id.clock + this.length - 1) // allocating ids is pretty costly because of the amount of ids created, so we try to reuse whenever possible
return this.length === 1 ? this.id : createID(this.id.client, this.id.clock + this.length - 1)
} }
/** /**

View File

@ -126,15 +126,14 @@ const findPosition = (transaction, parent, index) => {
* *
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} negatedAttributes * @param {Map<string,any>} negatedAttributes
* @return {ItemListPosition}
* *
* @private * @private
* @function * @function
*/ */
const insertNegatedAttributes = (transaction, parent, left, right, negatedAttributes) => { const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes) => {
let { left, right } = currPos
// check if we really need to remove attributes // check if we really need to remove attributes
while ( while (
right !== null && ( right !== null && (
@ -156,7 +155,8 @@ const insertNegatedAttributes = (transaction, parent, left, right, negatedAttrib
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)
} }
return { left, right } currPos.left = left
currPos.right = right
} }
/** /**
@ -176,17 +176,16 @@ const updateCurrentAttributes = (currentAttributes, format) => {
} }
/** /**
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemListPosition}
* *
* @private * @private
* @function * @function
*/ */
const minimizeAttributeChanges = (left, right, currentAttributes, attributes) => { const minimizeAttributeChanges = (currPos, currentAttributes, attributes) => {
// go right while attributes[right.key] === right.value (or right is deleted) // go right while attributes[right.key] === right.value (or right is deleted)
let { left, right } = currPos
while (true) { while (true) {
if (right === null) { if (right === null) {
break break
@ -201,22 +200,22 @@ const minimizeAttributeChanges = (left, right, currentAttributes, attributes) =>
left = right left = right
right = right.right right = right.right
} }
return new ItemListPosition(left, right) currPos.left = left
currPos.right = right
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemInsertionResult} * @return {Map<string,any>}
* *
* @private * @private
* @function * @function
**/ **/
const insertAttributes = (transaction, parent, left, right, currentAttributes, attributes) => { const insertAttributes = (transaction, parent, currPos, currentAttributes, attributes) => {
const doc = transaction.doc const doc = transaction.doc
const ownClientId = doc.clientID const ownClientId = doc.clientID
const negatedAttributes = new Map() const negatedAttributes = new Map()
@ -227,27 +226,26 @@ const insertAttributes = (transaction, parent, left, right, currentAttributes, a
if (!equalAttrs(currentVal, val)) { if (!equalAttrs(currentVal, val)) {
// save negated attribute (set null if currentVal undefined) // save negated attribute (set null if currentVal undefined)
negatedAttributes.set(key, currentVal) negatedAttributes.set(key, currentVal)
left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val)) const { left, right } = currPos
left.integrate(transaction) 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)
} }
} }
return new ItemInsertionResult(left, right, negatedAttributes) return negatedAttributes
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {string|object} text * @param {string|object} text
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemListPosition}
* *
* @private * @private
* @function * @function
**/ **/
const insertText = (transaction, parent, left, right, currentAttributes, text, attributes) => { const insertText = (transaction, parent, currPos, currentAttributes, text, attributes) => {
for (const [key] of currentAttributes) { for (const [key] of currentAttributes) {
if (attributes[key] === undefined) { if (attributes[key] === undefined) {
attributes[key] = null attributes[key] = null
@ -255,38 +253,33 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a
} }
const doc = transaction.doc const doc = transaction.doc
const ownClientId = doc.clientID const ownClientId = doc.clientID
const minPos = minimizeAttributeChanges(left, right, currentAttributes, attributes) minimizeAttributeChanges(currPos, currentAttributes, attributes)
const insertPos = insertAttributes(transaction, parent, minPos.left, minPos.right, currentAttributes, attributes) const negatedAttributes = insertAttributes(transaction, parent, currPos, currentAttributes, attributes)
left = insertPos.left
right = insertPos.right
// insert content // insert content
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)
left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content) const { left, right } = currPos
left.integrate(transaction) currPos.left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes) currPos.left.integrate(transaction)
return insertNegatedAttributes(transaction, parent, currPos, negatedAttributes)
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {number} length * @param {number} length
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemListPosition}
* *
* @private * @private
* @function * @function
*/ */
const formatText = (transaction, parent, left, right, currentAttributes, length, attributes) => { const formatText = (transaction, parent, currPos, currentAttributes, length, attributes) => {
const doc = transaction.doc const doc = transaction.doc
const ownClientId = doc.clientID const ownClientId = doc.clientID
const minPos = minimizeAttributeChanges(left, right, currentAttributes, attributes) minimizeAttributeChanges(currPos, currentAttributes, attributes)
const insertPos = insertAttributes(transaction, parent, minPos.left, minPos.right, currentAttributes, attributes) const negatedAttributes = insertAttributes(transaction, parent, currPos, currentAttributes, attributes)
const negatedAttributes = insertPos.negatedAttributes let { left, right } = currPos
left = insertPos.left
right = insertPos.right
// iterate until first non-format or null is found // iterate until first non-format or null is found
// delete all formats with attributes[format.key] != null // delete all formats with attributes[format.key] != null
while (length > 0 && right !== null) { while (length > 0 && right !== null) {
@ -329,7 +322,9 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
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)
} }
return insertNegatedAttributes(transaction, parent, left, right, negatedAttributes) currPos.left = left
currPos.right = right
insertNegatedAttributes(transaction, parent, currPos, negatedAttributes)
} }
/** /**
@ -438,8 +433,7 @@ export const cleanupYTextFormatting = type => {
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {Item|null} left * @param {ItemListPosition} currPos
* @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {number} length * @param {number} length
* @return {ItemListPosition} * @return {ItemListPosition}
@ -447,9 +441,10 @@ export const cleanupYTextFormatting = type => {
* @private * @private
* @function * @function
*/ */
const deleteText = (transaction, left, right, currentAttributes, length) => { const deleteText = (transaction, currPos, currentAttributes, length) => {
const startAttrs = map.copy(currentAttributes) const startAttrs = map.copy(currentAttributes)
const start = right const start = currPos.right
let { left, right } = currPos
while (length > 0 && right !== null) { while (length > 0 && right !== null) {
if (right.deleted === false) { if (right.deleted === false) {
switch (right.content.constructor) { switch (right.content.constructor) {
@ -472,7 +467,9 @@ const deleteText = (transaction, left, right, currentAttributes, length) => {
if (start) { if (start) {
cleanupFormattingGap(transaction, start, right, startAttrs, map.copy(currentAttributes)) cleanupFormattingGap(transaction, start, right, startAttrs, map.copy(currentAttributes))
} }
return { left, right } currPos.left = left
currPos.right = right
return currPos
} }
/** /**
@ -860,7 +857,7 @@ export class YText extends AbstractType {
/** /**
* @type {ItemListPosition} * @type {ItemListPosition}
*/ */
let pos = new ItemListPosition(null, this._start) const currPos = new ItemListPosition(null, this._start)
const currentAttributes = new Map() const currentAttributes = new Map()
for (let i = 0; i < delta.length; i++) { for (let i = 0; i < delta.length; i++) {
const op = delta[i] const op = delta[i]
@ -870,14 +867,14 @@ export class YText extends AbstractType {
// there is a newline at the end of the content. // there is a newline at the end of the content.
// If we omit this step, clients will see a different number of // If we omit this step, clients will see a different number of
// paragraphs, but nothing bad will happen. // paragraphs, but nothing bad will happen.
const ins = (!sanitize && typeof op.insert === 'string' && i === delta.length - 1 && pos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert const ins = (!sanitize && typeof op.insert === 'string' && i === delta.length - 1 && currPos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert
if (typeof ins !== 'string' || ins.length > 0) { if (typeof ins !== 'string' || ins.length > 0) {
pos = insertText(transaction, this, pos.left, pos.right, currentAttributes, ins, op.attributes || {}) insertText(transaction, this, currPos, currentAttributes, ins, op.attributes || {})
} }
} else if (op.retain !== undefined) { } else if (op.retain !== undefined) {
pos = formatText(transaction, this, pos.left, pos.right, currentAttributes, op.retain, op.attributes || {}) formatText(transaction, this, currPos, currentAttributes, op.retain, op.attributes || {})
} else if (op.delete !== undefined) { } else if (op.delete !== undefined) {
pos = deleteText(transaction, pos.left, pos.right, currentAttributes, op.delete) deleteText(transaction, currPos, currentAttributes, op.delete)
} }
} }
}) })
@ -1015,7 +1012,7 @@ export class YText extends AbstractType {
// @ts-ignore // @ts-ignore
currentAttributes.forEach((v, k) => { attributes[k] = v }) currentAttributes.forEach((v, k) => { attributes[k] = v })
} }
insertText(transaction, this, left, right, currentAttributes, text, attributes) insertText(transaction, this, new ItemListPosition(left, right), currentAttributes, text, attributes)
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.insert(index, text, attributes)) /** @type {Array<function>} */ (this._pending).push(() => this.insert(index, text, attributes))
@ -1040,7 +1037,7 @@ export class YText extends AbstractType {
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
const { left, right, currentAttributes } = findPosition(transaction, this, index) const { left, right, currentAttributes } = findPosition(transaction, this, index)
insertText(transaction, this, left, right, currentAttributes, embed, attributes) insertText(transaction, this, new ItemListPosition(left, right), currentAttributes, embed, attributes)
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes)) /** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes))
@ -1063,7 +1060,7 @@ export class YText extends AbstractType {
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
const { left, right, currentAttributes } = findPosition(transaction, this, index) const { left, right, currentAttributes } = findPosition(transaction, this, index)
deleteText(transaction, left, right, currentAttributes, length) deleteText(transaction, new ItemListPosition(left, right), currentAttributes, length)
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length)) /** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length))
@ -1091,7 +1088,7 @@ export class YText extends AbstractType {
if (right === null) { if (right === null) {
return return
} }
formatText(transaction, this, left, right, currentAttributes, length, attributes) formatText(transaction, this, new ItemListPosition(left, right), currentAttributes, length, attributes)
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.format(index, length, attributes)) /** @type {Array<function>} */ (this._pending).push(() => this.format(index, length, attributes))

View File

@ -207,9 +207,9 @@ export const testFormattingRemovedInMidText = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
* */
export const testLargeFragmentedDocument = tc => { export const testLargeFragmentedDocument = tc => {
const { text0, text1, testConnector } = init(tc, { users: 2 }) const { text0, testConnector } = init(tc, { users: 2 })
// @ts-ignore // @ts-ignore
text0.doc.transact(() => { text0.doc.transact(() => {
for (let i = 0; i < 1000000; i++) { for (let i = 0; i < 1000000; i++) {
@ -220,7 +220,6 @@ export const testLargeFragmentedDocument = tc => {
testConnector.flushAllMessages() testConnector.flushAllMessages()
}) })
} }
*/
// RANDOM TESTS // RANDOM TESTS