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%) - 未部署真机(设备音频线排查中, 部署留主会话)
This commit is contained in:
1 parent
d8898afa09
commit
997f89f879
18 files changed
+987
-209
No files matched your search
@@ -1,19 +1,23 @@
|
||||
/* bench/pagination_sim.c — 三期第一批 歌单分页 离线状态机验证(0 网络请求)。
|
||||
/* 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 / 合成歌曲对象),验证 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 截断)。
|
||||
* (合成 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 -Wl,--gc-sections -o pagination_sim /tmp/ps.o /tmp/ps_app.o -lpthread
|
||||
* $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"
|
||||
@@ -21,7 +25,7 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* appstate.c 依赖的全局(app.c 提供;本 bench 只测分页路径,仅 g_st 被用到) */
|
||||
/* 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;
|
||||
@@ -49,7 +53,7 @@ int ncm_api_playlist_detail(const char *device_id, const char *cookie, const cha
|
||||
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)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;
|
||||
@@ -75,7 +79,7 @@ int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
/* ---- 驱动:整批载入到尾部/上限,返回最终 song_count ---- */
|
||||
/* ---- 驱动:重头 + 逐段 ensure 到尾部,返回 track_ids_count ---- */
|
||||
static int load_all(int pid_total, int *detail_calls, int *song_calls)
|
||||
{
|
||||
memset(&g_st, 0, sizeof g_st);
|
||||
@@ -87,17 +91,13 @@ static int load_all(int pid_total, int *detail_calls, int *song_calls)
|
||||
|
||||
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) {
|
||||
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; }
|
||||
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;
|
||||
return g_st.track_ids_count;
|
||||
}
|
||||
|
||||
static int g_fail = 0;
|
||||
@@ -108,36 +108,50 @@ static int g_fail = 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");
|
||||
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, "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(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 = 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);
|
||||
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 song_count=%d", rc, g_st.song_count);
|
||||
CHECK(rc == NCM_OK && g_st.song_count == 130, "load past tail: rc=%d valid=%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");
|
||||
/* (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 == 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(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 == 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);
|
||||
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.track_ids_count == 0, "empty: song_count=%d track_ids_count=%d", n, g_st.track_ids_count);
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/* bench/window_sim.c — 三期第三批 滑动窗口 6000 首滚动全程离线验证(0 网络请求)。
|
||||
*
|
||||
* 驱动真实 appstate.c(滑动窗口)+ 真实 songmeta.c(盘缓存,/tmp 临时目录),
|
||||
* 网络端点(ncm_api_playlist_detail / ncm_api_song_detail)为本地合成 stub。
|
||||
* 场景(对齐用户「我喜欢」6000+ 首):
|
||||
* ① 重头:detail 取全量 6000 trackIds,物化首段(1 detail + 1 song_detail 批);
|
||||
* ② 全程下滚:视口 12 行、步进 8 行(模拟跟手拖动),每步 pl_maybe_ensure 等价物
|
||||
* ensure(pos-24, pos+36);每步断言视口 12 行全部物化(列表无占位破窗);
|
||||
* 断言 song_detail 调用数 ≈ 6000/批网格(批对齐预取,无小请求放大);
|
||||
* ③ 全程回滚到 0:盘缓存(songmeta)全命中,song_detail 调用数 0 增长;
|
||||
* ④ 播放兼容:窗口感知的取歌——播放线程路径 appstate_song_fetch 在窗口远离时
|
||||
* 仍能物化当前播放曲(不滑窗、不崩、盘缓存命中免请求);
|
||||
* ⑤ 尾部/越界:ensure 尾部区间 + 越过尾部无操作;
|
||||
* ⑥ 内存账:6000 首场景改前(1000 截断 × 1324B)vs 改后(窗口 256×608B + 索引)。
|
||||
* 风控纪律:0 次真实 NCM 请求(离线 stub + /tmp 盘缓存)。
|
||||
*
|
||||
* 编译(宿主):$CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
* -Icore -I../vendor/jsmn -c bench/window_sim.c -o /tmp/ws.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/ws_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/ws_meta.o
|
||||
* $CC_HOST -O2 -Wl,--gc-sections -o window_sim /tmp/ws.o /tmp/ws_app.o /tmp/ws_meta.o -lpthread
|
||||
* 运行:./window_sim (0 网络;PASS/FAIL 退出码)
|
||||
*/
|
||||
#include "../app/ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
|
||||
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(离线,合成数据) ---- */
|
||||
#define SIM_TOTAL 6000
|
||||
static int g_detail_calls;
|
||||
static int g_song_calls;
|
||||
static int g_song_served; /* song_detail 累计返回首数 */
|
||||
|
||||
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 = (SIM_TOTAL < id_cap) ? SIM_TOTAL : id_cap;
|
||||
for (int i = 0; i < n; i++) ids[i] = 2000000LL + i;
|
||||
*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;
|
||||
g_song_served += m;
|
||||
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].album_name, sizeof out[i].album_name, "Album%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[i].duration_ms = 200000;
|
||||
}
|
||||
*out_n = m;
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/* 模拟 playlist.c pl_maybe_ensure:视口 12 行 ±24 行余量。 */
|
||||
static int ui_ensure(int pos)
|
||||
{
|
||||
int lo = pos - 24, hi = pos + 12 + 24;
|
||||
if (lo < 0) lo = 0;
|
||||
if (hi > SIM_TOTAL) hi = SIM_TOTAL;
|
||||
if (lo >= hi) return NCM_OK;
|
||||
if (appstate_range_loaded(lo, hi)) return NCM_OK;
|
||||
return appstate_song_ensure_range(lo, hi);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
if (system("rm -rf /tmp/ncm_window_sim_cache") != 0) { /* 清上次运行残留(盘缓存) */
|
||||
printf("FAIL cannot clean /tmp cache dir\n");
|
||||
return 1;
|
||||
}
|
||||
memset(&g_st, 0, sizeof g_st);
|
||||
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "/tmp/ncm_window_sim_cache");
|
||||
g_st.playlist_count = 1;
|
||||
snprintf(g_st.playlists[0].id, sizeof g_st.playlists[0].id, "pl6000");
|
||||
g_st.playlists[0].track_count = SIM_TOTAL;
|
||||
|
||||
size_t win_bytes = sizeof g_st.songs + sizeof g_st.song_valid;
|
||||
size_t idx_bytes = sizeof g_st.song_ids;
|
||||
size_t old_bytes = (size_t)1000 * 1324; /* §14:NCM_SONG_MAX=1000 × ~1324B/首 */
|
||||
printf("== memory model ==\n");
|
||||
printf(" ncm_song_t=%zuB (old 1324B) window=%zuB(%d首) trackIds idx=%zuB(%d首)\n",
|
||||
sizeof(ncm_song_t), win_bytes, NCM_SONG_WINDOW, idx_bytes, NCM_TRACKID_MAX);
|
||||
printf(" 6000首场景:旧模型 %zuKB(且 1000 截断)→ 新模型 %zuKB(任意长度恒定)\n",
|
||||
old_bytes / 1024, (win_bytes + idx_bytes) / 1024);
|
||||
|
||||
/* ① 重头 */
|
||||
printf("== phase 1: open 6000-song playlist ==\n");
|
||||
int rc = appstate_load_playlist_songs_page(0, 0, NCM_SONG_BATCH);
|
||||
CHECK(rc == NCM_OK, "page0 rc=%d", rc);
|
||||
CHECK(g_st.track_ids_count == SIM_TOTAL, "track_ids_count=%d (expect %d, 无截断)",
|
||||
g_st.track_ids_count, SIM_TOTAL);
|
||||
CHECK(g_st.song_count == NCM_SONG_BATCH, "valid slots=%d (expect first batch %d)",
|
||||
g_st.song_count, NCM_SONG_BATCH);
|
||||
CHECK(g_st.window_base == 0, "window_base=%d", g_st.window_base);
|
||||
CHECK(g_detail_calls == 1 && g_song_calls == 1, "detail=%d song_detail=%d (expect 1+1)",
|
||||
g_detail_calls, g_song_calls);
|
||||
|
||||
/* ② 全程下滚(0→6000,步进 8 行):视口每步全部物化 */
|
||||
printf("== phase 2: scroll down 0 -> %d (step 8 rows) ==\n", SIM_TOTAL);
|
||||
int step_checks = 0;
|
||||
for (int pos = 0; pos < SIM_TOTAL; pos += 8) {
|
||||
rc = ui_ensure(pos);
|
||||
if (rc != NCM_OK) { printf(" FAIL ensure(pos=%d) rc=%d\n", pos, rc); g_fail = 1; break; }
|
||||
int vhi = pos + 12; if (vhi > SIM_TOTAL) vhi = SIM_TOTAL;
|
||||
if (!appstate_range_loaded(pos, vhi)) {
|
||||
printf(" FAIL viewport [%d,%d) not fully materialized\n", pos, vhi);
|
||||
g_fail = 1; break;
|
||||
}
|
||||
step_checks++;
|
||||
}
|
||||
CHECK(step_checks == (SIM_TOTAL + 7) / 8, "scroll steps verified=%d (expect %d)",
|
||||
step_checks, (SIM_TOTAL + 7) / 8);
|
||||
CHECK(g_st.song_count <= NCM_SONG_WINDOW, "valid slots=%d ≤ window %d (全程不超窗)",
|
||||
g_st.song_count, NCM_SONG_WINDOW);
|
||||
printf(" INFO song_detail calls=%d served=%d (grid prefetch; 下限 %d)\n",
|
||||
g_song_calls, g_song_served, SIM_TOTAL / NCM_SONG_BATCH);
|
||||
CHECK(g_song_calls <= SIM_TOTAL / NCM_SONG_BATCH + 6,
|
||||
"song_detail calls=%d ≤ %d (批网格对齐,无小请求放大)",
|
||||
g_song_calls, SIM_TOTAL / NCM_SONG_BATCH + 6);
|
||||
ncm_song_t sq;
|
||||
CHECK(appstate_song_copy(SIM_TOTAL - 1, &sq) == 0 &&
|
||||
strcmp(sq.name, "Song2005999") == 0, "tail song name='%s'", sq.name);
|
||||
CHECK(appstate_song_copy(0, &sq) != 0, "head evicted after scrolling to tail");
|
||||
|
||||
/* ③ 全程回滚到 0:盘缓存全命中,0 新增 song_detail */
|
||||
printf("== phase 3: scroll back up %d -> 0 (disk cache) ==\n", SIM_TOTAL);
|
||||
int calls_before = g_song_calls;
|
||||
step_checks = 0;
|
||||
for (int pos = SIM_TOTAL - 8; pos >= 0; pos -= 8) {
|
||||
rc = ui_ensure(pos);
|
||||
if (rc != NCM_OK) { printf(" FAIL ensure(pos=%d) rc=%d\n", pos, rc); g_fail = 1; break; }
|
||||
int vhi = pos + 12; if (vhi > SIM_TOTAL) vhi = SIM_TOTAL;
|
||||
if (!appstate_range_loaded(pos, vhi)) {
|
||||
printf(" FAIL viewport [%d,%d) not materialized on way back\n", pos, vhi);
|
||||
g_fail = 1; break;
|
||||
}
|
||||
step_checks++;
|
||||
}
|
||||
CHECK(g_song_calls == calls_before,
|
||||
"scroll-back song_detail calls +%d (expect 0,盘缓存全命中)", g_song_calls - calls_before);
|
||||
CHECK(appstate_song_copy(0, &sq) == 0 && strcmp(sq.name, "Song2000000") == 0,
|
||||
"head re-materialized from disk: name='%s'", sq.name);
|
||||
|
||||
/* ④ 播放兼容:窗口滑出当前播放曲 → fetch 不滑窗、不崩、盘命中免请求 */
|
||||
printf("== phase 4: playback survives window slide ==\n");
|
||||
rc = appstate_song_ensure_range(3000 - 128, 3000 + 128); /* 窗口移到 3000 附近 */
|
||||
int base_at_play = g_st.window_base;
|
||||
CHECK(rc == NCM_OK, "ensure around 3000 rc=%d", rc);
|
||||
/* 模拟 run_song:cur=3050 在窗内 → song_copy;然后窗口滑回 0,再 fetch(3050) */
|
||||
CHECK(appstate_song_copy(3050, &sq) == 0, "song_copy(3050) in window");
|
||||
rc = appstate_song_ensure_range(0, 100); /* 用户滚回顶部(窗口滑走) */
|
||||
CHECK(rc == NCM_OK && g_st.window_base != base_at_play,
|
||||
"window slid away base %d -> %d", base_at_play, g_st.window_base);
|
||||
CHECK(appstate_song_copy(3050, &sq) != 0, "playing song evicted from window");
|
||||
calls_before = g_song_calls;
|
||||
rc = appstate_song_fetch(3050, &sq); /* 播放线程路径:不滑窗 */
|
||||
CHECK(rc == NCM_OK && strcmp(sq.id, "2003050") == 0 && sq.duration_ms == 200000,
|
||||
"fetch(3050) ok id=%s dur=%d", sq.id, sq.duration_ms);
|
||||
CHECK(g_st.window_base != base_at_play && g_st.song_count <= NCM_SONG_WINDOW,
|
||||
"fetch did not move window (base=%d)", g_st.window_base);
|
||||
CHECK(g_song_calls == calls_before, "fetch hit disk cache (0 new requests)");
|
||||
/* next/prev 边界语义:cur 可到 SIM_TOTAL-1,不越界不崩 */
|
||||
CHECK(appstate_song_fetch(SIM_TOTAL - 1, &sq) == NCM_OK, "fetch(tail) ok");
|
||||
CHECK(appstate_song_fetch(SIM_TOTAL, &sq) != NCM_OK, "fetch(out of range) rejected");
|
||||
|
||||
/* ⑤ 尾部/越界 ensure */
|
||||
printf("== phase 5: tail & out-of-range ==\n");
|
||||
rc = appstate_song_ensure_range(SIM_TOTAL - 12, SIM_TOTAL);
|
||||
CHECK(rc == NCM_OK && appstate_range_loaded(SIM_TOTAL - 12, SIM_TOTAL),
|
||||
"tail ensure rc=%d", rc);
|
||||
CHECK(g_st.window_base <= SIM_TOTAL - NCM_SONG_WINDOW ||
|
||||
SIM_TOTAL < NCM_SONG_WINDOW, "base=%d clamped at tail", g_st.window_base);
|
||||
rc = appstate_song_ensure_range(SIM_TOTAL, SIM_TOTAL + 100);
|
||||
CHECK(rc == NCM_OK, "ensure past tail no-op rc=%d", rc);
|
||||
rc = appstate_song_ensure_range(-50, 0);
|
||||
CHECK(rc == NCM_OK, "ensure empty range no-op rc=%d", rc);
|
||||
|
||||
/* ⑥ 盘缓存规模核对 */
|
||||
printf("== phase 6: disk cache ==\n");
|
||||
{
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"ls /tmp/ncm_window_sim_cache/meta | wc -l");
|
||||
FILE *p = popen(cmd, "r");
|
||||
int files = -1;
|
||||
if (p) { if (fscanf(p, "%d", &files) != 1) files = -1; pclose(p); }
|
||||
CHECK(files == SIM_TOTAL, "songmeta files=%d (expect %d, 全程拉过即落盘)", files, SIM_TOTAL);
|
||||
}
|
||||
|
||||
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user