让浏览器模拟器的存储在不同浏览器中可靠工作

浏览器模拟器不只是把原生代码编译为 WebAssembly。一个可用的运行时还需要安全保存游戏数据、适应不同浏览器能力,并且在模拟器启动时保持页面响应。这篇说明介绍 Emu666 围绕源私有文件系统(OPFS)、Emscripten WasmFS 和 Web Worker 建立的工程边界。

这是技术说明,不是性能基准或兼容性认证。

关键边界:哪个线程拥有存储操作?

OPFS 可以通过 JavaScript 访问,也可以通过 WasmFS 后端访问;两条路径不能互相替代。特别是在部分 Safari 版本中,从浏览器主线程同步代理 WasmFS 的 OPFS 路径操作可能造成死锁。

因此实现将两类责任分开:

  • 模拟器启动前,JavaScript 使用浏览器原生 OPFS API 写入必须预先存在的数据。
  • 启动后,模拟器在 pthread worker 中挂载由 OPFS 支持的文件系统,而不是在主线程挂载。

这一边界既避免初始化期间的写入被后续挂载覆盖,也避免页面响应依赖同步文件系统代理。

启动流程

浏览器页面
  │
  ├─ JavaScript 通过原生 OPFS API 写入必要数据
  │
  ├─ Emscripten 创建 WebAssembly 运行时
  │
  └─ pthread worker 挂载 WasmFS OPFS 后端
       │
       └─ 模拟器读取已挂载的存储并启动

细节十分重要:主线程不会把属于 OPFS 运行时的路径当作通用的 module.FS 路径处理。小型临时数据可以放在内存文件系统;持久化数据则在 worker 所拥有的运行时准备好前走原生 OPFS 路径。

用能力探测代替浏览器名称规则

浏览器名称并不能说明全部情况。兼容性报告会检测运行时实际需要的能力,包括 WebAssembly、共享内存、线程、BigInt 集成、OPFS、Worker、WebGL、音频能力和 Gamepad API。

用户代理仅用于显示易读的浏览器与系统名称;是否可用由能力探测决定。你可以在 兼容性报告 中运行同一组检测。

跨浏览器安全写入

部分浏览器为 OPFS 文件提供 createWritable(),其他浏览器则需要基于 Worker 的 SyncAccessHandle 路径。存储层将这种差异收口到同一接口中,并在关闭文件前等待每次已排队的写入完成,避免在较严格实现中丢失尾部数据。

对大文件,运行时采用流式文件操作,而不是把完整文件载入 WebAssembly heap。这样既让内存占用更可控,也更适合长时间会话。

本说明不作出的承诺

  • 不声称所有浏览器或设备都受到支持。
  • 不发布通用性能对比。
  • 不提供游戏文件、固件、下载来源或规避版权的指引。
  • 不替代浏览器厂商文档,也不替代针对具体应用的可复现实验。

目标更明确:记录一个实用的线程与存储边界,使浏览器端 WebAssembly 运行时更容易理解、测试和持续改进。

复现相关检测

使用 浏览器兼容性报告 查看当前浏览器的能力结果。公开讨论实现细节时,应始终配套最小、与游戏无关的复现,以及明确的浏览器/版本矩阵。

延伸阅读