Skip to content

Commit 31ab614

Browse files
authored
Merge pull request #95 from xpadev-net/develop
release: v0.2.70
2 parents 977ff8f + 088e06c commit 31ab614

File tree

10 files changed

+124
-73
lines changed

10 files changed

+124
-73
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@xpadev-net/niconicomments",
3-
"version": "0.2.69",
3+
"version": "0.2.70",
44
"description": "NiconiComments is a comment drawing library that is somewhat compatible with the official Nico Nico Douga player.",
55
"main": "dist/bundle.js",
66
"types": "dist/bundle.d.ts",

src/@types/IComment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
export interface IComment {
99
comment: FormattedCommentWithSize;
1010
invisible: boolean;
11+
index: number;
1112
loc: CommentLoc;
1213
width: number;
1314
long: number;

src/@types/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type BaseOptions = {
5050
showFPS: boolean;
5151
useLegacy: boolean;
5252
video: HTMLVideoElement | undefined;
53+
lazy: boolean;
5354
};
5455
export type Options = Partial<BaseOptions>;
5556

src/comments/BaseComment.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,22 @@ class BaseComment implements IComment {
3030
public readonly pluginName: string = "BaseComment";
3131
public image?: IRenderer | null;
3232
public buttonImage?: IRenderer | null;
33+
public index: number;
3334

3435
/**
3536
* コンストラクタ
3637
* @param comment 処理対象のコメント
3738
* @param renderer 描画対象のレンダラークラス
39+
* @param index コメントのインデックス
3840
*/
39-
constructor(comment: FormattedComment, renderer: IRenderer) {
41+
constructor(comment: FormattedComment, renderer: IRenderer, index: number) {
4042
this.renderer = renderer;
41-
this.posY = 0;
43+
this.posY = -1;
4244
this.pos = { x: 0, y: 0 };
4345
comment.content = comment.content.replace(/\t/g, "\u2003\u2003");
4446
this.comment = this.convertComment(comment);
4547
this.cacheKey = this.getCacheKey();
48+
this.index = index;
4649
}
4750
get invisible() {
4851
return this.comment.invisible;

src/comments/FlashComment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class FlashComment extends BaseComment {
3434
private _globalScale: number;
3535
override readonly pluginName: string = "FlashComment";
3636
override buttonImage: IRenderer;
37-
constructor(comment: FormattedComment, renderer: IRenderer) {
38-
super(comment, renderer);
37+
constructor(comment: FormattedComment, renderer: IRenderer, index: number) {
38+
super(comment, renderer, index);
3939
this._globalScale ??= getConfig(config.commentScale, true);
4040
this.buttonImage = renderer.getCanvas();
4141
}

src/comments/HTML5Comment.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import { BaseComment } from "./BaseComment";
2828

2929
class HTML5Comment extends BaseComment {
3030
override readonly pluginName: string = "HTML5Comment";
31-
constructor(comment: FormattedComment, context: IRenderer) {
32-
super(comment, context);
33-
this.posY = 0;
31+
constructor(comment: FormattedComment, context: IRenderer, index: number) {
32+
super(comment, context, index);
33+
this.posY = -1;
3434
}
3535

3636
override get content() {

src/definition/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const defaultOptions: BaseOptions = {
2626
showFPS: false,
2727
useLegacy: false,
2828
video: undefined,
29+
lazy: false,
2930
};
3031

3132
let config: BaseConfig;

src/main.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class NiconiComments {
5353
public showFPS: boolean;
5454
public showCommentCount: boolean;
5555
private lastVpos: number;
56+
private processedCommentIndex: number;
57+
private comments: IComment[];
5658
private readonly renderer: IRenderer;
5759
private readonly collision: Collision;
5860
private readonly timeline: Timeline;
@@ -135,26 +137,29 @@ class NiconiComments {
135137
right: [],
136138
};
137139
this.lastVpos = -1;
138-
this.preRendering(parsedData);
140+
this.processedCommentIndex = 0;
141+
142+
this.comments = this.preRendering(parsedData);
139143

140144
logger(`constructor complete: ${performance.now() - constructorStart}ms`);
141145
}
142146

143147
/**
144148
* 事前に当たり判定を考慮してコメントの描画場所を決定する
145149
* @param rawData コメントデータ
150+
* @returns コメントのインスタンス配列
146151
*/
147152
private preRendering(rawData: FormattedComment[]) {
148153
const preRenderingStart = performance.now();
149154
if (options.keepCA) {
150155
rawData = changeCALayer(rawData);
151156
}
152-
let instances = rawData.reduce<IComment[]>((pv, val) => {
153-
pv.push(createCommentInstance(val, this.renderer));
157+
let instances = rawData.reduce<IComment[]>((pv, val, index) => {
158+
pv.push(createCommentInstance(val, this.renderer, index));
154159
return pv;
155160
}, []);
156-
this.getCommentPos(instances);
157-
this.sortComment();
161+
this.getCommentPos(instances, instances.length, options.lazy);
162+
this.sortTimelineComment();
158163

159164
const plugins: IPluginList = [];
160165
for (const plugin of config.plugins) {
@@ -175,25 +180,34 @@ class NiconiComments {
175180

176181
setPlugins(plugins);
177182
logger(`preRendering complete: ${performance.now() - preRenderingStart}ms`);
183+
return instances;
178184
}
179185

180186
/**
181187
* 計算された描画サイズをもとに各コメントの配置位置を決定する
182188
* @param data コメントデータ
189+
* @param end 終了インデックス
190+
* @param lazy 遅延処理を行うか
183191
*/
184-
private getCommentPos(data: IComment[]) {
192+
private getCommentPos(data: IComment[], end: number, lazy: boolean = false) {
185193
const getCommentPosStart = performance.now();
186-
for (const comment of data) {
187-
if (comment.invisible) continue;
194+
if (this.processedCommentIndex + 1 >= end) return;
195+
for (const comment of data.slice(this.processedCommentIndex, end)) {
196+
if (comment.invisible || (comment.posY > -1 && !lazy)) continue;
188197
if (comment.loc === "naka") {
189-
processMovableComment(comment, this.collision, this.timeline);
198+
processMovableComment(comment, this.collision, this.timeline, lazy);
190199
} else {
191200
processFixedComment(
192201
comment,
193202
this.collision[comment.loc],
194203
this.timeline,
204+
lazy,
195205
);
196206
}
207+
this.processedCommentIndex = comment.index;
208+
}
209+
if (lazy) {
210+
this.processedCommentIndex = 0;
197211
}
198212
logger(
199213
`getCommentPos complete: ${performance.now() - getCommentPosStart}ms`,
@@ -203,7 +217,7 @@ class NiconiComments {
203217
/**
204218
* 投稿者コメントを前に移動
205219
*/
206-
private sortComment() {
220+
private sortTimelineComment() {
207221
const sortCommentStart = performance.now();
208222
for (const vpos of Object.keys(this.timeline)) {
209223
const item = this.timeline[Number(vpos)];
@@ -228,8 +242,10 @@ class NiconiComments {
228242
* @param rawComments コメントデータ
229243
*/
230244
public addComments(...rawComments: FormattedComment[]) {
231-
const comments = rawComments.reduce<IComment[]>((pv, val) => {
232-
pv.push(createCommentInstance(val, this.renderer));
245+
const comments = rawComments.reduce<IComment[]>((pv, val, index) => {
246+
pv.push(
247+
createCommentInstance(val, this.renderer, this.comments.length + index),
248+
);
233249
return pv;
234250
}, []);
235251
for (const plugin of plugins) {
@@ -333,6 +349,7 @@ class NiconiComments {
333349
if (comment.invisible) {
334350
continue;
335351
}
352+
this.getCommentPos(this.comments, comment.index + 1);
336353
comment.draw(vpos, this.showCollision, cursor);
337354
}
338355
}

src/utils/comment.ts

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -585,25 +585,15 @@ const isBanActive = (vpos: number): boolean => {
585585
* @param comment 固定コメント
586586
* @param collision コメントの衝突判定用配列
587587
* @param timeline コメントのタイムライン
588+
* @param lazy Y座標の計算を遅延させるか
588589
*/
589590
const processFixedComment = (
590591
comment: IComment,
591592
collision: CollisionItem,
592593
timeline: Timeline,
594+
lazy: boolean = false,
593595
) => {
594-
let posY = 0,
595-
isChanged = true,
596-
count = 0;
597-
while (isChanged && count < 10) {
598-
isChanged = false;
599-
count++;
600-
for (let j = 0; j < comment.long; j++) {
601-
const result = getPosY(posY, comment, collision[comment.vpos + j]);
602-
posY = result.currentPos;
603-
isChanged = result.isChanged;
604-
if (result.isBreak) break;
605-
}
606-
}
596+
const posY = lazy ? -1 : getFixedPosY(comment, collision);
607597
for (let j = 0; j < comment.long; j++) {
608598
const vpos = comment.vpos + j;
609599
arrayPush(timeline, vpos, comment);
@@ -618,52 +608,21 @@ const processFixedComment = (
618608
* @param comment nakaコメント
619609
* @param collision コメントの衝突判定用配列
620610
* @param timeline コメントのタイムライン
611+
* @param lazy Y座標の計算を遅延させるか
621612
*/
622613
const processMovableComment = (
623614
comment: IComment,
624615
collision: Collision,
625616
timeline: Timeline,
617+
lazy: boolean = false,
626618
) => {
627619
const beforeVpos =
628620
Math.round(-288 / ((1632 + comment.width) / (comment.long + 125))) - 100;
629-
const posY = (() => {
630-
if (config.canvasHeight < comment.height) {
631-
return (comment.height - config.canvasHeight) / -2;
632-
}
633-
let posY = 0;
634-
let isChanged = true;
635-
while (isChanged) {
636-
isChanged = false;
637-
for (let j = beforeVpos, n = comment.long + 125; j < n; j++) {
638-
const vpos = comment.vpos + j;
639-
const leftPos = getPosX(comment.comment, vpos);
640-
let isBreak = false;
641-
if (
642-
leftPos + comment.width >= config.collisionRange.right &&
643-
leftPos <= config.collisionRange.right
644-
) {
645-
const result = getPosY(posY, comment, collision.right[vpos]);
646-
posY = result.currentPos;
647-
isChanged ||= result.isChanged;
648-
isBreak = result.isBreak;
649-
}
650-
if (
651-
leftPos + comment.width >= config.collisionRange.left &&
652-
leftPos <= config.collisionRange.left
653-
) {
654-
const result = getPosY(posY, comment, collision.left[vpos]);
655-
posY = result.currentPos;
656-
isChanged ||= result.isChanged;
657-
isBreak = result.isBreak;
658-
}
659-
if (isBreak) return posY;
660-
}
661-
}
662-
return posY;
663-
})();
621+
const posY = lazy ? -1 : getMovablePosY(comment, collision, beforeVpos);
664622
for (let j = beforeVpos, n = comment.long + 125; j < n; j++) {
665623
const vpos = comment.vpos + j;
666624
const leftPos = getPosX(comment.comment, vpos);
625+
if (timeline[vpos]?.includes(comment)) break;
667626
arrayPush(timeline, vpos, comment);
668627
if (
669628
leftPos + comment.width + config.collisionPadding >=
@@ -683,6 +642,69 @@ const processMovableComment = (
683642
comment.posY = posY;
684643
};
685644

645+
const getFixedPosY = (comment: IComment, collision: CollisionItem) => {
646+
let posY = 0,
647+
isChanged = true,
648+
count = 0;
649+
while (isChanged && count < 10) {
650+
isChanged = false;
651+
count++;
652+
for (let j = 0; j < comment.long; j++) {
653+
const result = getPosY(posY, comment, collision[comment.vpos + j]);
654+
posY = result.currentPos;
655+
isChanged = result.isChanged;
656+
if (result.isBreak) break;
657+
}
658+
}
659+
return posY;
660+
};
661+
662+
const getMovablePosY = (
663+
comment: IComment,
664+
collision: Collision,
665+
beforeVpos: number,
666+
) => {
667+
if (config.canvasHeight < comment.height) {
668+
return (comment.height - config.canvasHeight) / -2;
669+
}
670+
let posY = 0;
671+
let isChanged = true;
672+
let lastUpdatedIndex: number | undefined = undefined;
673+
while (isChanged) {
674+
isChanged = false;
675+
for (let j = beforeVpos, n = comment.long + 125; j < n; j += 5) {
676+
const vpos = comment.vpos + j;
677+
const leftPos = getPosX(comment.comment, vpos);
678+
let isBreak = false;
679+
if (lastUpdatedIndex !== undefined && lastUpdatedIndex === vpos) {
680+
return posY;
681+
}
682+
if (
683+
leftPos + comment.width >= config.collisionRange.right &&
684+
leftPos <= config.collisionRange.right
685+
) {
686+
const result = getPosY(posY, comment, collision.right[vpos]);
687+
posY = result.currentPos;
688+
isChanged ||= result.isChanged;
689+
if (result.isChanged) lastUpdatedIndex = vpos;
690+
isBreak = result.isBreak;
691+
}
692+
if (
693+
leftPos + comment.width >= config.collisionRange.left &&
694+
leftPos <= config.collisionRange.left
695+
) {
696+
const result = getPosY(posY, comment, collision.left[vpos]);
697+
posY = result.currentPos;
698+
isChanged ||= result.isChanged;
699+
if (result.isChanged) lastUpdatedIndex = vpos;
700+
isBreak = result.isBreak;
701+
}
702+
if (isBreak) return posY;
703+
}
704+
}
705+
return posY;
706+
};
707+
686708
/**
687709
* 当たり判定からコメントを配置できる場所を探す
688710
* @param currentPos 現在のy座標
@@ -700,11 +722,13 @@ const getPosY = (
700722
let isBreak = false;
701723
if (!collision) return { currentPos, isChanged, isBreak };
702724
for (const collisionItem of collision) {
725+
if (collisionItem.index === targetComment.index || collisionItem.posY < 0)
726+
continue;
703727
if (
704-
currentPos < collisionItem.posY + collisionItem.height &&
705-
currentPos + targetComment.height > collisionItem.posY &&
706728
collisionItem.owner === targetComment.owner &&
707-
collisionItem.layer === targetComment.layer
729+
collisionItem.layer === targetComment.layer &&
730+
currentPos < collisionItem.posY + collisionItem.height &&
731+
currentPos + targetComment.height > collisionItem.posY
708732
) {
709733
if (collisionItem.posY + collisionItem.height > currentPos) {
710734
currentPos = collisionItem.posY + collisionItem.height;
@@ -779,6 +803,8 @@ const parseFont = (font: CommentFont, size: string | number): string => {
779803

780804
export {
781805
getDefaultCommand,
806+
getFixedPosY,
807+
getMovablePosY,
782808
getPosX,
783809
getPosY,
784810
isBanActive,

src/utils/plugins.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ import { config } from "@/definition/config";
66
* コメントのインスタンスを生成する
77
* @param comment コメント
88
* @param context 描画対象のCanvasコンテキスト
9+
* @param index コメントのインデックス
910
* @returns プラグインまたは内臓のコメントインスタンス
1011
*/
1112
const createCommentInstance = (
1213
comment: FormattedComment,
1314
context: IRenderer,
15+
index: number,
1416
) => {
1517
for (const plugin of config.commentPlugins) {
1618
if (plugin.condition(comment)) {
17-
return new plugin.class(comment, context);
19+
return new plugin.class(comment, context, index);
1820
}
1921
}
20-
return new HTML5Comment(comment, context);
22+
return new HTML5Comment(comment, context, index);
2123
};
2224

2325
export { createCommentInstance };

0 commit comments

Comments
 (0)