Files
next-tjwater-drainage-frontend/docs/rive-persona-incident.md
T

4.2 KiB

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 兼容版本范围。 资源仍使用:

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. 移除 useViewModeluseViewModelInstanceuseViewModelInstanceColor 及所有 bindViewModelInstance 调用。
  3. 使用 CSS brightnesscontrast 保持浅色界面中的灰黑视觉,不再监听并不存在的 应用深色主题。
  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 均验证无横向溢出。

验证命令:

pnpm typecheck
pnpm lint
pnpm test
pnpm test:browser
pnpm build

后续修改约束

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