## 概述
对 `0.2.0` 做了一轮静态走查,发现 5 处值得修复的缺陷,按影响排序。全部可在本地复现或由代码逻辑直接推出。
---
### 1. PLY 头解析脆弱,可能在训练完成后误判产物无效(最高优先级)
`src-tauri/src/reconstruction/ply.rs:18-21`
```rust
let mut bytes = vec![0_u8; (size.min(256 * 1024)) as usize];
let read = file.read(&mut bytes)?;
Read::read 允许短读,返回少于 256KB 也合法。此时 end_header 可能落在未读区域,报错「PLY 缺少 end_header」。失败点在 Brush 训练完成之后,用户损失最大。
ply.rs:37-49 属性校验用 line.ends_with(" x"),但 header.lines() 会保留 CRLF 的 \r,CRLF 头必然匹配失败,报「PLY 缺少 Gaussian 属性」。而同函数 :26 又显式接受 ply\r\n,逻辑自相矛盾。
建议:改用 file.take(n).read_to_end(&mut bytes);所有头行统一 trim_end() 后再比较。
2. 失败/取消时进度条跳到 100%,时间线全部标绿
src-tauri/src/pipeline/progress.rs:16:Failed | Cancelled => (0.0, 100.0)
src-tauri/src/commands/mod.rs:115:PipelineEvent::mapped(stage, 1.0, error.to_string()) → progress = 100
src/stores/appStore.ts:65:Math.max(...) 把进度锁死在 100%
src/app/App.tsx:47:failed/cancelled 映射到 stage index 6,:295 据此把前 6 个阶段渲染为 done
结果:任务在「顺序匹配」失败,UI 却显示 100.0% 且所有阶段已完成。
建议:终止事件不参与进度更新(前端过滤,或后端携带终止前的真实进度);时间线的 done 判定改用终止前记录的阶段索引。
3. 日志超过 500 条后停止自动滚动
src/app/App.tsx:141
useEffect(() => { logEnd.current?.scrollIntoView({ block: "nearest" }); }, [store.events.length]);
appStore.ts:61 对日志做了 slice(-500) 封顶,长度恒为 500 后依赖值不再变化,effect 不再触发。长任务(精细档位 Brush 训练)后期日志不跟随底部。
建议:依赖改为 store.events(每次 receiveEvent 都是新数组,引用必变)。
4. 完成结果与注册率警告从未展示给用户
App.tsx 中 store.result 只写不读,完成后的 splat 数量、PLY 大小、路径、耗时均不渲染。
runner.rs:472 生成的 PipelineResult.warning(注册率 50%–80% 的质量警告)被完全丢弃,与 README「50%–80% 时给出质量警告并继续」不符。
src/lib/backend.ts:46 的 exportPly 无任何调用点,是死代码。
建议:phase === "completed" 时渲染结果卡片,含 warning 提示条,并接上「导出 PLY / 在文件管理器中显示」。
5. 失败或取消时 state.json 丢失全部进度
src-tauri/src/pipeline/runner.rs:335-341
let mut state = PipelineStateFile::created(quality);
state.stage = if cancelled { Cancelled } else { Failed };
run_project 内部维护的 state(video / frames / features_complete / matching_complete / reconstruction_complete)是局部变量,错误路径拿不到,于是用一个空白 state 覆盖磁盘,事后诊断信息全部丢失。
建议:错误分支先读回磁盘上已有的 state.json,只覆写 stage 字段;解析失败才退回 created(quality)。
其他(较次要,可顺带处理)
src-tauri/src/process/mod.rs:207-230:Windows 下先 spawn 再 AssignProcessToJobObject,这个窗口内子进程派生的孙进程会逃出 Job,取消时可能残留。彻底修需 CREATE_SUSPENDED;短期可把 Job 创建提前,并在取消分支补 child.wait().await。
src-tauri/src/engines/ffprobe.rs:17:probe_video 自建 ProcessManager::new(),导致探测阶段无法被取消。
src-tauri/src/pipeline/runner.rs:752:best_sparse_model 在 async 上下文里使用阻塞 std::fs::read_dir。
src/app/App.tsx:220:靠 message.includes("取消") 判断取消状态,依赖错误文案,建议结构化错误码。
src/lib/backend.ts:22:checkColmapAcceleration 缺少 inTauri() 守卫,与同文件其他函数不一致。
src-tauri/src/project/catalog.rs:236:只要 final.ply 存在就强制标记 Completed,会把「训练成功但发布失败」的项目误标为完成。
环境
- 版本:
0.2.0
- 方式:源码静态走查,未运行端到端流水线
Read::read允许短读,返回少于 256KB 也合法。此时end_header可能落在未读区域,报错「PLY 缺少 end_header」。失败点在 Brush 训练完成之后,用户损失最大。ply.rs:37-49属性校验用line.ends_with(" x"),但header.lines()会保留 CRLF 的\r,CRLF 头必然匹配失败,报「PLY 缺少 Gaussian 属性」。而同函数:26又显式接受ply\r\n,逻辑自相矛盾。建议:改用
file.take(n).read_to_end(&mut bytes);所有头行统一trim_end()后再比较。2. 失败/取消时进度条跳到 100%,时间线全部标绿
src-tauri/src/pipeline/progress.rs:16:Failed | Cancelled => (0.0, 100.0)src-tauri/src/commands/mod.rs:115:PipelineEvent::mapped(stage, 1.0, error.to_string())→progress = 100src/stores/appStore.ts:65:Math.max(...)把进度锁死在 100%src/app/App.tsx:47:failed/cancelled映射到 stage index 6,:295据此把前 6 个阶段渲染为done结果:任务在「顺序匹配」失败,UI 却显示
100.0%且所有阶段已完成。建议:终止事件不参与进度更新(前端过滤,或后端携带终止前的真实进度);时间线的 done 判定改用终止前记录的阶段索引。
3. 日志超过 500 条后停止自动滚动
src/app/App.tsx:141appStore.ts:61对日志做了slice(-500)封顶,长度恒为 500 后依赖值不再变化,effect 不再触发。长任务(精细档位 Brush 训练)后期日志不跟随底部。建议:依赖改为
store.events(每次receiveEvent都是新数组,引用必变)。4. 完成结果与注册率警告从未展示给用户
App.tsx中store.result只写不读,完成后的 splat 数量、PLY 大小、路径、耗时均不渲染。runner.rs:472生成的PipelineResult.warning(注册率 50%–80% 的质量警告)被完全丢弃,与 README「50%–80% 时给出质量警告并继续」不符。src/lib/backend.ts:46的exportPly无任何调用点,是死代码。建议:
phase === "completed"时渲染结果卡片,含 warning 提示条,并接上「导出 PLY / 在文件管理器中显示」。5. 失败或取消时
state.json丢失全部进度src-tauri/src/pipeline/runner.rs:335-341run_project内部维护的state(video/frames/features_complete/matching_complete/reconstruction_complete)是局部变量,错误路径拿不到,于是用一个空白 state 覆盖磁盘,事后诊断信息全部丢失。建议:错误分支先读回磁盘上已有的
state.json,只覆写stage字段;解析失败才退回created(quality)。其他(较次要,可顺带处理)
src-tauri/src/process/mod.rs:207-230:Windows 下先spawn再AssignProcessToJobObject,这个窗口内子进程派生的孙进程会逃出 Job,取消时可能残留。彻底修需CREATE_SUSPENDED;短期可把 Job 创建提前,并在取消分支补child.wait().await。src-tauri/src/engines/ffprobe.rs:17:probe_video自建ProcessManager::new(),导致探测阶段无法被取消。src-tauri/src/pipeline/runner.rs:752:best_sparse_model在 async 上下文里使用阻塞std::fs::read_dir。src/app/App.tsx:220:靠message.includes("取消")判断取消状态,依赖错误文案,建议结构化错误码。src/lib/backend.ts:22:checkColmapAcceleration缺少inTauri()守卫,与同文件其他函数不一致。src-tauri/src/project/catalog.rs:236:只要final.ply存在就强制标记Completed,会把「训练成功但发布失败」的项目误标为完成。环境
0.2.0