feat(desktop): embed BoosAPI Desktop client as a subproject

Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,229 @@
# Worker Cache 性能分析总结
## 快速回答
**Worker 的 cache 非常重要且有意义!**
## 核心价值
### 1. **性能提升巨大** 🚀
- Cache 命中时间: **~0.01ms**
- Worker 渲染时间: **5-50ms**
- 提升倍数: **500-5000x**
### 2. **避免跨线程通信开销** 📡
```
无 Cache: 主线程 → Worker → 渲染 → 主线程 (每次 1-2ms 开销)
有 Cache: 内存读取 → 立即返回 (<0.01ms)
```
### 3. **内存占用极小** 💾
- 单条缓存: ~180 bytes
- 200 条缓存: ~36 KB
- 结论: **可以忽略不计**
## 实际测量数据
### 测试场景:真实文档(50 个公式)
| 方案 | 总耗时 | 主线程阻塞 | 缓存命中率 |
|------|--------|-----------|-----------|
| 无优化(直接渲染) | 250ms | 250ms | 0% |
| 仅 Worker(无缓存) | 265ms | 0ms | 0% |
| **Worker + Cache** | **75ms** | **0ms** | **70%** |
**性能提升: 3.3x** ✅
**用户体验: 完全流畅** ✅
### 公式复杂度对比
| 类型 | 示例 | 首次渲染 | 缓存命中 | 提升 |
|------|------|----------|----------|------|
| 简单 | `x = y` | 2ms | 0.005ms | 400x |
| 中等 | `\sum_{i=1}^{n}` | 10ms | 0.008ms | 1250x |
| 复杂 | `\int f(x) dx` | 30ms | 0.01ms | 3000x |
## 为什么 Cache 如此有效?
### 真实文档的特点:
1. **高重复率**:变量 `x`, `y`, `z` 经常出现
2. **模板公式**:`\sum`, `\int` 等常见结构
3. **增量渲染**:滚动、组件重新挂载会重复请求
### 实际缓存命中率统计:
```
数学文档: 70-85% 命中率
技术文档: 60-75% 命中率
教程文档: 75-90% 命中率(大量重复示例)
```
## 性能监控方法
### 方法 1: 运行基准测试
```bash
pnpm test test/benchmark/katex-worker-vs-direct.test.ts
```
### 方法 2: 浏览器控制台
```javascript
// 启用监控
window.__katexPerfMonitor.enable()
// 使用应用...
// 查看报告
window.__katexPerfReport()
```
### 方法 3: Chrome DevTools
1. 打开 Performance 标签
2. 录制渲染过程
3. 查看 Main 线程活动
4. 检查是否有长任务(>50ms)
**判断标准:**
- ✅ 如果看到 Main 线程有长时间黄色块 → Worker 有价值
- ✅ 如果 Worker 线程有独立活动 → Cache 在工作
- ⚠️ 如果两者都很空闲 → 公式太简单
## 决策树
```mermaid
graph TD
A[需要渲染数学公式?] -->|是| B{公式复杂度?}
A -->|否| Z[不需要 Worker]
B -->|简单 <5ms| C{数量多吗?}
B -->|中等 5-20ms| D[✅ 使用 Worker + Cache]
B -->|复杂 >20ms| D
C -->|<5 个| E[可选 Worker]
C -->|>5 个| D
D --> F{有重复公式吗?}
F -->|是 >30%| G[✅✅ Cache 价值巨大!]
F -->|否| H[✅ Worker 仍防止阻塞]
```
## 最终建议
### ✅ **保留当前架构**
你的代码已经是最佳实践:
```typescript
// 1. 先检查 Cache(<0.01ms)
const cached = cache.get(cacheKey)
if (cached)
return cached
// 2. 使用 Worker 渲染(不阻塞主线程)
renderKaTeXInWorker(content)
.then((html) => {
// 3. 保存到 Cache(未来受益)
cache.set(cacheKey, html)
})
.catch(() => {
// 4. Fallback 到直接渲染(稳定性)
katex.renderToString(content)
})
```
### 优势总结
| 特性 | 价值 |
|------|------|
| 性能 | ✅✅✅ Cache 提升 500-5000x |
| 体验 | ✅✅✅ Worker 避免 UI 卡顿 |
| 内存 | ✅✅✅ 仅 36KB (200 条) |
| 稳定性 | ✅✅✅ Fallback 机制保底 |
| 维护性 | ✅✅ 代码清晰简洁 |
## 常见问题
### Q: Cache 会不会内存泄漏?
**A:** 不会。使用 LRU 策略,限制最多 200 条。
### Q: SSR 环境怎么办?
**A:** 自动 fallback 到直接渲染,无需特殊处理。
### Q: Worker 初始化失败怎么办?
**A:** 代码已处理,会 reject 并触发 fallback。
### Q: 需要手动管理 Cache 吗?
**A:** 不需要。自动管理,开发者零负担。
## 性能优化技巧(可选)
### 1. 预热常见公式
```typescript
const common = ['x', 'y', 'E=mc^2']
common.forEach(f => renderKaTeXInWorker(f))
```
### 2. 调整缓存大小
```typescript
// 如果有大量唯一公式
const CACHE_MAX = 500 // 默认 200
```
### 3. 使用 requestIdleCallback
```typescript
requestIdleCallback(() => {
// 在浏览器空闲时预渲染
renderKaTeXInWorker(formula)
})
```
## 结论
**Worker 的 Cache 不仅有意义,而且是性能关键!**
- ✅ 提升性能 3-10 倍
- ✅ 改善用户体验(无卡顿)
- ✅ 内存占用可忽略
- ✅ 代码维护性好
**不要删除 Cache!它是整个架构的核心价值所在。** 🎯
## 组件级回退策略与差异(Inline vs Block)
为保证在高并发/高压力场景下不出现“无限 Loading”,组件层实现了差异化回退策略:
- MathBlock(块级公式)
- 优先使用 Worker 渲染。
- Worker 初始化失败(code: WORKER_INIT_ERROR)时,回退到主线程同步渲染(katex.renderToString,displayMode: true)。
- 同步渲染成功后,会通过 setKaTeXCache 预热 Worker 客户端缓存(key 区分 displayMode),以便后续同一公式命中缓存、避免重复成本。
- MathInline(行内公式)
- 优先使用 Worker 渲染,带回压重试。
- 发生以下错误时会尝试主线程同步渲染(displayMode: false),避免长时间处于 Loading:
- WORKER_INIT_ERROR(Worker 初始化失败)
- WORKER_BUSY(达到并发上限,忙)
- WORKER_TIMEOUT(单次 Worker 渲染超时)
- 如果同步渲染不可用或失败,将停止 Loading 并回退展示原始文本(raw),确保用户不看到“永久转圈”。
- 默认不调用 setKaTeXCache 进行缓存预热(与块级不同);这样做是出于实现简洁和收益权衡。若需要最大化命中率,可在同步回退成功后调用缓存预热(可选)。
### 设计权衡
- 行内公式通常更轻量、数量更多且重复度不稳定,主线程同步一次的代价较小,因此在 Worker 繁忙/超时时进行同步回退能显著改善体验。
- 块级公式复杂度和复用度通常更高,缓存预热能带来更明显的收益,所以默认启用。
### 相关错误码
- WORKER_INIT_ERROR:未注入/未能初始化 Worker;需要回退到主线程。
- WORKER_BUSY(常量:WORKER_BUSY_CODE):达到并发上限;可等待或回退。
- WORKER_TIMEOUT(name/code:"WorkerTimeout"/"WORKER_TIMEOUT"):单次渲染超时;可回退。
### 可选增强(按需开启)
- 如需让行内公式也享受回退后的缓存命中,可在同步渲染成功后调用 setKaTeXCache(content, false, html)。
- 若极端场景中担心主线程压力,可仅对超时(WORKER_TIMEOUT)而不是忙(WORKER_BUSY)启用同步回退,或增加等待窗口后再回退。