From 0a0098fdfb6cc2e155f30749119d0754a267770d Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 5 Jun 2020 00:27:36 +0200 Subject: [PATCH] reuse item position references in Y.Text --- src/structs/Item.js | 3 +- src/types/YText.js | 101 ++++++++++++++++++++---------------------- tests/y-text.tests.js | 5 +-- 3 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/structs/Item.js b/src/structs/Item.js index 1e158a10..ceffeb51 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -438,7 +438,8 @@ export class Item extends AbstractStruct { * Computes the last content address of this Item. */ 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) } /** diff --git a/src/types/YText.js b/src/types/YText.js index 31998b21..a3f26778 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -126,15 +126,14 @@ const findPosition = (transaction, parent, index) => { * * @param {Transaction} transaction * @param {AbstractType} parent - * @param {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} negatedAttributes - * @return {ItemListPosition} * * @private * @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 while ( 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.integrate(transaction) } - return { left, right } + currPos.left = left + currPos.right = right } /** @@ -176,17 +176,16 @@ const updateCurrentAttributes = (currentAttributes, format) => { } /** - * @param {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} currentAttributes * @param {Object} attributes - * @return {ItemListPosition} * * @private * @function */ -const minimizeAttributeChanges = (left, right, currentAttributes, attributes) => { +const minimizeAttributeChanges = (currPos, currentAttributes, attributes) => { // go right while attributes[right.key] === right.value (or right is deleted) + let { left, right } = currPos while (true) { if (right === null) { break @@ -201,22 +200,22 @@ const minimizeAttributeChanges = (left, right, currentAttributes, attributes) => left = right right = right.right } - return new ItemListPosition(left, right) + currPos.left = left + currPos.right = right } /** * @param {Transaction} transaction * @param {AbstractType} parent - * @param {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} currentAttributes * @param {Object} attributes - * @return {ItemInsertionResult} + * @return {Map} * * @private * @function **/ -const insertAttributes = (transaction, parent, left, right, currentAttributes, attributes) => { +const insertAttributes = (transaction, parent, currPos, currentAttributes, attributes) => { const doc = transaction.doc const ownClientId = doc.clientID const negatedAttributes = new Map() @@ -227,27 +226,26 @@ const insertAttributes = (transaction, parent, left, right, currentAttributes, a if (!equalAttrs(currentVal, val)) { // save negated attribute (set null if currentVal undefined) 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)) - left.integrate(transaction) + 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.integrate(transaction) } } - return new ItemInsertionResult(left, right, negatedAttributes) + return negatedAttributes } /** * @param {Transaction} transaction * @param {AbstractType} parent - * @param {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} currentAttributes * @param {string|object} text * @param {Object} attributes - * @return {ItemListPosition} * * @private * @function **/ -const insertText = (transaction, parent, left, right, currentAttributes, text, attributes) => { +const insertText = (transaction, parent, currPos, currentAttributes, text, attributes) => { for (const [key] of currentAttributes) { if (attributes[key] === undefined) { attributes[key] = null @@ -255,38 +253,33 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a } const doc = transaction.doc const ownClientId = doc.clientID - const minPos = minimizeAttributeChanges(left, right, currentAttributes, attributes) - const insertPos = insertAttributes(transaction, parent, minPos.left, minPos.right, currentAttributes, attributes) - left = insertPos.left - right = insertPos.right + minimizeAttributeChanges(currPos, currentAttributes, attributes) + const negatedAttributes = insertAttributes(transaction, parent, currPos, currentAttributes, attributes) // insert content 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) - left.integrate(transaction) - return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes) + 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.integrate(transaction) + return insertNegatedAttributes(transaction, parent, currPos, negatedAttributes) } /** * @param {Transaction} transaction * @param {AbstractType} parent - * @param {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} currentAttributes * @param {number} length * @param {Object} attributes - * @return {ItemListPosition} * * @private * @function */ -const formatText = (transaction, parent, left, right, currentAttributes, length, attributes) => { +const formatText = (transaction, parent, currPos, currentAttributes, length, attributes) => { const doc = transaction.doc const ownClientId = doc.clientID - const minPos = minimizeAttributeChanges(left, right, currentAttributes, attributes) - const insertPos = insertAttributes(transaction, parent, minPos.left, minPos.right, currentAttributes, attributes) - const negatedAttributes = insertPos.negatedAttributes - left = insertPos.left - right = insertPos.right + minimizeAttributeChanges(currPos, currentAttributes, attributes) + const negatedAttributes = insertAttributes(transaction, parent, currPos, currentAttributes, attributes) + let { left, right } = currPos // iterate until first non-format or null is found // delete all formats with attributes[format.key] != 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.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 {Item|null} left - * @param {Item|null} right + * @param {ItemListPosition} currPos * @param {Map} currentAttributes * @param {number} length * @return {ItemListPosition} @@ -447,9 +441,10 @@ export const cleanupYTextFormatting = type => { * @private * @function */ -const deleteText = (transaction, left, right, currentAttributes, length) => { +const deleteText = (transaction, currPos, currentAttributes, length) => { const startAttrs = map.copy(currentAttributes) - const start = right + const start = currPos.right + let { left, right } = currPos while (length > 0 && right !== null) { if (right.deleted === false) { switch (right.content.constructor) { @@ -472,7 +467,9 @@ const deleteText = (transaction, left, right, currentAttributes, length) => { if (start) { 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} */ - let pos = new ItemListPosition(null, this._start) + const currPos = new ItemListPosition(null, this._start) const currentAttributes = new Map() for (let i = 0; i < delta.length; i++) { const op = delta[i] @@ -870,14 +867,14 @@ export class YText extends AbstractType { // there is a newline at the end of the content. // If we omit this step, clients will see a different number of // 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) { - 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) { - 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) { - 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 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 { /** @type {Array} */ (this._pending).push(() => this.insert(index, text, attributes)) @@ -1040,7 +1037,7 @@ export class YText extends AbstractType { if (y !== null) { transact(y, transaction => { 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 { /** @type {Array} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes)) @@ -1063,7 +1060,7 @@ export class YText extends AbstractType { if (y !== null) { transact(y, transaction => { const { left, right, currentAttributes } = findPosition(transaction, this, index) - deleteText(transaction, left, right, currentAttributes, length) + deleteText(transaction, new ItemListPosition(left, right), currentAttributes, length) }) } else { /** @type {Array} */ (this._pending).push(() => this.delete(index, length)) @@ -1091,7 +1088,7 @@ export class YText extends AbstractType { if (right === null) { return } - formatText(transaction, this, left, right, currentAttributes, length, attributes) + formatText(transaction, this, new ItemListPosition(left, right), currentAttributes, length, attributes) }) } else { /** @type {Array} */ (this._pending).push(() => this.format(index, length, attributes)) diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index e388ba1f..13f8d62f 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -207,9 +207,9 @@ export const testFormattingRemovedInMidText = tc => { /** * @param {t.TestCase} tc - * + */ export const testLargeFragmentedDocument = tc => { - const { text0, text1, testConnector } = init(tc, { users: 2 }) + const { text0, testConnector } = init(tc, { users: 2 }) // @ts-ignore text0.doc.transact(() => { for (let i = 0; i < 1000000; i++) { @@ -220,7 +220,6 @@ export const testLargeFragmentedDocument = tc => { testConnector.flushAllMessages() }) } -*/ // RANDOM TESTS