From 533e4fbf2ccbf404c1eed389aa0c319d774ca6bd Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 28 Apr 2025 00:02:06 +0200 Subject: [PATCH] [AttributionManager] further improve performance to be almost on-par with toString --- src/types/YText.js | 156 +++++++++++++++++--------------- src/utils/AttributionManager.js | 26 +++--- src/utils/Delta.js | 4 +- 3 files changed, 96 insertions(+), 90 deletions(-) diff --git a/src/types/YText.js b/src/types/YText.js index da88b402..7bd386d1 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -1006,85 +1006,91 @@ export class YText extends AbstractType { getContent (am = noAttributionsManager) { this.doc ?? warnPrematureAccess() const d = delta.create() - for (let item = this._start; item !== null; item = item.right) { - const cs = am.getContent(item) - if (cs != null) { - for (let i = 0; i < cs.length; i++) { - const { content, deleted, attrs } = cs[i] - /** - * @type {import('../utils/Delta.js').Attribution?} - */ - let attributions = null - if (attrs != null) { - attributions = {} - if (deleted) { - attributions.delete = [] - } else { - attributions.insert = [] - } - attrs.forEach(attr => { - switch (attr.name) { - case 'insert': - case 'delete': - case 'suggest': { - const as = /** @type {import('../utils/Delta.js').Attribution} */ (attributions) - const ls = as[attr.name] = as[attr.name] ?? [] - ls.push(attr.val) - break - } - default: { - if (attr.name[0] !== '_') { - /** @type {any} */ (attributions)[attr.name] = attr.val - } - } - } - }) + /** + * @type {Array>} + */ + const cs = [] + for (let item = this._start; item !== null; cs.length = 0) { + // populate cs + for (; item !== null && cs.length < 50; item = item.right) { + am.readContent(cs, item) + } + for (let i = 0; i < cs.length; i++) { + const { content, deleted, attrs } = cs[i] + /** + * @type {import('../utils/Delta.js').Attribution?} + */ + let attributions = null + if (attrs != null) { + attributions = {} + if (deleted) { + attributions.delete = [] + } else { + attributions.insert = [] } - switch (content.constructor) { - case ContentString: { - d.insert(/** @type {ContentString} */ (content).str, null, attributions) - break - } - case ContentType: - case ContentEmbed: { - d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attributions) - break - } - case ContentFormat: - const contentFormat = /** @type {ContentFormat} */ (content) - if (attributions != null) { - /** - * @type {import('../utils/Delta.js').Attribution} - */ - const formattingAttributions = object.assign({}, d.usedAttribution) - const attributesChanged = /** @type {{ [key: string]: Array }} */ (formattingAttributions.attributes = object.assign({}, formattingAttributions.attributes ?? {})) - if (contentFormat.value === null) { - delete attributesChanged[contentFormat.key] - } else { - const by = attributesChanged[contentFormat.key] = attributesChanged[contentFormat.key]?.slice() ?? [] - by.push(...((deleted ? attributions.delete : attributions.insert) ?? [])) - const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt) - if (attributedAt) formattingAttributions.attributedAt = attributedAt - } - if (object.isEmpty(attributesChanged)) { - d.useAttribution(null) - } else { - const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt) - if (attributedAt != null) formattingAttributions.attributedAt = attributedAt - d.useAttribution(formattingAttributions) + attrs.forEach(attr => { + switch (attr.name) { + case 'insert': + case 'delete': + case 'suggest': { + const as = /** @type {import('../utils/Delta.js').Attribution} */ (attributions) + const ls = as[attr.name] = as[attr.name] ?? [] + ls.push(attr.val) + break + } + default: { + if (attr.name[0] !== '_') { + /** @type {any} */ (attributions)[attr.name] = attr.val } } - if (!deleted) { - const currAttrs = d.usedAttributes - if (contentFormat.value == null) { - const nextAttrs = object.assign({}, currAttrs) - delete nextAttrs[contentFormat.key] - d.useAttributes(nextAttrs) - } else { - d.useAttributes(object.assign({}, currAttrs, { [contentFormat.key]: contentFormat.value })) - } + } + }) + } + switch (content.constructor) { + case ContentString: { + d.insert(/** @type {ContentString} */ (content).str, null, attributions) + break + } + case ContentType: + case ContentEmbed: { + d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attributions) + break + } + case ContentFormat: { + const contentFormat = /** @type {ContentFormat} */ (content) + if (attributions != null) { + /** + * @type {import('../utils/Delta.js').Attribution} + */ + const formattingAttributions = object.assign({}, d.usedAttribution) + const attributesChanged = /** @type {{ [key: string]: Array }} */ (formattingAttributions.attributes = object.assign({}, formattingAttributions.attributes ?? {})) + if (contentFormat.value === null) { + delete attributesChanged[contentFormat.key] + } else { + const by = attributesChanged[contentFormat.key] = attributesChanged[contentFormat.key]?.slice() ?? [] + by.push(...((deleted ? attributions.delete : attributions.insert) ?? [])) + const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt) + if (attributedAt) formattingAttributions.attributedAt = attributedAt } - break + if (object.isEmpty(attributesChanged)) { + d.useAttribution(null) + } else { + const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt) + if (attributedAt != null) formattingAttributions.attributedAt = attributedAt + d.useAttribution(formattingAttributions) + } + } + if (!deleted) { + const currAttrs = d.usedAttributes + if (contentFormat.value == null) { + const nextAttrs = object.assign({}, currAttrs) + delete nextAttrs[contentFormat.key] + d.useAttributes(nextAttrs) + } else { + d.useAttributes(object.assign({}, currAttrs, { [contentFormat.key]: contentFormat.value })) + } + } + break } } } diff --git a/src/utils/AttributionManager.js b/src/utils/AttributionManager.js index b2d45561..95bfad77 100644 --- a/src/utils/AttributionManager.js +++ b/src/utils/AttributionManager.js @@ -25,10 +25,10 @@ export class AttributedContent { */ export class AbstractAttributionManager { /** + * @param {Array>} _contents * @param {Item} _item - * @return {Array>?} */ - getContent (_item) { + readContent (_contents, _item) { error.methodUnimplemented() } } @@ -49,24 +49,22 @@ export class TwosetAttributionManager { } /** + * @param {Array>} contents * @param {Item} item - * @return {Array>} */ - getContent (item) { + readContent (contents, item) { const deleted = item.deleted const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length) let content = slice.length === 1 ? item.content : item.content.copy() - let res = slice.map(s => { + slice.forEach(s => { const c = content if (s.len < c.getLength()) { content = c.splice(s.len) } - return new AttributedContent(c, deleted, s.attrs) + if (!deleted || s.attrs != null) { + contents.push(new AttributedContent(c, deleted, s.attrs)) + } }) - if (deleted) { - res = res.filter(s => s.attrs != null) - } - return res } } @@ -77,11 +75,13 @@ export class TwosetAttributionManager { */ export class NoAttributionsManager { /** + * @param {Array>} contents * @param {Item} item - * @return {Array>?} */ - getContent (item) { - return item.deleted ? null : [new AttributedContent(item.content, false, null)] + readContent (contents, item) { + if (!item.deleted) { + contents.push(new AttributedContent(item.content, false, null)) + } } } diff --git a/src/utils/Delta.js b/src/utils/Delta.js index 402550b7..0bd70115 100644 --- a/src/utils/Delta.js +++ b/src/utils/Delta.js @@ -158,7 +158,7 @@ export class DeltaBuilder extends Delta { */ useAttributes (attributes) { if (this.usedAttributes === attributes) return this - this.usedAttributes = attributes ?? object.assign({}, attributes) + this.usedAttributes = attributes && (object.isEmpty(attributes) ? null : object.assign({}, attributes)) return this } @@ -167,7 +167,7 @@ export class DeltaBuilder extends Delta { */ useAttribution (attribution) { if (this.usedAttribution === attribution) return this - this.usedAttribution = attribution ?? object.assign({}, attribution) + this.usedAttribution = attribution && (object.isEmpty(attribution) ? null : object.assign({}, attribution)) return this }