From e321d9946fc638b3c4860ef201fd6a34941abf52 Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 09:49:34 +0900 Subject: [PATCH 1/6] refactor: rename function --- src/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index c92ba61a..c2f303fc 100755 --- a/src/main.ts +++ b/src/main.ts @@ -154,7 +154,7 @@ class NiconiComments { return pv; }, []); this.getCommentPos(instances); - this.sortComment(); + this.sortTimelineComment(); const plugins: IPluginList = []; for (const plugin of config.plugins) { @@ -203,7 +203,7 @@ class NiconiComments { /** * 投稿者コメントを前に移動 */ - private sortComment() { + private sortTimelineComment() { const sortCommentStart = performance.now(); for (const vpos of Object.keys(this.timeline)) { const item = this.timeline[Number(vpos)]; From a7a65ee3eb545179f20eea343acfaed9e3c65e14 Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 09:50:17 +0900 Subject: [PATCH 2/6] feat: lazy locator --- src/utils/comment.ts | 113 +++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/src/utils/comment.ts b/src/utils/comment.ts index aa31e3eb..4a6f3217 100755 --- a/src/utils/comment.ts +++ b/src/utils/comment.ts @@ -585,25 +585,15 @@ const isBanActive = (vpos: number): boolean => { * @param comment 固定コメント * @param collision コメントの衝突判定用配列 * @param timeline コメントのタイムライン + * @param lazy Y座標の計算を遅延させるか */ const processFixedComment = ( comment: IComment, collision: CollisionItem, timeline: Timeline, + lazy: boolean = false, ) => { - let posY = 0, - isChanged = true, - count = 0; - while (isChanged && count < 10) { - isChanged = false; - count++; - for (let j = 0; j < comment.long; j++) { - const result = getPosY(posY, comment, collision[comment.vpos + j]); - posY = result.currentPos; - isChanged = result.isChanged; - if (result.isBreak) break; - } - } + const posY = lazy ? -1 : getFixedPosY(comment, collision); for (let j = 0; j < comment.long; j++) { const vpos = comment.vpos + j; arrayPush(timeline, vpos, comment); @@ -618,49 +608,17 @@ const processFixedComment = ( * @param comment nakaコメント * @param collision コメントの衝突判定用配列 * @param timeline コメントのタイムライン + * @param lazy Y座標の計算を遅延させるか */ const processMovableComment = ( comment: IComment, collision: Collision, timeline: Timeline, + lazy: boolean = false, ) => { const beforeVpos = Math.round(-288 / ((1632 + comment.width) / (comment.long + 125))) - 100; - const posY = (() => { - if (config.canvasHeight < comment.height) { - return (comment.height - config.canvasHeight) / -2; - } - let posY = 0; - let isChanged = true; - while (isChanged) { - isChanged = false; - for (let j = beforeVpos, n = comment.long + 125; j < n; j++) { - const vpos = comment.vpos + j; - const leftPos = getPosX(comment.comment, vpos); - let isBreak = false; - if ( - leftPos + comment.width >= config.collisionRange.right && - leftPos <= config.collisionRange.right - ) { - const result = getPosY(posY, comment, collision.right[vpos]); - posY = result.currentPos; - isChanged ||= result.isChanged; - isBreak = result.isBreak; - } - if ( - leftPos + comment.width >= config.collisionRange.left && - leftPos <= config.collisionRange.left - ) { - const result = getPosY(posY, comment, collision.left[vpos]); - posY = result.currentPos; - isChanged ||= result.isChanged; - isBreak = result.isBreak; - } - if (isBreak) return posY; - } - } - return posY; - })(); + const posY = lazy ? -1 : getMovablePosY(comment, collision, beforeVpos); for (let j = beforeVpos, n = comment.long + 125; j < n; j++) { const vpos = comment.vpos + j; const leftPos = getPosX(comment.comment, vpos); @@ -683,6 +641,63 @@ const processMovableComment = ( comment.posY = posY; }; +const getFixedPosY = (comment: IComment, collision: CollisionItem) => { + let posY = 0, + isChanged = true, + count = 0; + while (isChanged && count < 10) { + isChanged = false; + count++; + for (let j = 0; j < comment.long; j++) { + const result = getPosY(posY, comment, collision[comment.vpos + j]); + posY = result.currentPos; + isChanged = result.isChanged; + if (result.isBreak) break; + } + } + return posY; +}; + +const getMovablePosY = ( + comment: IComment, + collision: Collision, + beforeVpos: number, +) => { + if (config.canvasHeight < comment.height) { + return (comment.height - config.canvasHeight) / -2; + } + let posY = 0; + let isChanged = true; + while (isChanged) { + isChanged = false; + for (let j = beforeVpos, n = comment.long + 125; j < n; j++) { + const vpos = comment.vpos + j; + const leftPos = getPosX(comment.comment, vpos); + let isBreak = false; + if ( + leftPos + comment.width >= config.collisionRange.right && + leftPos <= config.collisionRange.right + ) { + const result = getPosY(posY, comment, collision.right[vpos]); + posY = result.currentPos; + isChanged ||= result.isChanged; + isBreak = result.isBreak; + } + if ( + leftPos + comment.width >= config.collisionRange.left && + leftPos <= config.collisionRange.left + ) { + const result = getPosY(posY, comment, collision.left[vpos]); + posY = result.currentPos; + isChanged ||= result.isChanged; + isBreak = result.isBreak; + } + if (isBreak) return posY; + } + } + return posY; +}; + /** * 当たり判定からコメントを配置できる場所を探す * @param currentPos 現在のy座標 @@ -779,6 +794,8 @@ const parseFont = (font: CommentFont, size: string | number): string => { export { getDefaultCommand, + getFixedPosY, + getMovablePosY, getPosX, getPosY, isBanActive, From 0902680a43fbf11765e6c343c4f18148b61ded99 Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 10:07:08 +0900 Subject: [PATCH 3/6] feat: lazy locator option --- src/@types/IComment.ts | 1 + src/@types/options.ts | 1 + src/comments/BaseComment.ts | 5 ++++- src/comments/FlashComment.ts | 4 ++-- src/comments/HTML5Comment.ts | 4 ++-- src/definition/config.ts | 1 + src/main.ts | 32 ++++++++++++++++++++++---------- src/utils/plugins.ts | 6 ++++-- 8 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/@types/IComment.ts b/src/@types/IComment.ts index d6d57549..ba384ddf 100755 --- a/src/@types/IComment.ts +++ b/src/@types/IComment.ts @@ -4,6 +4,7 @@ import type { IRenderer } from "@/@types/renderer"; export interface IComment { comment: FormattedCommentWithSize; invisible: boolean; + index: number; loc: CommentLoc; width: number; long: number; diff --git a/src/@types/options.ts b/src/@types/options.ts index 748e4107..a7db15cd 100755 --- a/src/@types/options.ts +++ b/src/@types/options.ts @@ -41,6 +41,7 @@ export type BaseOptions = { showFPS: boolean; useLegacy: boolean; video: HTMLVideoElement | undefined; + lazy: boolean; }; export type Options = Partial; diff --git a/src/comments/BaseComment.ts b/src/comments/BaseComment.ts index 8d0d4bfa..20c2506e 100755 --- a/src/comments/BaseComment.ts +++ b/src/comments/BaseComment.ts @@ -30,19 +30,22 @@ class BaseComment implements IComment { public readonly pluginName: string = "BaseComment"; public image?: IRenderer | null; public buttonImage?: IRenderer | null; + public index: number; /** * コンストラクタ * @param comment 処理対象のコメント * @param renderer 描画対象のレンダラークラス + * @param index コメントのインデックス */ - constructor(comment: FormattedComment, renderer: IRenderer) { + constructor(comment: FormattedComment, renderer: IRenderer, index: number) { this.renderer = renderer; this.posY = 0; this.pos = { x: 0, y: 0 }; comment.content = comment.content.replace(/\t/g, "\u2003\u2003"); this.comment = this.convertComment(comment); this.cacheKey = this.getCacheKey(); + this.index = index; } get invisible() { return this.comment.invisible; diff --git a/src/comments/FlashComment.ts b/src/comments/FlashComment.ts index 0d9f2a39..748f0270 100755 --- a/src/comments/FlashComment.ts +++ b/src/comments/FlashComment.ts @@ -34,8 +34,8 @@ class FlashComment extends BaseComment { private _globalScale: number; override readonly pluginName: string = "FlashComment"; override buttonImage: IRenderer; - constructor(comment: FormattedComment, renderer: IRenderer) { - super(comment, renderer); + constructor(comment: FormattedComment, renderer: IRenderer, index: number) { + super(comment, renderer, index); this._globalScale ??= getConfig(config.commentScale, true); this.buttonImage = renderer.getCanvas(); } diff --git a/src/comments/HTML5Comment.ts b/src/comments/HTML5Comment.ts index e9ed0c37..bfee6b97 100755 --- a/src/comments/HTML5Comment.ts +++ b/src/comments/HTML5Comment.ts @@ -28,8 +28,8 @@ import { BaseComment } from "./BaseComment"; class HTML5Comment extends BaseComment { override readonly pluginName: string = "HTML5Comment"; - constructor(comment: FormattedComment, context: IRenderer) { - super(comment, context); + constructor(comment: FormattedComment, context: IRenderer, index: number) { + super(comment, context, index); this.posY = 0; } diff --git a/src/definition/config.ts b/src/definition/config.ts index 6cd6ab00..916f2bfc 100755 --- a/src/definition/config.ts +++ b/src/definition/config.ts @@ -26,6 +26,7 @@ const defaultOptions: BaseOptions = { showFPS: false, useLegacy: false, video: undefined, + lazy: false, }; let config: BaseConfig; diff --git a/src/main.ts b/src/main.ts index c2f303fc..24cbb80d 100755 --- a/src/main.ts +++ b/src/main.ts @@ -53,6 +53,8 @@ class NiconiComments { public showFPS: boolean; public showCommentCount: boolean; private lastVpos: number; + private processedCommentIndex: number; + private comments: IComment[]; private readonly renderer: IRenderer; private readonly collision: Collision; private readonly timeline: Timeline; @@ -135,7 +137,9 @@ class NiconiComments { right: [], }; this.lastVpos = -1; - this.preRendering(parsedData); + this.processedCommentIndex = 0; + + this.comments = this.preRendering(parsedData); logger(`constructor complete: ${performance.now() - constructorStart}ms`); } @@ -143,17 +147,18 @@ class NiconiComments { /** * 事前に当たり判定を考慮してコメントの描画場所を決定する * @param rawData コメントデータ + * @returns コメントのインスタンス配列 */ private preRendering(rawData: FormattedComment[]) { const preRenderingStart = performance.now(); if (options.keepCA) { rawData = changeCALayer(rawData); } - let instances = rawData.reduce((pv, val) => { - pv.push(createCommentInstance(val, this.renderer)); + let instances = rawData.reduce((pv, val, index) => { + pv.push(createCommentInstance(val, this.renderer, index)); return pv; }, []); - this.getCommentPos(instances); + this.getCommentPos(instances, options.lazy ? 0 : instances.length); this.sortTimelineComment(); const plugins: IPluginList = []; @@ -175,16 +180,19 @@ class NiconiComments { setPlugins(plugins); logger(`preRendering complete: ${performance.now() - preRenderingStart}ms`); + return instances; } /** * 計算された描画サイズをもとに各コメントの配置位置を決定する * @param data コメントデータ + * @param end 終了インデックス */ - private getCommentPos(data: IComment[]) { + private getCommentPos(data: IComment[], end: number) { const getCommentPosStart = performance.now(); - for (const comment of data) { - if (comment.invisible) continue; + if (this.processedCommentIndex >= end) return; + data.slice(this.processedCommentIndex, end).forEach((comment, index) => { + if (comment.invisible) return; if (comment.loc === "naka") { processMovableComment(comment, this.collision, this.timeline); } else { @@ -194,7 +202,8 @@ class NiconiComments { this.timeline, ); } - } + this.processedCommentIndex = index; + }); logger( `getCommentPos complete: ${performance.now() - getCommentPosStart}ms`, ); @@ -228,8 +237,10 @@ class NiconiComments { * @param rawComments コメントデータ */ public addComments(...rawComments: FormattedComment[]) { - const comments = rawComments.reduce((pv, val) => { - pv.push(createCommentInstance(val, this.renderer)); + const comments = rawComments.reduce((pv, val, index) => { + pv.push( + createCommentInstance(val, this.renderer, this.comments.length + index), + ); return pv; }, []); for (const plugin of plugins) { @@ -333,6 +344,7 @@ class NiconiComments { if (comment.invisible) { continue; } + this.getCommentPos(this.comments, comment.index + 1); comment.draw(vpos, this.showCollision, cursor); } } diff --git a/src/utils/plugins.ts b/src/utils/plugins.ts index 64c0f3e3..17bcf3c1 100755 --- a/src/utils/plugins.ts +++ b/src/utils/plugins.ts @@ -7,18 +7,20 @@ import { config } from "@/definition/config"; * コメントのインスタンスを生成する * @param comment コメント * @param context 描画対象のCanvasコンテキスト + * @param index コメントのインデックス * @returns プラグインまたは内臓のコメントインスタンス */ const createCommentInstance = ( comment: FormattedComment, context: IRenderer, + index: number, ) => { for (const plugin of config.commentPlugins) { if (plugin.condition(comment)) { - return new plugin.class(comment, context); + return new plugin.class(comment, context, index); } } - return new HTML5Comment(comment, context); + return new HTML5Comment(comment, context, index); }; export { createCommentInstance }; From 7482e7441635f7c11541724752758f00d105d7ee Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 10:20:42 +0900 Subject: [PATCH 4/6] fix: process logic --- src/main.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main.ts b/src/main.ts index 24cbb80d..a64c0e21 100755 --- a/src/main.ts +++ b/src/main.ts @@ -158,7 +158,7 @@ class NiconiComments { pv.push(createCommentInstance(val, this.renderer, index)); return pv; }, []); - this.getCommentPos(instances, options.lazy ? 0 : instances.length); + this.getCommentPos(instances, instances.length, options.lazy); this.sortTimelineComment(); const plugins: IPluginList = []; @@ -187,23 +187,28 @@ class NiconiComments { * 計算された描画サイズをもとに各コメントの配置位置を決定する * @param data コメントデータ * @param end 終了インデックス + * @param lazy 遅延処理を行うか */ - private getCommentPos(data: IComment[], end: number) { + private getCommentPos(data: IComment[], end: number, lazy: boolean = false) { const getCommentPosStart = performance.now(); - if (this.processedCommentIndex >= end) return; - data.slice(this.processedCommentIndex, end).forEach((comment, index) => { - if (comment.invisible) return; + if (this.processedCommentIndex + 1 >= end) return; + for (const comment of data.slice(this.processedCommentIndex, end)) { + if (comment.invisible) continue; if (comment.loc === "naka") { - processMovableComment(comment, this.collision, this.timeline); + processMovableComment(comment, this.collision, this.timeline, lazy); } else { processFixedComment( comment, this.collision[comment.loc], this.timeline, + lazy, ); } - this.processedCommentIndex = index; - }); + this.processedCommentIndex = comment.index; + } + if (lazy) { + this.processedCommentIndex = 0; + } logger( `getCommentPos complete: ${performance.now() - getCommentPosStart}ms`, ); From 050a555513b5ade7db5e4d49af3f459cf9159f49 Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 11:28:53 +0900 Subject: [PATCH 5/6] improve: reduce loop count --- src/comments/BaseComment.ts | 2 +- src/comments/HTML5Comment.ts | 2 +- src/main.ts | 2 +- src/utils/comment.ts | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/comments/BaseComment.ts b/src/comments/BaseComment.ts index 2afaa92b..b2f2ff0e 100755 --- a/src/comments/BaseComment.ts +++ b/src/comments/BaseComment.ts @@ -40,7 +40,7 @@ class BaseComment implements IComment { */ constructor(comment: FormattedComment, renderer: IRenderer, index: number) { this.renderer = renderer; - this.posY = 0; + this.posY = -1; this.pos = { x: 0, y: 0 }; comment.content = comment.content.replace(/\t/g, "\u2003\u2003"); this.comment = this.convertComment(comment); diff --git a/src/comments/HTML5Comment.ts b/src/comments/HTML5Comment.ts index 38f7e498..e25c0858 100755 --- a/src/comments/HTML5Comment.ts +++ b/src/comments/HTML5Comment.ts @@ -30,7 +30,7 @@ class HTML5Comment extends BaseComment { override readonly pluginName: string = "HTML5Comment"; constructor(comment: FormattedComment, context: IRenderer, index: number) { super(comment, context, index); - this.posY = 0; + this.posY = -1; } override get content() { diff --git a/src/main.ts b/src/main.ts index 19c83493..198fe0e2 100755 --- a/src/main.ts +++ b/src/main.ts @@ -193,7 +193,7 @@ class NiconiComments { const getCommentPosStart = performance.now(); if (this.processedCommentIndex + 1 >= end) return; for (const comment of data.slice(this.processedCommentIndex, end)) { - if (comment.invisible) continue; + if (comment.invisible || (comment.posY > -1 && !lazy)) continue; if (comment.loc === "naka") { processMovableComment(comment, this.collision, this.timeline, lazy); } else { diff --git a/src/utils/comment.ts b/src/utils/comment.ts index 4a6f3217..912cf296 100755 --- a/src/utils/comment.ts +++ b/src/utils/comment.ts @@ -622,6 +622,7 @@ const processMovableComment = ( for (let j = beforeVpos, n = comment.long + 125; j < n; j++) { const vpos = comment.vpos + j; const leftPos = getPosX(comment.comment, vpos); + if (timeline[vpos]?.includes(comment)) break; arrayPush(timeline, vpos, comment); if ( leftPos + comment.width + config.collisionPadding >= @@ -668,12 +669,16 @@ const getMovablePosY = ( } let posY = 0; let isChanged = true; + let lastUpdatedIndex: number | undefined = undefined; while (isChanged) { isChanged = false; - for (let j = beforeVpos, n = comment.long + 125; j < n; j++) { + for (let j = beforeVpos, n = comment.long + 125; j < n; j += 5) { const vpos = comment.vpos + j; const leftPos = getPosX(comment.comment, vpos); let isBreak = false; + if (lastUpdatedIndex !== undefined && lastUpdatedIndex === vpos) { + return posY; + } if ( leftPos + comment.width >= config.collisionRange.right && leftPos <= config.collisionRange.right @@ -681,6 +686,7 @@ const getMovablePosY = ( const result = getPosY(posY, comment, collision.right[vpos]); posY = result.currentPos; isChanged ||= result.isChanged; + if (result.isChanged) lastUpdatedIndex = vpos; isBreak = result.isBreak; } if ( @@ -690,6 +696,7 @@ const getMovablePosY = ( const result = getPosY(posY, comment, collision.left[vpos]); posY = result.currentPos; isChanged ||= result.isChanged; + if (result.isChanged) lastUpdatedIndex = vpos; isBreak = result.isBreak; } if (isBreak) return posY; @@ -715,11 +722,13 @@ const getPosY = ( let isBreak = false; if (!collision) return { currentPos, isChanged, isBreak }; for (const collisionItem of collision) { + if (collisionItem.index === targetComment.index || collisionItem.posY < 0) + continue; if ( - currentPos < collisionItem.posY + collisionItem.height && - currentPos + targetComment.height > collisionItem.posY && collisionItem.owner === targetComment.owner && - collisionItem.layer === targetComment.layer + collisionItem.layer === targetComment.layer && + currentPos < collisionItem.posY + collisionItem.height && + currentPos + targetComment.height > collisionItem.posY ) { if (collisionItem.posY + collisionItem.height > currentPos) { currentPos = collisionItem.posY + collisionItem.height; From 088e06c84ab8bd92f6d5be9e8389f3f3c9d2850a Mon Sep 17 00:00:00 2001 From: xpadev Date: Fri, 12 Jan 2024 14:27:00 +0900 Subject: [PATCH 6/6] release: v0.2.70 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72cf37dd..6941a210 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xpadev-net/niconicomments", - "version": "0.2.69", + "version": "0.2.70", "description": "NiconiComments is a comment drawing library that is somewhat compatible with the official Nico Nico Douga player.", "main": "dist/bundle.js", "types": "dist/bundle.d.ts",