diff --git a/src/@types/IComment.ts b/src/@types/IComment.ts index e37d2421..2fa87cdd 100755 --- a/src/@types/IComment.ts +++ b/src/@types/IComment.ts @@ -8,6 +8,7 @@ import type { 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 3cfae1be..091ed33e 100755 --- a/src/@types/options.ts +++ b/src/@types/options.ts @@ -50,6 +50,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 b8ed7c2d..2afaa92b 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 da236e92..a66ddf7a 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 8084f812..38f7e498 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 2e8c70a8..19c83493 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,18 +147,19 @@ 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.sortComment(); + this.getCommentPos(instances, instances.length, options.lazy); + this.sortTimelineComment(); const plugins: IPluginList = []; for (const plugin of config.plugins) { @@ -175,25 +180,34 @@ class NiconiComments { setPlugins(plugins); logger(`preRendering complete: ${performance.now() - preRenderingStart}ms`); + return instances; } /** * 計算された描画サイズをもとに各コメントの配置位置を決定する * @param data コメントデータ + * @param end 終了インデックス + * @param lazy 遅延処理を行うか */ - private getCommentPos(data: IComment[]) { + private getCommentPos(data: IComment[], end: number, lazy: boolean = false) { const getCommentPosStart = performance.now(); - for (const comment of data) { + 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 = comment.index; + } + if (lazy) { + this.processedCommentIndex = 0; } logger( `getCommentPos complete: ${performance.now() - getCommentPosStart}ms`, @@ -203,7 +217,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)]; @@ -228,8 +242,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 +349,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/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, diff --git a/src/utils/plugins.ts b/src/utils/plugins.ts index 561dde8d..41f931c7 100755 --- a/src/utils/plugins.ts +++ b/src/utils/plugins.ts @@ -6,18 +6,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 };