Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/pagination_sim.c
T
MeowOS ee104f1b18 feat(ncm): 三期第一批 — 歌单真分页(取消256截断)+专辑封面ncm_cache+歌词后台加载补锁
[agent-239] 仅改 patch_v2/ncm_app/。

1. 歌单分页(REPORT §14.1): track/all 全 404(§13.1) 弃用, 回 detail(n=1)全量 trackIds
   + song_detail 分批(≤NCM_SONG_BATCH=100) 触底逐页加载。appstate_load_playlist_songs_page
   offset=0 重建 song_ids[]/track_ids_count 载第0页, offset>0 追加(失败保留已载);
   playlist.c g_pl_more=(song_count<track_ids_count && <NCM_SONG_MAX), 触底 g_ploffset=song_count
   起下一页(保留滚动), 首屏失败整页空态+点按重试, 追加失败 snack+保留已载。NCM_SONG_MAX
   256→1000(单页渲染上限, bss +~1MB), NCM_SONG_BATCH 64→100, 删 NCM_PLAYLIST_PAGE(track/all 遗留)。

2. 专辑封面(§14.2): cover.c 封面改走 ncm_cache(LRU, 键=djb2(url)→song_cov<h>.jpg 同池),
   缓存命中免下载/未命中下载→open/write/commit 落盘, 仍写 cover_current.jpg 供 launcher;
   顺带修真 bug: save_cover_raw 在已持 g_cm 临界区内再 lock(非递归死锁)→封面永不刷新, 改调用方持锁。
   歌单列表/详情头块封面保持现状。

3. 歌词后台加载(§14.3): lyric_worker 已异步(§11)未改线程模型; 补 lyric.c g_lyric_mu 数据竞争锁,
   写侧 lyric_parse 整段持锁(清空用 lyric_clear_nolock 免二次取锁), 读侧 lyric_count/line_at/
   line_text/line_tl 逐次取锁, 消除 UI/IPC 与 worker 间数据竞争(原 §10.9 FM4 良性竞态)。

验证(0 真实 NCM 请求): bench/pagination_sim.c 离线 stub 驱动真实 appstate 分页函数全 PASS
(130首读全程/5191首封顶1000/空/越界无操作); mipsel build_ncm.sh 0 warning(text 842820/bss 2052068);
宿主 build_ncm_host.sh+MEOW_UI_DUMP home/playlist/player/lands 渲染正常无崩溃; host SDK_SRC 补
meow_pinyin(拼音IME py_predict 漏编致链接断)。ncm md5 b34bc1ed, ncm_host md5 1a2327e0。
未动 sdk/(主会话改 meow_ui_ime.c)/meow_launcherd/meow_amsd/vendor/core; 未 commit 它们。
2026-08-31 19:33:10 +08:00

145 lines
6.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* bench/pagination_sim.c — 三期第一批 歌单分页 离线状态机验证(0 网络请求)。
*
* 直接驱动 App 的真实函数 appstate_load_playlist_songs_page()(app/logic/appstate.c),
* 仅把两个网络端点(ncm_api_playlist_detail / ncm_api_song_detail)替换成本地 stub
* (合成 trackIds / 合成歌曲对象),验证 detail 后按页 song_detail 的累加/封顶/尾部语义:
* ① offset=0 重建索引(song_ids[]←全量 trackIds,track_ids_count)并载第 0 页;
* ② 逐页 offset 前进,song_count 单调累加;
* ③ 尾部(song_count==track_ids_count)不再载下一页;④ NCM_SONG_MAX 封顶不越界。
* 风控纪律:本验证 0 次真实 NCM 请求(离线 stub)。歌曲上限 = NCM_SONG_MAX(内存封顶);
* 本模拟用两种歌单规模:(a) 130 首(< 上限,读全程);(b) 5191 首(> 上限,读满 NCM_SONG_MAX 截断)。
*
* 编译(宿主):$CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c bench/pagination_sim.c -o /tmp/ps.o
* $CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c app/logic/appstate.c -o /tmp/ps_app.o
* $CC_HOST -O2 -Wl,--gc-sections -o pagination_sim /tmp/ps.o /tmp/ps_app.o -lpthread
* 运行:./pagination_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* appstate.c 依赖的全局(app.c 提供;本 bench 只测分页路径,仅 g_st 被用到) */
ncm_app_state_t g_st;
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER, .state = NCM_PC_STOPPED, .cur_song = -1 };
volatile int g_app_quit = 0;
volatile int g_net_ready = 0;
int g_host_mock = 0;
/* ncm_strlcpy:appstate_apply_playlist_cover 用到(从 core ncm_common 借;此处最小内联实现) */
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
{
size_t n = 0;
if (sz) {
while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; }
dst[n] = 0;
}
while (src[n]) n++;
return n;
}
/* ---- 网络端点 stub(离线,合成数据) ---- */
static int g_pid_total; /* detail 应返回的 trackIds 总数(供测试注入) */
static int g_detail_calls;
static int g_song_calls;
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
char *name, size_t name_sz, char *cover, size_t cover_sz,
int64_t *ids, int id_cap, int *out_id_n)
{
(void)device_id; (void)cookie; (void)cover_sz;
g_detail_calls++;
if (name) snprintf(name, name_sz, "Sim Playlist");
if (cover) cover[0] = 0;
int n = (g_pid_total < id_cap) ? g_pid_total : id_cap;
for (int i = 0; i < n; i++) ids[i] = 1000000LL + i; /* 合成连续 id */
*out_id_n = n;
return NCM_OK;
}
int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t *ids, int n,
ncm_song_t *out, int max, int *out_n)
{
(void)device_id; (void)cookie;
g_song_calls++;
int m = (n < max) ? n : max;
for (int i = 0; i < m; i++) {
snprintf(out[i].id, sizeof out[i].id, "%lld", (long long)ids[i]);
snprintf(out[i].name, sizeof out[i].name, "Song%lld", (long long)ids[i]);
snprintf(out[i].art_name, sizeof out[i].art_name, "Artist%lld", (long long)ids[i]);
snprintf(out[i].al_pic, sizeof out[i].al_pic, "http://p.music.126.net/cov%lld.jpg", (long long)ids[i]);
}
*out_n = m;
return NCM_OK;
}
/* ---- 驱动:整批载入到尾部/上限,返回最终 song_count ---- */
static int load_all(int pid_total, int *detail_calls, int *song_calls)
{
memset(&g_st, 0, sizeof g_st);
g_st.playlist_count = 1;
snprintf(g_st.playlists[0].id, sizeof g_st.playlists[0].id, "pl0");
g_st.playlists[0].track_count = pid_total;
g_pid_total = pid_total;
g_detail_calls = 0; g_song_calls = 0;
int rc = appstate_load_playlist_songs_page(0, 0, NCM_SONG_BATCH);
if (rc != NCM_OK) { printf(" page0 rc=%d\n", rc); return -1; }
int off = g_st.song_count;
while (off < g_st.track_ids_count && off < NCM_SONG_MAX) {
rc = appstate_load_playlist_songs_page(0, off, NCM_SONG_BATCH);
if (rc != NCM_OK) { printf(" page(%d) rc=%d\n", off, rc); return -1; }
int nxt = g_st.song_count;
if (nxt <= off) break;
off = nxt;
}
if (detail_calls) *detail_calls = g_detail_calls;
if (song_calls) *song_calls = g_song_calls;
return g_st.song_count;
}
static int g_fail = 0;
#define CHECK(cond, fmt, ...) do { \
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
} while (0)
int main(void)
{
/* (a) 130 首(< NCM_SONG_MAX):应读全程,track_ids_count=130,song_count=130,尾部停。 */
printf("== case A: 130 songs (reads to tail) ==\n");
int dc = 0, sc = 0;
int n = load_all(130, &dc, &sc);
CHECK(n == 130, "song_count=%d (expect 130)", n);
CHECK(g_st.track_ids_count == 130, "track_ids_count=%d (expect 130)", g_st.track_ids_count);
CHECK(dc == 1, "detail calls=%d (expect 1, index fetched once)", dc);
CHECK(sc == 2, "song_detail calls=%d (expect 2 = 100 + 30 pages)", sc);
CHECK(strcmp(g_st.songs[129].name, "Song1000129") == 0, "last song name='%s'", g_st.songs[129].name);
CHECK(g_st.songs[129].al_pic[0] != 0, "last song has al_pic='%s'", g_st.songs[129].al_pic);
/* 越过尾部再 load:应无操作(不越界) */
int rc = appstate_load_playlist_songs_page(0, 130, NCM_SONG_BATCH);
CHECK(rc == NCM_OK && g_st.song_count == 130, "load past tail: rc=%d song_count=%d", rc, g_st.song_count);
/* (b) 5191 首(> NCM_SONG_MAX):索引封顶 NCM_SONG_MAX,读满封顶截断。 */
printf("== case B: 5191 songs (capped at NCM_SONG_MAX) ==\n");
dc = sc = 0;
n = load_all(5191, &dc, &sc);
CHECK(n == NCM_SONG_MAX, "song_count=%d (expect NCM_SONG_MAX=%d)", n, NCM_SONG_MAX);
CHECK(g_st.track_ids_count == NCM_SONG_MAX, "track_ids_count=%d (index capped)", g_st.track_ids_count);
CHECK(dc == 1, "detail calls=%d", dc);
CHECK(sc == NCM_SONG_MAX / NCM_SONG_BATCH, "song_detail calls=%d (expect %d full pages)", sc, NCM_SONG_MAX / NCM_SONG_BATCH);
/* 越界再 load:应 NCM_OK 但不动 song_count */
rc = appstate_load_playlist_songs_page(0, NCM_SONG_MAX, NCM_SONG_BATCH);
CHECK(rc == NCM_OK && g_st.song_count == NCM_SONG_MAX, "load past cap: rc=%d song_count=%d", rc, g_st.song_count);
/* (c) 空歌单(0 首) */
printf("== case C: empty playlist ==\n");
n = load_all(0, &dc, &sc);
CHECK(n == 0 && g_st.track_ids_count == 0, "empty: song_count=%d track_ids_count=%d", n, g_st.track_ids_count);
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;
}