fix(sdk): 从公开仓回补 M19 滚动池修复(free 归还 wrap 槽)+ 宿主回归

meow_scroll_wrap 从 MEOW_SCROLL_POOL(=8) 个 wrap 上下文槽占一个,meow_scroll_free
只归还会话槽、从不归还 wrap 槽也不清 ctx.w → 进程内最多成功 wrap 8 次,之后恒 NULL。
主仓此前仍是有缺陷版本;本提交把公开 SDK 仓 c85877b 的修复按内容移植到主仓(唯一权威
源),meow_ui_scroll.c 结果与公开仓 sdk/meow_ui_scroll.c 逐字节一致,diff 仅 wrap 相关
hunk(之前两文件差异也只有这些 hunk,故无其它改动被覆盖)。

- meow_scroll 结构记 wrap_slot(create 置 -1),wrap 时写槽下标;free 按该下标清
  g_wrap_ctx[].w/lv 并归还 g_wrap_used,随后置 -1(二次 free 不触碰任何槽)。
  直接 create 的实例 wrap_slot=-1,free 不误清别人的槽。池满语义不变:8 个同时活跃时
  第 9 个 wrap 仍返回 NULL;free(NULL) 安全。
- test_meow_ui_scroll 断言 17 → 44:64 次 wrap/free(修复前 8/64、修复后 64/64)、
  free 后再 wrap、8 满 + 第 9 个 NULL + 释放其一即复用、交错 free 不串槽、直接 create
  不误清槽、list→listview→list 槽复用不残留 kind、free(NULL)/二次 free。
  头注释补 meow_ui_nav.c:原配方缺该 TU,链接期 meow_ui_nav_request_exit 未定义。

验证:LAUNCHER_DIR=主检出/patch_v2/meow_launcherd build_sdk_host.sh --verify
exit=0、0 warning/error、成员清单 76 项一致且全 MIPS32r2;宿主测试 44 pass / 0 fail;
同测试对修复前 meow_ui_scroll.c 为 13 pass / 19 fail(首条即 ok=8 first_fail=8)。
meow_ui.a 中 meow_ui_scroll.o 已变化,链 meow_ui.a 的 App 需重链。
This commit is contained in:
AskaEth committed 2026-11-05 20:59:36 +08:00
1 parent c2635daf3f
commit 72c6e9889d
2 files changed
+244 -6

No files matched your search

+20 -5
View File
@@ -52,11 +52,18 @@ struct meow_scroll {
int bounce; /* fling 边界回弹幅度(带符号;0=不回弹) */ int bounce; /* fling 边界回弹幅度(带符号;0=不回弹) */
int bounce_start; /* 回弹回位起点(= clamp 边界) */ int bounce_start; /* 回弹回位起点(= clamp 边界) */
int snap_to; /* snap 目标 */ int snap_to; /* snap 目标 */
int wrap_slot; /* >=0:占用的 g_wrap_ctx 槽下标(-1=非 wrap 实例) */
}; };
static meow_scroll g_pool[MEOW_SCROLL_POOL]; static meow_scroll g_pool[MEOW_SCROLL_POOL];
static unsigned char g_used[MEOW_SCROLL_POOL]; static unsigned char g_used[MEOW_SCROLL_POOL];
/* wrap 上下文槽:meow_scroll_wrap 占用一个,meow_scroll_free 必须归还(否则进程内最多 wrap
* MEOW_SCROLL_POOL 次后恒 NULL)。槽由 wrap 实例独占,free 时按 wrap_slot 精确归还。 */
typedef struct { meow_widget *w; int lv; } wrap_ctx;
static wrap_ctx g_wrap_ctx[MEOW_SCROLL_POOL];
static int g_wrap_used[MEOW_SCROLL_POOL];
/* ---- 生命周期 ---- */ /* ---- 生命周期 ---- */
meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max, meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
meow_scroll_get_fn get, meow_scroll_redraw_fn redraw, meow_scroll_get_fn get, meow_scroll_redraw_fn redraw,
@@ -70,6 +77,7 @@ meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
memset(s, 0, sizeof *s); memset(s, 0, sizeof *s);
s->set = set; s->max = max; s->get = get; s->redraw = redraw; s->ctx = ctx; s->set = set; s->max = max; s->get = get; s->redraw = redraw; s->ctx = ctx;
s->tau_ms = MEOW_SCROLL_TAU; s->tau_ms = MEOW_SCROLL_TAU;
s->wrap_slot = -1;
return s; return s;
} }
} }
@@ -81,11 +89,20 @@ void meow_scroll_free(meow_scroll *s)
if (s->anim) meow_anim_cancel(s->anim); if (s->anim) meow_anim_cancel(s->anim);
s->anim = NULL; s->anim = NULL;
for (int i = 0; i < MEOW_SCROLL_POOL; i++) for (int i = 0; i < MEOW_SCROLL_POOL; i++)
if (&g_pool[i] == s) { g_used[i] = 0; return; } if (&g_pool[i] == s) {
int ws = s->wrap_slot;
if (ws >= 0 && ws < MEOW_SCROLL_POOL) { /* wrap 实例:归还上下文槽 */
g_wrap_ctx[ws].w = NULL;
g_wrap_ctx[ws].lv = 0;
g_wrap_used[ws] = 0;
}
s->wrap_slot = -1; /* 二次 free 时不再触碰任何槽 */
g_used[i] = 0;
return;
}
} }
/* ---- wrap:包一个 meow_widget 列表 ---- */ /* ---- wrap:包一个 meow_widget 列表 ---- */
typedef struct { meow_widget *w; int lv; } wrap_ctx;
static void wrap_set(void *c, int off) static void wrap_set(void *c, int off)
{ wrap_ctx *wc = c; if (wc->lv) meow_listview_set_scroll(wc->w, off); else meow_list_set_scroll(wc->w, off); } { wrap_ctx *wc = c; if (wc->lv) meow_listview_set_scroll(wc->w, off); else meow_list_set_scroll(wc->w, off); }
static int wrap_max(void *c) static int wrap_max(void *c)
@@ -100,9 +117,6 @@ static void wrap_redraw(void *c)
if (r[2] > 0 && r[3] > 0) meow_ui_flush_region(r[0], r[1], r[2], r[3]); /* 只刷视口区 */ if (r[2] > 0 && r[3] > 0) meow_ui_flush_region(r[0], r[1], r[2], r[3]); /* 只刷视口区 */
} }
static wrap_ctx g_wrap_ctx[MEOW_SCROLL_POOL];
static int g_wrap_used[MEOW_SCROLL_POOL];
meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview) meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview)
{ {
if (!list) return NULL; if (!list) return NULL;
@@ -113,6 +127,7 @@ meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview)
g_wrap_used[i] = 1; g_wrap_used[i] = 1;
g_wrap_ctx[i].w = list; g_wrap_ctx[i].lv = is_listview ? 1 : 0; g_wrap_ctx[i].w = list; g_wrap_ctx[i].lv = is_listview ? 1 : 0;
s->ctx = &g_wrap_ctx[i]; s->ctx = &g_wrap_ctx[i];
s->wrap_slot = i;
return s; return s;
} }
} }
+224 -1
View File
@@ -7,7 +7,7 @@
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ * meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_meow_ui_scroll * 运行:./test_meow_ui_scroll
*/ */
@@ -169,15 +169,238 @@ static void test_stop(void)
meow_widget_free(lv); meow_widget_free(lv);
} }
/* ============ 池槽归还回归(M19)============
* meow_scroll_wrap 从 MEOW_SCROLL_POOL(=8) 个 wrap 上下文槽里占一个;旧实现 free 只归还
* 会话槽、不归还 wrap 槽 → 进程内最多成功 wrap 8 次,之后恒 NULL(图库每次 load_dir 重建,
* 进 8 个目录后列表丢惯性)。以下用例覆盖:长循环回收、free 后重建、交错不串槽、
* 池满仍返回 NULL、直接 create 不误清槽、free(NULL)/二次 free 安全。 */
#define WRAP_POOL 8 /* = meow_ui_scroll.c 的 MEOW_SCROLL_POOL(内部常量,测试按契约固定) */
/* 连续 64 次 wrap/free:每次都要拿到可用会话,且绑定当前列表 */
static void test_wrap_free_recycle(void)
{
meow_widget *lv = make_list();
int ok = 0, first_fail = -1;
for (int i = 0; i < 64; i++) {
meow_scroll *s = meow_scroll_wrap(lv, 0);
if (!s) { if (first_fail < 0) first_fail = i; continue; }
meow_list_set_scroll(lv, 0);
meow_scroll_track(s, 7, meow_ui_time_ms());
if (meow_scroll_offset(s) == 7) ok++;
else if (first_fail < 0) first_fail = i; /* 串槽:set/get 不指向本列表 */
meow_scroll_free(s);
}
CHECK(ok == 64, "64 wrap/free cycles all usable, ok=%d first_fail=%d", ok, first_fail);
meow_widget_free(lv);
}
/* wrap → free → 同一列表再 wrap:必须重新可用 */
static void test_wrap_after_free(void)
{
meow_widget *lv = make_list();
meow_scroll *s = meow_scroll_wrap(lv, 0);
CHECK(s != NULL, "wrap#1 same list");
meow_scroll_free(s);
s = meow_scroll_wrap(lv, 0);
CHECK(s != NULL, "wrap#2 same list after free");
if (s) {
meow_list_set_scroll(lv, 0);
meow_scroll_track(s, 12, meow_ui_time_ms());
CHECK(meow_scroll_offset(s) == 12, "re-wrapped scroll works got %d", meow_scroll_offset(s));
meow_scroll_free(s);
}
meow_widget_free(lv);
}
/* 8 个同时活跃 → 第 9 个仍返回 NULL(池满不越界);释放其一的槽立刻可再用 */
static void test_wrap_pool_full(void)
{
meow_widget *lv[WRAP_POOL];
meow_scroll *s[WRAP_POOL];
int ok = 0;
for (int i = 0; i < WRAP_POOL; i++) {
lv[i] = make_list();
s[i] = meow_scroll_wrap(lv[i], 0);
if (s[i]) ok++;
}
CHECK(ok == WRAP_POOL, "all %d wraps active, ok=%d", WRAP_POOL, ok);
meow_widget *extra_lv = make_list();
meow_scroll *extra = meow_scroll_wrap(extra_lv, 0);
CHECK(extra == NULL, "9th simultaneous wrap returns NULL (pool full), got %p", (void *)extra);
/* 释放中间一个 → 槽立即回收;活着的实例仍各自绑定自己的列表 */
meow_scroll_free(s[3]);
s[3] = NULL;
meow_scroll *again = meow_scroll_wrap(extra_lv, 0);
CHECK(again != NULL, "wrap succeeds right after freeing one slot");
if (again) {
meow_list_set_scroll(extra_lv, 0);
meow_scroll_track(again, 9, meow_ui_time_ms());
CHECK(meow_scroll_offset(again) == 9, "new wrap bound to its own list got %d",
meow_scroll_offset(again));
meow_list_set_scroll(lv[5], 33);
CHECK(meow_scroll_offset(s[5]) == 33, "other live wrap keeps its own list got %d",
meow_scroll_offset(s[5]));
meow_scroll_free(again);
}
for (int i = 0; i < WRAP_POOL; i++) {
if (s[i]) meow_scroll_free(s[i]);
meow_widget_free(lv[i]);
}
meow_widget_free(extra_lv);
}
/* 交错:两个 wrap 活跃 → free 其一 → 再 wrap,三者不串槽 */
static void test_wrap_interleaved(void)
{
meow_widget *a = make_list(), *b = make_list(), *c = make_list();
meow_scroll *sa = meow_scroll_wrap(a, 0);
meow_scroll *sb = meow_scroll_wrap(b, 0);
CHECK(sa != NULL && sb != NULL, "two wraps (interleaved) a=%p b=%p", (void *)sa, (void *)sb);
if (!sa || !sb) {
meow_scroll_free(sa); meow_scroll_free(sb);
meow_widget_free(a); meow_widget_free(b); meow_widget_free(c);
return;
}
meow_list_set_scroll(a, 11);
meow_list_set_scroll(b, 22);
CHECK(meow_scroll_offset(sa) == 11 && meow_scroll_offset(sb) == 22,
"distinct ctx a=%d b=%d", meow_scroll_offset(sa), meow_scroll_offset(sb));
meow_scroll_free(sa);
CHECK(meow_scroll_offset(sb) == 22, "remaining wrap intact after freeing first got %d",
meow_scroll_offset(sb));
meow_scroll *sc = meow_scroll_wrap(c, 0);
CHECK(sc != NULL, "third wrap after freeing first");
if (sc) {
meow_list_set_scroll(c, 0);
meow_scroll_track(sc, 5, meow_ui_time_ms());
CHECK(meow_scroll_offset(sc) == 5, "third wrap bound to its own list got %d",
meow_scroll_offset(sc));
CHECK(meow_scroll_offset(sb) == 22, "second wrap not clobbered by third got %d",
meow_scroll_offset(sb));
meow_scroll_free(sc);
}
meow_scroll_free(sb);
meow_widget_free(a); meow_widget_free(b); meow_widget_free(c);
}
/* ---- 直接 create(非 wrap)的回调:用于验证 free 不误清别人的 wrap 槽 ---- */
static int g_direct_off;
static void direct_set(void *c, int off) { (void)c; g_direct_off = off; }
static int direct_max(void *c) { (void)c; return 1000; }
static int direct_get(void *c) { (void)c; return g_direct_off; }
static void test_direct_create_free_keeps_wrap_slot(void)
{
meow_widget *lv = make_list();
meow_scroll *w = meow_scroll_wrap(lv, 0);
CHECK(w != NULL, "wrap before direct create");
meow_scroll *d = meow_scroll_create(direct_set, direct_max, direct_get, NULL, NULL);
CHECK(d != NULL, "direct create");
if (d) {
meow_scroll_track(d, 40, meow_ui_time_ms());
CHECK(g_direct_off == 40, "direct scroll tracks got %d", g_direct_off);
meow_scroll_free(d); /* 非 wrap 实例:不得归还/清除任何 wrap 槽 */
}
if (w) {
meow_list_set_scroll(lv, 0);
meow_scroll_track(w, 10, meow_ui_time_ms());
CHECK(meow_scroll_offset(w) == 10, "wrap intact after freeing direct instance got %d",
meow_scroll_offset(w));
meow_scroll_free(w);
}
meow_widget_free(lv);
}
/* free(NULL) 安全;同一实例二次 free 不崩、不清活着的 wrap 槽 */
static void test_wrap_free_null_and_double(void)
{
meow_scroll_free(NULL); /* 必须安全 */
meow_widget *a = make_list(), *b = make_list();
meow_scroll *sa = meow_scroll_wrap(a, 0);
meow_scroll *sb = meow_scroll_wrap(b, 0);
CHECK(sa != NULL && sb != NULL, "two wraps (double free) a=%p b=%p", (void *)sa, (void *)sb);
if (sb) meow_list_set_scroll(b, 44);
if (sa) meow_scroll_free(sa);
if (sa) meow_scroll_free(sa); /* 二次 free:重复清零,不得清掉 sb 的 ctx */
if (sb) {
CHECK(meow_scroll_offset(sb) == 44, "live wrap intact after double free got %d",
meow_scroll_offset(sb));
meow_scroll_free(sb);
}
meow_scroll *sc = meow_scroll_wrap(a, 0);
CHECK(sc != NULL, "wrap still available after free(NULL)/double free");
if (sc) meow_scroll_free(sc);
meow_widget_free(a); meow_widget_free(b);
}
/* 虚拟化列表行源(仅需 count 有效,不绘制) */
static int lv_src2(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= COUNT) return 0;
memset(out, 0, sizeof *out);
out->title = "row";
return 1;
}
/* 槽复用不继承上一个 wrap 的 kind:list → listview → list 交替必须各自生效
* (free 时清 lv 标记,wrap 时按参数重设;否则复用槽上的旧 kind 会让 set/get 打空) */
static void test_wrap_slot_kind_reset(void)
{
meow_widget *plain = make_list();
meow_widget *vl = meow_listview_create(0, 0, 480, LIST_H, COUNT, lv_src2, NULL,
MEOW_LIST_SEL_SINGLE);
meow_listview_set_item_h(vl, ITEM_H);
meow_scroll *s = meow_scroll_wrap(plain, 0);
CHECK(s != NULL, "wrap plain list");
if (s) meow_scroll_free(s);
s = meow_scroll_wrap(vl, 1);
CHECK(s != NULL, "wrap listview on recycled slot");
if (s) {
meow_listview_set_scroll(vl, 0);
meow_scroll_track(s, 20, meow_ui_time_ms());
CHECK(meow_scroll_offset(s) == 20, "listview wrapped scroll works got %d",
meow_scroll_offset(s));
meow_scroll_free(s);
}
s = meow_scroll_wrap(plain, 0);
CHECK(s != NULL, "wrap plain list again on recycled slot");
if (s) {
meow_list_set_scroll(plain, 0);
meow_scroll_track(s, 15, meow_ui_time_ms());
CHECK(meow_scroll_offset(s) == 15, "plain list after listview slot reuse got %d",
meow_scroll_offset(s));
meow_scroll_free(s);
}
meow_widget_free(vl);
meow_widget_free(plain);
}
int main(void) int main(void)
{ {
meow_ui_opts o = { 480, 720 }; meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
/* 池槽回归先跑:新建进程里第一个动 pool 的用例,便于对照「修复前 8/64、修复后 64/64」 */
test_wrap_free_recycle();
test_track_follow(); test_track_follow();
test_fling_no_boundary(); test_fling_no_boundary();
test_fling_boundary_bounce(); test_fling_boundary_bounce();
test_snap(); test_snap();
test_stop(); test_stop();
test_wrap_after_free();
test_wrap_pool_full();
test_wrap_interleaved();
test_direct_create_free_keeps_wrap_slot();
test_wrap_slot_kind_reset();
test_wrap_free_null_and_double();
meow_ui_shutdown(); meow_ui_shutdown();
printf("scroll: %d pass / %d fail\n", g_pass, g_fail); printf("scroll: %d pass / %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0; return g_fail ? 1 : 0;