# Rive Persona 内存问题排查记录

日期：2026-07-21

## 背景

`next-webgis` 中的 Obsidian Persona 使用 Rive WebGL2 渲染。迁移到
`next-tjwater-frontend` 的 Vite、React 运行环境后，页面加载 Persona 时出现浏览器
`Out of Memory`，同时需要保留原有羽化灰黑球体的视觉效果。

本次排障验证使用 `@rive-app/react-webgl2@4.29.3`，依赖声明保留 4.x 兼容版本范围。
资源仍使用：

```text
https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/obsidian-2.0.riv
```

## 已证实的触发条件

在同一 Chromium 环境下进行了对照测试。以下内存值是排障时的单次观测范围，
用于判断增长趋势，不应视为跨设备性能基准。

| 场景                                              | 结果                                            |
| ------------------------------------------------- | ----------------------------------------------- |
| Vite、WebGL2、绑定 Rive View Model                | 渲染进程快速增长到约 4.6 GB，随后失去响应或崩溃 |
| Vite、阻止 `.riv` 加载                            | 约 29 MB 至 61 MB，保持稳定                     |
| Vite、Canvas、不绑定 View Model                   | 内存稳定，但渲染边缘和质感不符合原设计          |
| Vite、Canvas、调用 `bindViewModelInstance`        | 再次出现持续增长                                |
| Vite、WebGL2 4.29.3、调用 `bindViewModelInstance` | 再次出现持续增长                                |
| Vite、WebGL2 4.29.3、不绑定 View Model            | 约 71 MB 至 116 MB，最终约 78 MB                |
| `next-webgis` 原实现                              | 约 165 MB 至 221 MB，未出现持续增长             |

可以确认：在迁移后的 Vite 应用中，`bindViewModelInstance` 是泄漏的稳定触发点。
`useViewModelInstance` 内部也会执行该绑定，因此仅替换颜色 hook 不能解决问题。

不能仅凭现有证据断言缺陷完全属于 Vite、React 或 Rive 某一方。`next-webgis` 的同一
资源和完整绑定能够稳定运行，说明这是新运行环境与 Rive View Model 绑定生命周期的
兼容问题。若要定位到 Rive 内部对象，需要进一步制作最小复现并进行运行时级堆分析。

## 为什么不是 TypeScript 7

TypeScript 在该项目中只通过 `tsc --noEmit` 做静态检查。浏览器实际执行的是 Vite 和
SWC 转换后的 JavaScript。泄漏可以通过是否调用 Rive View Model 绑定稳定开关，因此
与 TypeScript 类型检查器无关。

## 最终修改

1. 保留 Rive WebGL2、Obsidian 资源和 `default` 状态机。
2. 移除 `useViewModel`、`useViewModelInstance`、`useViewModelInstanceColor` 及所有
   `bindViewModelInstance` 调用。
3. 使用 CSS `brightness` 和 `contrast` 保持浅色界面中的灰黑视觉，不再监听并不存在的
   应用深色主题。
4. Persona 仅在元素进入视口且页面可见时挂载，避免桌面和移动响应式节点同时初始化。
5. 首次初始化延迟到下一帧，使 React Strict Mode 的探测挂载可以在创建 Rive 实例前取消。
6. 保留懒加载和加载失败占位，但移除实验阶段的多皮肤、重复状态推导和无用事件透传。
7. 移除 Persona 环境变量开关，动画由组件生命周期直接管理。

## 回归保护

- `src/shared/ai-elements/persona.test.tsx` 验证加载的是 Obsidian 和 `default` 状态机，
  同时保证不会调用 `bindViewModelInstance`。
- `src/app/app.e2e.ts` 验证页面只发出一次 `.riv` 请求。
- 桌面 1440 x 900 和移动端 375 x 812 均验证无横向溢出。

验证命令：

```bash
pnpm typecheck
pnpm lint
pnpm test
pnpm test:browser
pnpm build
```

## 后续修改约束

- 在独立最小复现和持续内存采样通过前，不要重新引入 Rive View Model 绑定。
- 不要用 CSS 隐藏两个已挂载的 Persona，响应式副本必须通过可见性挂载保持单实例。
- 不要移除 Strict Mode 延迟初始化，除非重新验证开发和生产生命周期。
- 更新 Rive 版本时，至少验证单次 `.riv` 请求、状态机切换、页面切换后的实例清理和
  30 秒以上的浏览器进程内存趋势。
