Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/pagination_sim.c
T
MeowOS 997f89f879 ncm 三期三批: 歌曲对象瘦身(1324→608B)+滑动窗口(NCM_SONG_WINDOW=256, 取消1000截断支撑任意长度歌单)+播放快照解耦 — agent-251
- core/ncm_api.h: ncm_song_t 瘦身 id24/name128/art96/al96/pic256/dt int32 (1324B→608B)
- appstate.c 滑动窗口: song_ids 全量 trackIds 索引(16384) + songs[256] 窗口感知
  (song_valid 位图/window_base/滑窗 memmove 保留重叠), ensure_range 批网格(100)对齐
  预取, songmeta.c 盘缓存 <cache_dir>/meta/<id>.bin (回滚/二次进入 0 网络请求)
- playlist.c 触底分页退役→视口 ensure(±24 行), 占位行可直点(播放线程 fetch 物化),
  footer 改视口区间/总数
- 播放兼容: g_play.play_song 快照(mu), run_song 起播物化后全链路(下载/歌词/IPC META/
  播放页 UI)读快照——窗口滑出当前播放曲不崩; 边界 song_count→track_ids_count
- 验证(0 真实请求): pagination_sim 全 PASS(5191 无截断/52 批); window_sim 6000 首
  全程下滚 750 步视口全物化/song_detail 恰 60 次/回滚+0 请求/播放 fetch 不滑窗;
  core 宿主+qemu 全 PASS; mipsel -Wall -Wextra 0 warning, bss 2052068→1080900(-47%)
- 未部署真机(设备音频线排查中, 部署留主会话)
2026-09-01 04:03:01 +08:00

159 lines
7.8 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 网络请求)。
* (§14 版为「分页 append 到 NCM_SONG_MAX=1000 截断」回归;§17 起分页被滑动窗口取代,
* 本文件随之改测兼容入口 appstate_load_playlist_songs_page 的新语义。)
*
* 直接驱动 App 的真实函数 appstate_load_playlist_songs_page()(app/logic/appstate.c),
* 仅把两个网络端点(ncm_api_playlist_detail / ncm_api_song_detail)替换成本地 stub
* (合成 trackIds / 合成歌曲对象),验证:
* ① offset=0 重建索引(song_ids[]←全量 trackIds,track_ids_count)并物化首段;
* ② offset>0 物化 [offset,offset+limit)(滑窗 + song_detail 批补载);
* ③ 尾部(offset≥track_ids_count)无操作;④ 大歌单不再截断(索引全量 5191,
* 窗口内有效槽位 ≤ NCM_SONG_WINDOW);⑤ 占位/出窗槽 song_copy 失败、入窗成功。
* 风控纪律:本验证 0 次真实 NCM 请求(离线 stub;cache_dir 置空 → songmeta 全部 miss/不落盘)。
*
* 编译(宿主):$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 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c app/logic/songmeta.c -o /tmp/ps_meta.o
* $CC_HOST -O2 -Wl,--gc-sections -o pagination_sim /tmp/ps.o /tmp/ps_app.o /tmp/ps_meta.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; (void)playlist_id;
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;
}
/* ---- 驱动:重头 + 逐段 ensure 到尾部,返回 track_ids_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; }
for (int off = NCM_SONG_BATCH; off < g_st.track_ids_count; off += NCM_SONG_BATCH) {
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; }
}
if (detail_calls) *detail_calls = g_detail_calls;
if (song_calls) *song_calls = g_song_calls;
return g_st.track_ids_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)
{
printf("ncm_song_t=%zuB window=%zuKB(%d首) trackIds idx=%zuKB(%d首)\n",
sizeof(ncm_song_t),
sizeof g_st.songs / 1024, NCM_SONG_WINDOW,
sizeof g_st.song_ids / 1024, NCM_TRACKID_MAX);
/* (a) 130 首(< 窗口):读全程后 130 槽全有效(窗口装得下,无需滑窗)。 */
printf("== case A: 130 songs (whole list fits window) ==\n");
int dc = 0, sc = 0;
int n = load_all(130, &dc, &sc);
CHECK(n == 130, "track_ids_count=%d (expect 130)", n);
CHECK(g_st.song_count == 130, "valid slots=%d (expect 130, all in window)", g_st.song_count);
CHECK(g_st.window_base == 0, "window_base=%d (expect 0)", g_st.window_base);
CHECK(dc == 1, "detail calls=%d (expect 1, index fetched once)", dc);
CHECK(sc == 2, "song_detail calls=%d (expect 2 = batches 0-99 + 100-129)", sc);
ncm_song_t sq;
CHECK(appstate_song_copy(129, &sq) == 0 && strcmp(sq.name, "Song1000129") == 0,
"last song copy name='%s'", sq.name);
CHECK(sq.al_pic[0] != 0, "last song has al_pic='%s'", sq.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 valid=%d", rc, g_st.song_count);
/* (b) 5191 首(>> 窗口):索引全量不截断(§17 取消 NCM_SONG_MAX=1000 上限),
* 窗口有效槽恒 ≤ NCM_SONG_WINDOW,末段滑窗后末首可达。 */
printf("== case B: 5191 songs (no cap; window slides) ==\n");
dc = sc = 0;
n = load_all(5191, &dc, &sc);
CHECK(n == 5191, "track_ids_count=%d (expect 5191, 不再 1000 截断)", n);
CHECK(g_st.song_count <= NCM_SONG_WINDOW, "valid slots=%d ≤ window %d", g_st.song_count, NCM_SONG_WINDOW);
CHECK(dc == 1, "detail calls=%d", dc);
CHECK(sc == (5191 + NCM_SONG_BATCH - 1) / NCM_SONG_BATCH,
"song_detail calls=%d (expect %d grid batches)", sc, (5191 + NCM_SONG_BATCH - 1) / NCM_SONG_BATCH);
CHECK(appstate_song_copy(5190, &sq) == 0 && strcmp(sq.name, "Song1005190") == 0,
"tail song copy name='%s'", sq.name);
CHECK(appstate_song_copy(0, &sq) != 0, "head song evicted (out of window) -> copy fails");
/* 越界再 load:应 NCM_OK 但不动窗口 */
int valid_before = g_st.song_count;
rc = appstate_load_playlist_songs_page(0, 5191, NCM_SONG_BATCH);
CHECK(rc == NCM_OK && g_st.song_count == valid_before, "load past tail: rc=%d valid=%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.song_count == 0, "empty: track_ids_count=%d valid=%d", n, g_st.song_count);
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;
}