feat: M6.43 — reader分段流式读取(Phase1,只txt) + 面板前台管理/返回回桌面按钮

- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮

0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
This commit is contained in:
AskaEth committed 2026-11-04 19:47:10 +08:00
1 parent a64ae06eb6
commit f40abbd494
17 files changed
+1069 -70

No files matched your search

+189 -14
View File
@@ -8,6 +8,7 @@
#include "book.h"
#include "rdsrc.h"
#include "rdtxt.h"
#include "seg.h"
#include "rdlayout.h"
#include "rdimg.h"
#include "../engine/rdft.h"
@@ -44,6 +45,12 @@ struct rd_book {
uint32_t *seg_cps; size_t seg_cps_n, seg_cps_cap;
rd_line *seg_lines; rd_page *seg_pages; int seg_nlines, seg_npages;
int seg_cache_idx;
/* R3.2 分段流式(TXT):段目录 + 全局段数 + 已建段数 + 段/页表容量(增量补齐用) */
char seg_dir[400];
int seg_n; /* 段文件总数(manifest);fallback = nsegs(全建) */
int seg_built; /* 已布局段数;fallback = nsegs */
int segs_cap, pages_cap; /* segs[]/pages[] 当前容量(只在 seg 路径增量增长) */
};
/* ---- 页索引持久化(MeowData;见 rd_book_set_index_dir) ----
@@ -60,6 +67,15 @@ void rd_book_set_index_dir(const char *dir)
else g_index_dir[0] = 0;
}
/* R3.2:分段流式缓存根(MeowData/os.meow.reader)。存到 g_seg_base 并同步 rdseg。 */
static char g_seg_base[400];
void rd_book_set_seg_base(const char *base)
{
if (base && base[0]) { strncpy(g_seg_base, base, sizeof g_seg_base - 1); g_seg_base[sizeof g_seg_base - 1] = 0; }
else g_seg_base[0] = 0;
rdseg_set_base(g_seg_base);
}
#define IDX_MAGIC0 'R'
#define IDX_MAGIC1 'I'
#define IDX_MAGIC2 'D'
@@ -284,15 +300,117 @@ static int build_index(rd_book *b)
return 0;
}
/* ---- R3.2 分段流式(TXT):把一段 UTF-8 布局并追加到 flat 页表 ---- */
static int append_seg_pages(rd_book *b, int seg_idx, const uint8_t *utf8, size_t ulen)
{
uint32_t *cps = NULL; size_t n = 0;
if (rd_utf8_to_cp(utf8, ulen, &cps, &n) != RD_ENC_OK) return -5;
rd_line *lines = (rd_line *)malloc((n ? n : 1) * sizeof(rd_line));
rd_page *pages = (rd_page *)malloc((n ? n : 1) * sizeof(rd_page));
if (!lines || !pages) { free(lines); free(pages); free(cps); return -6; }
rd_layout_in in;
rd_layout_out out;
in.chars = cps; in.n = (int32_t)n; in.fs = b->fs; in.line_scale = b->ls;
in.page_w = b->content_w; in.page_h = b->content_h; in.indent = b->fs;
in.meas = book_meas; in.mctx = b;
out.lines = lines; out.pages = pages; out.max_lines = (int)(n ? n : 1); out.max_pages = (int)(n ? n : 1);
int lrc = rd_layout(&in, &out);
free(cps);
if (lrc != RD_LAYOUT_OK) { free(lines); free(pages); return -4; }
b->segs[seg_idx].page_start = b->npages;
b->segs[seg_idx].page_count = out.npages;
for (int p = 0; p < out.npages; p++) {
if (b->npages >= b->pages_cap) {
int nc = b->pages_cap ? b->pages_cap * 2 : 1024;
rd_book_page *np = (rd_book_page *)realloc(b->pages, (size_t)nc * sizeof(rd_book_page));
if (!np) { free(lines); free(pages); return -6; }
b->pages = np; b->pages_cap = nc;
}
b->pages[b->npages].seg = seg_idx;
b->pages[b->npages].seg_char = out.pages[p].first_char;
b->npages++;
}
free(lines); free(pages);
return 0;
}
/* ---- R3.2 段路径:把 [seg_built, upto_seg] 逐段(512KB 段文件 → utf8 → 布局)补齐进 flat 页表。
* 幂等:已建段跳过。开书只建到目标(页 0/保存进度),翻页/跳转再按需补齐 —— 免整书一次性布局。 */
static int seg_ensure_built(rd_book *b, int upto_seg)
{
if (!b->seg_dir[0] || b->fmt != RD_FMT_TXT) return 0;
int total = b->seg_n > 0 ? b->seg_n : 1;
if (upto_seg >= total) upto_seg = total - 1;
if (upto_seg < 0) upto_seg = 0;
int built_any = 0;
while (b->seg_built <= upto_seg) {
int idx = b->seg_built;
size_t rawlen = 0;
if (rdseg_rawlen(b->seg_dir, idx, &rawlen) != 0) break;
if (b->segs_cap <= idx) {
int nc = b->segs_cap ? b->segs_cap * 2 : 64;
if (nc <= idx) nc = idx + 1;
seg_t *ns = (seg_t *)realloc(b->segs, (size_t)nc * sizeof(seg_t));
if (!ns) return -6;
b->segs = ns; b->segs_cap = nc;
}
size_t cap = rawlen * 3 / 2 + 16;
if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8 || b->seg_utf8_cap < cap) {
uint8_t *t = (uint8_t *)malloc(cap);
if (!t) return -6;
free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap;
}
uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1);
if (!raw) return -6;
size_t rn = 0;
if (rdseg_load(b->seg_dir, idx, raw, rawlen, &rn) != 0) { free(raw); return -4; }
size_t ulen = 0;
int rc = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen);
free(raw);
if (rc == RD_ENC_ERR_BUFSZ) return -2;
if (rc != RD_ENC_OK) return -3;
int prc = append_seg_pages(b, idx, b->seg_utf8, ulen);
if (prc != 0) return prc;
b->seg_built++;
built_any = 1;
}
b->nsegs = b->seg_built;
if (built_any) index_cache_save(b);
return 0;
}
/* ---- 正文重显 + 布局到复用缓冲 ---- */
static int load_seg(rd_book *b, int seg_idx)
{
if (seg_idx == b->seg_cache_idx) return 0;
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
size_t ulen = 0;
if (rd_src_seg(b->src, seg_idx, b->seg_utf8, cap, &ulen) != 0) return -4;
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
/* R3.2 段路径:只读当前页所在 512KB 段文件(有界,不全量读整书) */
size_t rawlen = 0;
if (rdseg_rawlen(b->seg_dir, seg_idx, &rawlen) != 0) return -4;
size_t cap = rawlen * 3 / 2 + 16;
if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8 || b->seg_utf8_cap < cap) {
uint8_t *t = (uint8_t *)malloc(cap);
if (!t) return -6;
free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap;
}
uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1);
if (!raw) return -6;
size_t rn = 0;
int rc = rdseg_load(b->seg_dir, seg_idx, raw, rawlen, &rn);
if (rc != 0) { free(raw); return -4; }
int r2 = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen);
free(raw);
if (r2 == RD_ENC_ERR_BUFSZ) return -2;
if (r2 != RD_ENC_OK) return -3;
} else {
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
if (rd_src_seg(b->src, seg_idx, b->seg_utf8, cap, &ulen) != 0) return -4;
}
size_t n = 0;
free(b->seg_cps); b->seg_cps = NULL;
if (rd_utf8_to_cp(b->seg_utf8, ulen, &b->seg_cps, &n) != RD_ENC_OK) return -5;
@@ -348,10 +466,35 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
if (font_path && rdft_load_font(x->ft, x->font) != 0) { rdft_free(x->ft); x->ft = NULL; goto close; }
if (x->ft) rdft_set_px(x->ft, fs);
x->seg_cache_idx = -1;
/* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */
if (index_cache_load(x) != 1) {
if (build_index(x) != 0) goto close;
index_cache_save(x);
int seg_mode = 0;
/* R3.2 分段流式(TXT 且段 base 已配置):开书切 512KB 段文件,逐段按需布局(不全量)。 */
if (g_seg_base[0] && x->fmt == RD_FMT_TXT) {
char sdir[600];
if (rdseg_dir_for(x->path, sdir, sizeof sdir) == 0) {
strncpy(x->seg_dir, sdir, sizeof x->seg_dir - 1); x->seg_dir[sizeof x->seg_dir - 1] = 0;
int ns = rdseg_build(x->path, x->enc, x->seg_dir);
if (ns > 0) { x->seg_n = ns; seg_mode = 1; }
}
}
if (seg_mode) {
/* 命中缓存(书+版面+编码+字体未变)→ 载入已建 (segs,pages);否则仅布局段 0 起步,
* 翻页/跳转再 seg_ensure_built 补齐(治大书开书整表布局卡 UI 线程)。 */
x->segs = NULL; x->pages = NULL; x->nsegs = x->npages = 0;
x->seg_built = 0; x->segs_cap = x->pages_cap = 0;
if (index_cache_load(x) != 1) {
if (seg_ensure_built(x, 0) != 0) goto close;
}
if (x->nsegs < 1) { /* 段 0 空(理论)→ 兜底再建 */
if (seg_ensure_built(x, 0) != 0) goto close;
}
x->segs_cap = x->nsegs; x->pages_cap = x->npages;
x->seg_built = x->nsegs; /* 缓存命中时恢复到已建段数 */
} else {
/* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */
if (index_cache_load(x) != 1) {
if (build_index(x) != 0) goto close;
index_cache_save(x);
}
}
} else {
int pages = rd_src_pages(x->src);
@@ -410,6 +553,7 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc)
int seg; int32_t ch;
rd_book_anchor(b, &seg, &ch);
rd_enc_t oldenc = b->enc;
rd_src *ns = NULL;
if (rd_src_open_with_enc(&ns, b->path, &enc) != 0) return -2; /* 旧 src 未动 */
rd_src_free(b->src); b->src = ns; /* 换新 */
@@ -418,9 +562,17 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc)
/* 重建索引(复用现值 fs/ls/content_*),再回锚 */
free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL;
b->nsegs = b->npages = 0;
if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */
index_cache_save(b); /* 编码切换后重新落盘(键含 enc,与旧缓存并存不冲突) */
b->nsegs = b->npages = 0; b->seg_built = 0; b->segs_cap = b->pages_cap = 0;
if (b->seg_dir[0]) {
/* R3.2 段路径:编码变了 → 段文件边界/解码不同 → 重建段文件 + 按需布局(段 0 起步) */
if (oldenc != b->enc) rdseg_clear(b->seg_dir);
b->seg_n = rdseg_build(b->path, b->enc, b->seg_dir);
if (seg_ensure_built(b, 0) != 0) return -3;
index_cache_save(b);
} else {
if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */
index_cache_save(b); /* 编码切换后重新落盘(键含 enc,与旧缓存并存不冲突) */
}
return rd_book_jump_to_anchor(b, seg, ch, b->fs, b->ls);
}
@@ -437,6 +589,12 @@ int rd_book_turn(rd_book *b, int delta)
if (!b || b->npages <= 0) return 0;
int np = b->cur + delta;
if (np < 0) np = 0;
/* R3.2 段路径:翻到已建表尾之外 → 按需补下一段(一次翻页只补到够用,不全量) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && np >= b->npages && b->seg_built < b->seg_n) {
while (np >= b->npages && b->seg_built < b->seg_n) {
if (seg_ensure_built(b, b->seg_built) != 0) break;
}
}
if (np >= b->npages) np = b->npages - 1;
b->cur = np;
return np;
@@ -445,6 +603,12 @@ int rd_book_turn(rd_book *b, int delta)
int rd_book_set_page(rd_book *b, int page)
{
if (!b || b->npages <= 0) return 0;
/* R3.2 段路径:跳到已建表外 → 按需补段到目标页(跳到哪建到哪) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && page >= b->npages && b->seg_built < b->seg_n) {
while (page >= b->npages && b->seg_built < b->seg_n) {
if (seg_ensure_built(b, b->seg_built) != 0) break;
}
}
if (page < 0) page = 0;
if (page >= b->npages) page = b->npages - 1;
b->cur = page;
@@ -543,9 +707,20 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
seg_t *nos = b->segs; int n_oldsegs = b->nsegs;
rd_book_page *nop = b->pages; int n_oldpages = b->npages;
b->segs = NULL; b->pages = NULL; b->nsegs = b->npages = 0;
if (build_index(b) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
/* R3.2 段路径:只重布局到含锚的段(不全量;段文件不变,仅页表变) */
b->seg_built = 0; b->segs_cap = b->pages_cap = 0;
if (seg_ensure_built(b, seg) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b);
} else {
if (build_index(b) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */
}
} else if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && seg >= b->seg_built) {
/* 锚在未建段 → 按需补齐(重开续读/目录跳转到后部) */
if (seg_ensure_built(b, seg) != 0) return -5;
}
int target = 0;
for (int p = 0; p < b->npages; p++) {