perf(reader): M31 字体路径记忆化(开书字体查找 35 目录 walk → 1 次 stat)+ 「· 读」徽标即时刷新
字体:find_font_rec/resolve_font 从 reader.c 抽到 app/font_resolve.c 并加进程内记忆化。 只缓存成功路径;命中后仍做一次 access() 校验(字库被删即重扫);没找到不缓存, 用户之后拷入的字库下次开书即可找到。顶栏「刷新」额外调 font_cache_invalidate(), 给「新拷入字库、旧字库仍在」一条显式失效路径。宿主实测:35 目录树 walk 0.026~0.54ms (=300+ 次系统调用)vs 命中 0.001ms(1 次 access);开书路径每次省一次完整 walk。 徽标:副题格式化抽成 shelf_fmt_sub()(扫描与「刚读完置顶」共用一份实现), promote_just_read() 改 ts 后立即重算这本的 sub/sub_folder,「· 读」不再等下次全量刷新。 回归:test_reader_core 6846、shelf_group_smoke 63(+8)、font_resolve_smoke 14 全绿; build_reader.sh 0 warning;扫描缓存/进度语义未改。app.json 0.1.3/code4 → 0.1.4/code5。
This commit is contained in:
1 parent
0b30cdbafb
commit
dc9bfdbdaf
12 files changed
+460
-53
No files matched your search
@@ -0,0 +1,76 @@
|
||||
/* app/font_resolve.c — 递归(深度 ≤ 3)扫 MeowOS/fonts 找首个 .otf/.ttf,进程内记忆化。
|
||||
*
|
||||
* 为什么缓存:fonts 树在 SD 卡(FUSE)上,每次开书都要调用;一次完整 walk 是
|
||||
* 「35+ 个目录的 opendir/readdir + 每个条目的 stat」,真机实测单次 10~250ms 抖动。
|
||||
*
|
||||
* 失效策略(红线:不许出现「明明有字库却说没有」):
|
||||
* 1) 只缓存成功路径。没找到 → 返回 NULL 且不写缓存,用户之后拷入字库,下次开书即可找到。
|
||||
* 2) 命中后仍做一次 access(路径, R_OK) 校验(1 次 stat):
|
||||
* 失败(被删/改名/不可读)→ 丢缓存重新 walk,立刻回到真实答案;
|
||||
* 成功 → 直接用。
|
||||
* 1 次 stat 比 35+ 个目录的 walk 便宜一到两个数量级(见 test/font_resolve_bench.c),
|
||||
* 所以正确性和省时两头都要。
|
||||
* 3) 命中与未命中的结果集与加缓存前一致:缓存前每次 walk 取的也是「readdir 顺序第一个」
|
||||
* 字库(同目录内容下顺序稳定),新增字库不会改变既有命中。
|
||||
* 4) 显式失效:font_cache_invalidate()(书架顶栏「刷新」调用)——用户新拷入字库且旧字库仍在时,
|
||||
* 缓存命中不会自动改选,刷新给一条确定的失效路径。
|
||||
*/
|
||||
#include "font_resolve.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define FONT_PATH_MAX 400
|
||||
#define FONT_SCAN_DEPTH 3
|
||||
|
||||
static char g_cached[FONT_PATH_MAX]; /* "" = 无有效缓存(从未找到,或已失效) */
|
||||
|
||||
/* 命中即写 out;0=没找到。 */
|
||||
static int find_font_rec(const char *dir, int depth, char *out, int outsz)
|
||||
{
|
||||
if (depth > FONT_SCAN_DEPTH) return 0;
|
||||
DIR *dp = opendir(dir);
|
||||
if (!dp) return 0;
|
||||
struct dirent *de;
|
||||
int found = 0;
|
||||
while ((de = readdir(dp)) && !found) {
|
||||
if (de->d_name[0] == '.') continue;
|
||||
char full[FONT_PATH_MAX + 80];
|
||||
if (snprintf(full, sizeof full, "%s/%s", dir, de->d_name) >= (int)sizeof full) continue;
|
||||
struct stat st;
|
||||
if (stat(full, &st) != 0) continue;
|
||||
if (S_ISDIR(st.st_mode)) { found = find_font_rec(full, depth + 1, out, outsz); }
|
||||
else {
|
||||
const char *d = strrchr(de->d_name, '.');
|
||||
if (d && (strcasecmp(d, ".otf") == 0 || strcasecmp(d, ".ttf") == 0)) {
|
||||
if (strlen(full) < (size_t)outsz) {
|
||||
strcpy(out, full);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dp);
|
||||
return found;
|
||||
}
|
||||
|
||||
const char *font_resolve(const char *root)
|
||||
{
|
||||
if (g_cached[0]) {
|
||||
if (access(g_cached, R_OK) == 0) return g_cached; /* 命中:1 次 stat 校验 */
|
||||
g_cached[0] = 0; /* 字库没了:缓存失效,重扫 */
|
||||
}
|
||||
if (!root || !root[0]) return NULL;
|
||||
if (find_font_rec(root, 0, g_cached, (int)sizeof g_cached)) return g_cached;
|
||||
g_cached[0] = 0; /* 没找到:不缓存(之后拷入的字库下次仍能找到) */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void font_cache_invalidate(void)
|
||||
{
|
||||
g_cached[0] = 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* app/font_resolve.h — 在 MeowOS/fonts 里找字体文件(进程内记忆化)。 */
|
||||
#ifndef FONT_RESOLVE_H
|
||||
#define FONT_RESOLVE_H
|
||||
|
||||
/* 递归(深度 ≤ 3)扫 root,返回第一个 .otf/.ttf 的完整路径;没找到返回 NULL。
|
||||
* 返回值指向模块内部静态缓冲,调用方不得 free,下次调用前一直有效。
|
||||
* 语义:只缓存成功路径;命中时做一次 access() 校验,字库被删即重新扫描;
|
||||
* 失败不缓存,用户之后拷入字库仍能立刻找到。 */
|
||||
const char *font_resolve(const char *root);
|
||||
|
||||
/* 丢弃缓存(下次 font_resolve 强制重扫)。书架顶栏「刷新」时调用:
|
||||
* 命中路径仍在时不会自动发现「用户新拷入、路序更靠前」的字库,手动刷新给一条显式失效路径。 */
|
||||
void font_cache_invalidate(void);
|
||||
|
||||
#endif /* FONT_RESOLVE_H */
|
||||
@@ -8,10 +8,10 @@
|
||||
#include "../reader_internal.h"
|
||||
#include "meow_ui_internal.h" /* back_buf/pitch/bpp 访问器(决策见报告) */
|
||||
#include "book.h"
|
||||
#include "font_resolve.h"
|
||||
#include "progress.h"
|
||||
#include "reader_conf.h"
|
||||
#include "rdchapter.h" /* RDCHAP_TITLE_MAX(章节标题) */
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -45,7 +45,6 @@ static int g_jump_scroll; /* 列表滚动(首行项下标) */
|
||||
static char g_jump_buf[8]; /* 章内/段内页码输入缓冲(ASCII 数字) */
|
||||
static int g_chap_cache_idx = -1; /* 章节范围缓存(章起/止页;随字号/行距重排失效) */
|
||||
static int g_chap_cache_start, g_chap_cache_end;
|
||||
static char g_font_path[400];
|
||||
static int g_notice_ms;
|
||||
|
||||
/* ---- R3 翻页动画(轻量水平滑入) ---- */
|
||||
@@ -141,39 +140,10 @@ static void reader_set_bg(const char *bg)
|
||||
reader_redraw();
|
||||
}
|
||||
|
||||
/* 递归(深度≤3)扫 MeowOS/fonts/ 找第一个 .otf/.ttf。0=找到。 */
|
||||
static int find_font_rec(const char *dir, int depth)
|
||||
{
|
||||
if (depth > 3) return 0;
|
||||
DIR *dp = opendir(dir);
|
||||
if (!dp) return 0;
|
||||
struct dirent *de;
|
||||
int found = 0;
|
||||
while ((de = readdir(dp)) && !found) {
|
||||
if (de->d_name[0] == '.') continue;
|
||||
char full[480];
|
||||
if (snprintf(full, sizeof full, "%s/%s", dir, de->d_name) >= (int)sizeof full) continue;
|
||||
struct stat st;
|
||||
if (stat(full, &st) != 0) continue;
|
||||
if (S_ISDIR(st.st_mode)) { found = find_font_rec(full, depth + 1); }
|
||||
else {
|
||||
const char *d = strrchr(de->d_name, '.');
|
||||
if (d && (strcasecmp(d, ".otf") == 0 || strcasecmp(d, ".ttf") == 0)) {
|
||||
if (strlen(full) < sizeof g_font_path) {
|
||||
strcpy(g_font_path, full);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dp);
|
||||
return found;
|
||||
}
|
||||
/* MeowOS/fonts/ 找第一个 .otf/.ttf(M31:路径进程内记忆化,见 font_resolve.c)。 */
|
||||
static const char *resolve_font(void)
|
||||
{
|
||||
g_font_path[0] = 0;
|
||||
if (find_font_rec(READER_SD_ROOT "/MeowOS/fonts", 0)) return g_font_path;
|
||||
return NULL;
|
||||
return font_resolve(READER_SD_ROOT "/MeowOS/fonts");
|
||||
}
|
||||
|
||||
static void chapter_name(void)
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
* 行为变化:新放进 Books/ 的书需点一次「刷新」才出现。
|
||||
* - **行数多时关圆角容器**(见 SHELF_ROUND_MAX_ROWS):圆角卡逐像素填充,595 本时滑一步 46ms。
|
||||
* M30:M29 的「刚读完不置顶」缺口补上(只刷一本书的 ts + 内存重排,不重扫全盘)。
|
||||
* M31:置顶时一并重算这本的副题(shelf_fmt_sub),「· 读」立即出现,不等下一次全量刷新;
|
||||
* 顶栏「刷新」同时丢弃字体路径缓存(新拷入的字库此时生效)。
|
||||
*/
|
||||
#include "../reader_internal.h"
|
||||
#include "font_resolve.h"
|
||||
#include "progress.h"
|
||||
#include "reader_conf.h"
|
||||
#include "shelf_model.h"
|
||||
@@ -115,7 +118,9 @@ static void build_rows(void)
|
||||
}
|
||||
|
||||
/* M30:从阅读页返回时把「刚读完」置顶。只读这一本的进度文件 + 重排内存里的 g_books,
|
||||
* 不 opendir/不遍历目录(对比全量重扫真机 3s)。重排后 on_enter 照常 build_rows + 重建控件。 */
|
||||
* 不 opendir/不遍历目录(对比全量重扫真机 3s)。重排后 on_enter 照常 build_rows + 重建控件。
|
||||
* M31:置顶的同时重建这本的副题——只改 ts 不重建的话,副题的「· 读」要等下次全量刷新
|
||||
* (scan_books 重新格式化)才出现,表现就是「刚读完却没显示已读」。 */
|
||||
static void promote_just_read(void)
|
||||
{
|
||||
if (!g_just_read[0]) return;
|
||||
@@ -124,6 +129,7 @@ static void promote_just_read(void)
|
||||
uint64_t ts = shelf_ts_of(g_books[i].path);
|
||||
if (ts > g_books[i].ts) {
|
||||
g_books[i].ts = ts;
|
||||
shelf_fmt_sub(&g_books[i]); /* 副题随 ts 重算,「· 读」立即出现 */
|
||||
shelf_sort_recent(g_books, g_nbooks);
|
||||
}
|
||||
break;
|
||||
@@ -433,6 +439,7 @@ int shelf_on_tap(int x, int y)
|
||||
return 1;
|
||||
} else if (bi == 2) { /* 刷新:强制重扫(缓存失效 + 重扫;新书/刚读完的书在此出现) */
|
||||
g_scanned = 0;
|
||||
font_cache_invalidate(); /* 字体路径记忆化也一并失效(新拷入的字库在此生效) */
|
||||
scan_books(); build_rows();
|
||||
shelf_build_content();
|
||||
shelf_redraw();
|
||||
|
||||
@@ -69,6 +69,30 @@ void shelf_sort_recent(shelf_book *books, int nbooks)
|
||||
qsort(books, (size_t)nbooks, sizeof(shelf_book), cmp_recent_then_name);
|
||||
}
|
||||
|
||||
/* 重建一本书的两种副题(扫描后与「刚读完置顶」共用同一份格式化逻辑)。
|
||||
* sub = 格式 · 大小 [· 读](format 模式/宫格)
|
||||
* sub_folder = 相对 book.group 第一级之后的子路径 · sub(folder 模式)
|
||||
*/
|
||||
void shelf_fmt_sub(shelf_book *b)
|
||||
{
|
||||
if (!b) return;
|
||||
char sz[24];
|
||||
shelf_fmt_size(sz, sizeof sz, b->size);
|
||||
/* 默认副题:与 M27 前逐字一致(format 模式/宫格都用它)。 */
|
||||
snprintf(b->sub, sizeof b->sub, "%s · %s%s", shelf_fmt_name(b->fmt), sz,
|
||||
b->ts ? " · 读" : "");
|
||||
/* folder 副题:第一级之后的子路径(分类/作品)前置,其余同默认副题。 */
|
||||
const char *rest = strchr(b->group, '/');
|
||||
if (rest) rest++;
|
||||
if (rest && rest[0]) {
|
||||
char pre[100];
|
||||
shelf_copy_utf8(pre, (int)sizeof pre, rest);
|
||||
snprintf(b->sub_folder, sizeof b->sub_folder, "%s · %s", pre, b->sub);
|
||||
} else {
|
||||
snprintf(b->sub_folder, sizeof b->sub_folder, "%s", b->sub);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct scan_ctx {
|
||||
shelf_book *books;
|
||||
int cap;
|
||||
@@ -146,21 +170,7 @@ static void scan_dir(scan_ctx *c, const char *dir, int depth, const char *rel, c
|
||||
b->fmt = f;
|
||||
b->size = (long)st.st_size;
|
||||
b->ts = c->ts_fn ? c->ts_fn(b->path) : 0;
|
||||
char sz[24];
|
||||
shelf_fmt_size(sz, sizeof sz, b->size);
|
||||
/* 默认副题:与 M27 前逐字一致(format 模式/宫格都用它)。 */
|
||||
snprintf(b->sub, sizeof b->sub, "%s · %s%s", shelf_fmt_name(f), sz,
|
||||
b->ts ? " · 读" : "");
|
||||
/* folder 副题:第一级之后的子路径(分类/作品)前置,其余同默认副题。 */
|
||||
const char *rest = strchr(rel, '/');
|
||||
if (rest) rest++;
|
||||
if (rest && rest[0]) {
|
||||
char pre[100];
|
||||
shelf_copy_utf8(pre, (int)sizeof pre, rest);
|
||||
snprintf(b->sub_folder, sizeof b->sub_folder, "%s · %s", pre, b->sub);
|
||||
} else {
|
||||
snprintf(b->sub_folder, sizeof b->sub_folder, "%s", b->sub);
|
||||
}
|
||||
shelf_fmt_sub(b); /* 副题:格式 · 大小 [· 读](+ folder 子路径) */
|
||||
c->n++;
|
||||
}
|
||||
closedir(dp);
|
||||
|
||||
@@ -79,6 +79,8 @@ int shelf_build_rows(const shelf_book *books, int nbooks, int folder_mode,
|
||||
int shelf_fmt_from_ext(const char *name);
|
||||
const char *shelf_fmt_name(int fmt);
|
||||
void shelf_fmt_size(char *out, int outsz, long size);
|
||||
/* 按 fmt/size/ts/group 重建 b->sub 与 b->sub_folder(扫描后与「刚读完置顶」共用)。 */
|
||||
void shelf_fmt_sub(shelf_book *b);
|
||||
/* UTF-8 安全截断:最多写 cap-1 字节;若截断点落在多字节序列中间,整段丢弃该残缺字符。
|
||||
* 返回写入字节数(不含 NUL)。 */
|
||||
int shelf_copy_utf8(char *dst, int cap, const char *src);
|
||||
|
||||
Reference in new issue
Block a user