From e19f57369923732bff5977734b91afd6e6d3c5c1 Mon Sep 17 00:00:00 2001 From: sunlei Date: Tue, 16 Jun 2026 07:38:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=A1=BA=E5=BA=8F=E6=B8=B2=E6=9F=93Bang?= =?UTF-8?q?Dream=E6=AD=8C=E6=9B=B2=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/song/song-search.renderer.ts | 46 +++++++++++++------ .../song/song-search-rendering.spec.ts | 39 ++++++++++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 test/qqbot/plugins/bangdream/song/song-search-rendering.spec.ts diff --git a/src/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer.ts b/src/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer.ts index d69c5e8..ede8126 100644 --- a/src/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer.ts +++ b/src/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer.ts @@ -54,20 +54,10 @@ export async function drawSongList( let tempSongImageList: Canvas[] = []; const songImageListHorizontal: Canvas[] = []; let tempH = 0; - const songPromises: Promise[] = []; - - for (let i = 0; i < tempSongList.length; i++) { - songPromises.push( - drawSongInList( - tempSongList[i], - undefined, - undefined, - displayedServerList, - ), - ); - } - - const songImages = await Promise.all(songPromises); + const songImages = await renderSongListItemsSequentially( + tempSongList, + displayedServerList, + ); for (let i = 0; i < songImages.length; i++) { const tempImage = songImages[i]; @@ -131,3 +121,31 @@ export const matchSongList = createBangDreamEntityMatcher({ */ relationValue: (song) => song.songId, }); + +export type SongListItemRenderer = ( + song: Song, + difficulty: number | undefined, + text: string | undefined, + displayedServerList: Server[], +) => Promise; + +/** + * 在歌曲列表中顺序渲染单项,避免并发 Skia 图片解码导致 native 内存峰值过高。 + * + * @param songs - 待绘制歌曲列表。 + * @param displayedServerList - 允许展示或下载资源的服务器优先级列表。 + * @param renderItem - 单项渲染函数,未传入时使用默认歌曲列表渲染器。 + */ +export async function renderSongListItemsSequentially( + songs: Song[], + displayedServerList: Server[], + renderItem: SongListItemRenderer = drawSongInList, +): Promise { + const songImages: Canvas[] = []; + for (const song of songs) { + songImages.push( + await renderItem(song, undefined, undefined, displayedServerList), + ); + } + return songImages; +} diff --git a/test/qqbot/plugins/bangdream/song/song-search-rendering.spec.ts b/test/qqbot/plugins/bangdream/song/song-search-rendering.spec.ts new file mode 100644 index 0000000..de58109 --- /dev/null +++ b/test/qqbot/plugins/bangdream/song/song-search-rendering.spec.ts @@ -0,0 +1,39 @@ +import { Server } from '@/modules/qqbot/plugins/bangdream/src/domain/catalog/server.model'; +import type { Song } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song.model'; + +describe('BangDream song search rendering', () => { + it('renders song list items sequentially to keep canvas memory bounded', async () => { + const { renderSongListItemsSequentially } = await import( + '@/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer' + ); + let active = 0; + let maxActive = 0; + const order: number[] = []; + const songs = [1, 2, 3].map( + (songId) => + ({ + songId, + }) as Song, + ); + + const images = await renderSongListItemsSequentially( + songs, + [Server.cn, Server.jp], + async (song) => { + active++; + maxActive = Math.max(maxActive, active); + await Promise.resolve(); + order.push(song.songId); + active--; + return { + height: song.songId, + width: song.songId, + } as any; + }, + ); + + expect(maxActive).toBe(1); + expect(order).toEqual([1, 2, 3]); + expect(images.map((image) => image.width)).toEqual([1, 2, 3]); + }); +});