proposal.md
3.68 KB
提案:历史记录列表增量渲染 + 视口懒加载(路线 A)
状态:第一阶段已完成(增量 prepend),第二阶段(Model/Delegate 重构)下周启动。
Why
2026-04-24 当天应用在 Mac 上闪退 14 次。根因定位:
-
app(7).log末尾戛然而止,无 Traceback -
crash_log(3).txt的 faulthandler 也没捕到任何信号栈 - 两处都没栈 = 只能是 SIGKILL(faulthandler 无法拦截 SIGKILL,Unix 铁律)→ 99% 是 macOS jetsam 内存压力强杀
image_generator.py 历史记录渲染路径有三个相互放大的问题:
-
每次生成完图片都
refresh_history()全量重建(:2859、:2884、:4452、:4507)。360 条 × 120×120 QPixmap + QIcon + QListWidgetItem,6 小时内累计创建 ~9 万个 Qt 对象,C++ 层回收滞后 + 内存碎片化。 -
load_history_index()每次调用都做 O(N) 路径修正,360 条 × 2 图 = 720 次os.stat在 UI 主线程上。崩溃前 11 秒内被 UI 事件重复触发了 7 次。 -
QListWidget架构决定"全部 widget 常驻",用户只看得到视口内 10–15 行,但 Qt 必须为所有 360 条持有 QPixmap。历史记录越多,风险越高。
代码注释已自承风险:# macOS 上触发 SIGKILL(image_generator.py:3071)。
What Changes
阶段 1:增量 prepend(已完成,2026-04-24)
历史记录是 append-only 数据(生成后 prompt/图片/参数不可编辑),生成完成后只需追加单条到列表首位,无需全量重建。
- 新增
HistoryManager.load_history_item_fast(timestamp)— 只读{timestamp}/metadata.json+ 扫目录下reference_*.png,不触碰index.json、不扫其他记录、不做路径修正 - 抽出
_build_history_list_item(item)— 单条 widget 构建逻辑,供全量和增量共用 - 新增
prepend_history_item(timestamp)— 增量插入到列表首位,异常时自动回退refresh_history()全量 - 四处生成完成回调从
refresh_history()切换为prepend_history_item(timestamp)
阶段 2:Model/Delegate 视口懒加载(下周启动)
阶段 1 消除了"每次生成的 O(N) 重建",但"首次加载/手动刷新"仍是一次性画 360 个 widget。真正的根治是换架构:
-
QListWidget→QListView + HistoryListModel(QAbstractListModel) + HistoryItemDelegate(QStyledItemDelegate) - Model 只存
list[str]的 timestamps(内存几乎为零) - Delegate 在
paint()时按需读 metadata + 缩略图,滚出视口自动不再绘制 - 效果等同于无限懒加载,用户零感知,不引入"翻页按钮"类的 UI 变化
额外优化:
-
QPixmapCache(LRU,默认 ~50 MB)缓存最近加载的缩略图 - 后台线程(
QThreadPool + QRunnable)异步加载缩略图,主线程占位 placeholder,缩略图回来再触发重绘 -
delete/clear改为 model 级别增量(removeRow/reset),不再触发"全量 refresh"
Impact
- Affected specs: history-list-rendering(新,阶段 2 时建立)
-
Affected code:
-
image_generator.py: HistoryManager / ImageGeneratorWindow 历史 tab 相关方法 - 阶段 2 会新增
HistoryListModel/HistoryItemDelegate两个类
-
-
User-visible:
- 阶段 1:无感知(仅性能/稳定性提升)
- 阶段 2:历史 tab 的选中样式、间距、hover 可能有 1–2 px 的视觉差异(
QListView默认样式 vsQListWidget),需要 QSS 调齐
-
Risk:
- 阶段 2 触及历史 tab 核心交互路径(单击/双击/右键菜单/详情面板联动),需要完整回归
- delegate 的尺寸计算(
sizeHint)必须和现有 icon size + text 行高严格一致,否则滚动位置/选中高亮会错位