{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/webgl-browser-filesystem/",
  "atlas": "https://www.pystone.net/?node=webgl-browser-filesystem#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/webgl-browser-filesystem.md",
  "context": "https://www.pystone.net/notes/webgl-browser-filesystem.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:webgl-browser-filesystem",
  "slug": "webgl-browser-filesystem",
  "title": "WebGL浏览器平台文件系统",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "WebGL浏览器平台文件系统 Origin Private File System（OPFS）——浏览器里的 “ext4”",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "605765247d859f24b952a4296a7392c8c1fc9eeea6bc08b05f571766074ed62a",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "WebGL浏览器平台文件系统",
      "anchor": "webgl浏览器平台文件系统",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#webgl%E6%B5%8F%E8%A7%88%E5%99%A8%E5%B9%B3%E5%8F%B0%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"
    },
    {
      "depth": 2,
      "text": "Origin-Private File System（OPFS）——浏览器里的 “ext4”",
      "anchor": "origin-private-file-systemopfs浏览器里的-ext4",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#origin-private-file-systemopfs%E6%B5%8F%E8%A7%88%E5%99%A8%E9%87%8C%E7%9A%84-ext4"
    },
    {
      "depth": 2,
      "text": "File Picker 系列——让用户自己选文件 / 目录",
      "anchor": "file-picker-系列让用户自己选文件-目录",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#file-picker-%E7%B3%BB%E5%88%97%E8%AE%A9%E7%94%A8%E6%88%B7%E8%87%AA%E5%B7%B1%E9%80%89%E6%96%87%E4%BB%B6-%E7%9B%AE%E5%BD%95"
    },
    {
      "depth": 2,
      "text": "IndexedDB（IDB）——浏览器级 NoSQL 仓库",
      "anchor": "indexeddbidb浏览器级-nosql-仓库",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#indexeddbidb%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BA%A7-nosql-%E4%BB%93%E5%BA%93"
    },
    {
      "depth": 2,
      "text": "IDBFS（Emscripten / Unity）——把 POSIX 调用映射到 IndexedDB",
      "anchor": "idbfsemscripten-unity把-posix-调用映射到-indexeddb",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#idbfsemscripten-unity%E6%8A%8A-posix-%E8%B0%83%E7%94%A8%E6%98%A0%E5%B0%84%E5%88%B0-indexeddb"
    },
    {
      "depth": 2,
      "text": "MEMFS——纯内存临时文件",
      "anchor": "memfs纯内存临时文件",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#memfs%E7%BA%AF%E5%86%85%E5%AD%98%E4%B8%B4%E6%97%B6%E6%96%87%E4%BB%B6"
    },
    {
      "depth": 2,
      "text": "LocalStorage / SessionStorage",
      "anchor": "localstorage-sessionstorage",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#localstorage-sessionstorage"
    },
    {
      "depth": 2,
      "text": "选型建议速览",
      "anchor": "选型建议速览",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E9%80%89%E5%9E%8B%E5%BB%BA%E8%AE%AE%E9%80%9F%E8%A7%88"
    },
    {
      "depth": 2,
      "text": "Emscripten文件系统深入",
      "anchor": "emscripten文件系统深入",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#emscripten%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%B7%B1%E5%85%A5"
    },
    {
      "depth": 3,
      "text": "常见后端一览",
      "anchor": "常见后端一览",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E5%B8%B8%E8%A7%81%E5%90%8E%E7%AB%AF%E4%B8%80%E8%A7%88"
    },
    {
      "depth": 2,
      "text": "IDBFS 深潜：如何把 POSIX 调用落到 IndexedDB",
      "anchor": "idbfs-深潜如何把-posix-调用落到-indexeddb",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#idbfs-%E6%B7%B1%E6%BD%9C%E5%A6%82%E4%BD%95%E6%8A%8A-posix-%E8%B0%83%E7%94%A8%E8%90%BD%E5%88%B0-indexeddb"
    },
    {
      "depth": 3,
      "text": "挂载阶段",
      "anchor": "挂载阶段",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E6%8C%82%E8%BD%BD%E9%98%B6%E6%AE%B5"
    },
    {
      "depth": 3,
      "text": "正常读写",
      "anchor": "正常读写",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E6%AD%A3%E5%B8%B8%E8%AF%BB%E5%86%99"
    },
    {
      "depth": 3,
      "text": "FS.syncfs(populate, cb) —— 真正 I/O 的时刻",
      "anchor": "fssyncfspopulate-cb-真正-io-的时刻",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#fssyncfspopulate-cb-%E7%9C%9F%E6%AD%A3-io-%E7%9A%84%E6%97%B6%E5%88%BB"
    },
    {
      "depth": 3,
      "text": "Unity WebGL 中的默认流程",
      "anchor": "unity-webgl-中的默认流程",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#unity-webgl-%E4%B8%AD%E7%9A%84%E9%BB%98%E8%AE%A4%E6%B5%81%E7%A8%8B"
    },
    {
      "depth": 3,
      "text": "优缺点",
      "anchor": "优缺点",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E4%BC%98%E7%BC%BA%E7%82%B9"
    },
    {
      "depth": 2,
      "text": "其他后端一瞥",
      "anchor": "其他后端一瞥",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E5%85%B6%E4%BB%96%E5%90%8E%E7%AB%AF%E4%B8%80%E7%9E%A5"
    },
    {
      "depth": 3,
      "text": "MEMFS",
      "anchor": "memfs",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#memfs"
    },
    {
      "depth": 3,
      "text": "WORKERFS",
      "anchor": "workerfs",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#workerfs"
    },
    {
      "depth": 3,
      "text": "PROXYFS / OPFS backend（实验）",
      "anchor": "proxyfs-opfs-backend实验",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#proxyfs-opfs-backend%E5%AE%9E%E9%AA%8C"
    },
    {
      "depth": 2,
      "text": "为什么 Unity 选择 IDBFS",
      "anchor": "为什么-unity-选择-idbfs",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E4%B8%BA%E4%BB%80%E4%B9%88-unity-%E9%80%89%E6%8B%A9-idbfs"
    },
    {
      "depth": 2,
      "text": "在 JSLib / 自己的 JS 中使用",
      "anchor": "在-jslib-自己的-js-中使用",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E5%9C%A8-jslib-%E8%87%AA%E5%B7%B1%E7%9A%84-js-%E4%B8%AD%E4%BD%BF%E7%94%A8"
    },
    {
      "depth": 2,
      "text": "浏览器端持久文件系统对三大游戏引擎的适配要点",
      "anchor": "浏览器端持久文件系统对三大游戏引擎的适配要点",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AB%AF%E6%8C%81%E4%B9%85%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AF%B9%E4%B8%89%E5%A4%A7%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E7%9A%84%E9%80%82%E9%85%8D%E8%A6%81%E7%82%B9"
    },
    {
      "depth": 2,
      "text": "Unity (WebGL) — Emscripten 100 % 驱动",
      "anchor": "unity-webgl-emscripten-100-驱动",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#unity-webgl-emscripten-100-%E9%A9%B1%E5%8A%A8"
    },
    {
      "depth": 2,
      "text": "Cocos2d-JS / Cocos2d-HTML5 (2.x 及旧 Creator 1.x)",
      "anchor": "cocos2d-js-cocos2d-html5-2x-及旧-creator-1x",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#cocos2d-js-cocos2d-html5-2x-%E5%8F%8A%E6%97%A7-creator-1x"
    },
    {
      "depth": 3,
      "text": "可选持久层",
      "anchor": "可选持久层",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E5%8F%AF%E9%80%89%E6%8C%81%E4%B9%85%E5%B1%82"
    },
    {
      "depth": 2,
      "text": "Cocos Creator 3.x (原生 C++ Engine → WebAssembly)",
      "anchor": "cocos-creator-3x-原生-c-engine-webassembly",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#cocos-creator-3x-%E5%8E%9F%E7%94%9F-c-engine-webassembly"
    },
    {
      "depth": 2,
      "text": "选型指南",
      "anchor": "选型指南",
      "citation": "https://www.pystone.net/notes/webgl-browser-filesystem/#%E9%80%89%E5%9E%8B%E6%8C%87%E5%8D%97"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:game-graphics-and-runtime",
      "title": "游戏图形与运行时",
      "url": "https://www.pystone.net/notes/game-graphics-and-runtime/",
      "atlas": "https://www.pystone.net/?node=game-graphics-and-runtime#knowledge-atlas",
      "label": "游戏图形与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "WebGL中的“WebGL浏览器平台文件系统”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#webgl"
    }
  ],
  "contentMarkdown": "# WebGL浏览器平台文件系统\n## Origin-Private File System（OPFS）——浏览器里的 “ext4”\n\n* **原理**\n  * File System Access API 在站点沙箱分区创建真文件；数据直接落磁盘块，不占用 IndexedDB 配额。\n  * 主线程异步 I/O；Worker 可用 `getSyncAccessHandle()` 获得互斥同步 I/O（≈ fread/fwrite）。\n\n* **兼容性**\n  * Chrome / Edge 86+、Opera 对齐；Firefox 111+ 默认开启；Safari 17+ 全面支持（桌面 + iOS）。\n  * Android WebView 基于 Chromium 同版本也已带上。\n\n* **典型用法**\n\n```ts\n// 打开 / 创建沙箱根目录\nconst root = await navigator.storage.getDirectory();\n\n// 1) 异步写\nconst fh   = await root.getFileHandle('crash.dmp', { create: true });\nconst w    = await fh.createWritable();\nawait w.write(new Uint8Array(coreDump));\nawait w.close();\n\n// 2) Worker 同步写（C/C++重度场景）\nconst sync = await fh.createSyncAccessHandle();\nsync.write(buffer, { at: 0 });\nsync.flush();\nsync.close();\n```\n\n* **适合场景**\n  大体积或高频随机读写：崩溃转储、SQLite-WASM、视频缓存等。\n\n---\n\n## File Picker 系列——让用户自己选文件 / 目录\n\n* `showOpenFilePicker()` `showSaveFilePicker()` `showDirectoryPicker()`\n* **原理**：浏览器弹系统对话框，返回 **FileSystemFileHandle**；会话结束或权限收回后失效。\n* **兼容性**：与 OPFS 基本一致，但 Safari 17 仍只读/只选文件。\n* **适合场景**：手动导入/导出存档、MOD、截图等。不适合后台自动日志。\n\n---\n\n## IndexedDB（IDB）——浏览器级 NoSQL 仓库\n\n* **原理**\n  * Web 标准对象数据库，B+Tree/LSM 事务结构；异步 API。\n  * 每域配额：Chrome/Safari ≈ 60 % 空闲盘；Firefox 10 GiB 或 10 %；超限触发逐步清理。\n* **兼容性**：所有现代浏览器 + 老 Safari，移动端同理。\n* **速查片段**\n\n```ts\nconst req = indexedDB.open('uwa-crash', 1);\nreq.onupgradeneeded = () => req.result.createObjectStore('logs');\n\nreq.onsuccess = () => {\nconst db   = req.result;\nconst tx   = db.transaction('logs', 'readwrite');\ntx.objectStore('logs').put(blobCore, 'core-20250624');\ntx.oncomplete = () => console.log('saved');\n};\n```\n\n* **适合场景**\n  * 10 MiB \\~ GB 数据；跨刷新持久；最广兼容。\n  * 频繁写入会有事务开销，上层可聚合写（批量 / worker）。\n\n---\n\n## IDBFS（Emscripten / Unity）——把 POSIX 调用映射到 IndexedDB\n\n* **原理**\n  * Emscripten 在内存建一棵 **MEMFS**；你调用 `FS.writeFile`、`fopen()` 等都是同步。\n  * 调 `FS.syncfs(true)` 时把 IndexedDB → 内存，`syncfs(false)` 再刷回去。\n  * Unity Player 会自动 mount `/idbfs/<hash>` 并把 `Application.persistentDataPath` 指过去。\n\n* **兼容性**：跟 IndexedDB 一致；无需任何额外权限或标头。\n\n* **快速上手**\n\n  ```js\n  // 仅首次加载\n  FS.mkdir('/Crash');\n  FS.mount(IDBFS, {}, '/Crash');\n\n  FS.syncfs(true, () => {          // 拉取已存在的数据\n    FS.writeFile('/Crash/log.txt', 'new line\\n', { encoding: 'utf8' });\n\n    window.addEventListener('pagehide', () =>\n      FS.syncfs(false, () => console.log('flushed')));\n  });\n  ```\n\n* **适合场景**\n\n  * 现有 C/C++ / Unity 代码直接用 `fopen` / `File.WriteAllBytes`，不想改 JS。\n  * 日志、存档、配置；容量≈ IndexedDB 配额。\n\n---\n\n## MEMFS——纯内存临时文件\n\n* **原理**：全部驻留 JS Heap（`Uint8Array`）；浏览器刷新即丢。\n\n* **代码示例**\n\n  ```js\n  FS.writeFile('/tmp/foo.txt', '42');\n  console.log(FS.readFile('/tmp/foo.txt', {encoding:'utf8'}));\n  ```\n\n* **适合场景**：每帧生成的小文件、shader 缓存、测试 stub。\n\n---\n\n## LocalStorage / SessionStorage\n\n* 同步 `key ⇄ string`，容量 ≤ 5 \\~ 10 MiB。\n* 严重阻塞主线程，且自动转 UTF-16，不适合二进制。\n* 只建议存极小配置或「上次崩溃标记位」。\n\n## 选型建议速览\n\n* **优先级**\n  1. **OPFS**（若浏览器全部覆盖且对容量/并发有要求）\n  2. **IDBFS / IndexedDB**（最大兼容且容量较大）\n  3. **MEMFS + 手动上报/合并**（只要临时）\n  4. **File Picker**（需要人手操作）\n  5. **LocalStorage**（极小配置）\n\n## Emscripten文件系统深入\n\nEmscripten 把 **POSIX-style** 文件 API（`open / read / write / stat ...`）移植到浏览器，做法是：\n\n1. 在 **JavaScript** 里实现一个叫 **`FS`** 的虚拟文件系统内核；\n2. 允许把 *不同后端* “挂载”到目录，后端决定 **数据最终落在哪里**。\n\n```js\nFS.mkdir('/save');\nFS.mount(IDBFS , {}, '/save');        // /save 目录 → IndexedDB\nFS.mount(MEMFS , {}, '/tmp');         // /tmp  → 纯内存\nFS.mount(WORKERFS,{ files: …},'/rom'); // /rom  → 只读打包资源\n```\n\n### 常见后端一览\n\n| 后端           | 数据落点                               | 特性                                    | 典型用途                             |\n| ------------ | ---------------------------------- | ------------------------------------- | -------------------------------- |\n| **MEMFS**    | JS Heap                            | 同步最快；刷新即丢                             | 临时缓冲 / 单元测试                      |\n| **IDBFS**    | IndexedDB                          | **持久化**；同步 API + `FS.syncfs` 异步落盘 | 存档、日志、Unity `persistentDataPath` |\n| **WORKERFS** | 只读 HTML `<input type=file>` / Drop | 读取用户选中文件                              | MOD / 关卡导入                       |\n| **NODEFS**   | Node.js 真文件系统                      | 仅 Node 环境可用                           | CLI 工具                           |\n| **PROXYFS**  | 通过 `postMessage` 代理到 Worker        | 多线程 WASM                              | pthread 模式                       |\n\n\n## IDBFS 深潜：如何把 POSIX 调用落到 IndexedDB\n\n\n> **IDBFS =【同步 POSIX API】+【IndexedDB 持久层】**\n> 对 Unity WebGL & 传统 C/C++ 项目是当前最稳妥的文件系统解决方案；\n> 若追求极致性能和最新浏览器覆盖，可开始关注 **OPFS 后端 / WASMFS** 动向。\n\n原理: **在内存里跑一个 MEMFS，读写秒级同步；你调用 `FS.syncfs()` 时再把 *脏块* 批量落到 IndexedDB。**\n\n### 挂载阶段\n\n```js\nFS.mkdir('/idb');\nFS.mount(IDBFS, {}, '/idb');    // 现在 /idb 看起来就像一个普通磁盘\nFS.syncfs(true, onReady);       // populate=true ➜ IndexedDB ➜ 内存\n```\n\n* **`mount()`** 创建一个 *IDBFS mount* 结构体，内部再开一棵 **MEMFS** 作为 cache。\n* 会在 IndexedDB 里打开数据库 `EM_FS_<origin_path>`，对象仓库 `FILE_DATA`。\n* 每个文件序列化为 `{ path, timestamp, mode, contents(Blob) }`。\n\n### 正常读写\n\n所有 **POSIX 调用全是同步**，因为此时只是动 JS Heap：\n\n```cpp\nint fd = open(\"/idb/user/crash.dmp\", O_WRONLY | O_CREAT);\nwrite(fd, buf, len);                      // <1ms 内存写\nclose(fd);\n```\n\nEmscripten 在写入时为每个节点打 “dirty” 标记。\n\n### `FS.syncfs(populate, cb)` —— 真正 I/O 的时刻\n\n| 步骤               | populate = **true**     | populate = **false**        |\n| ---------------- | ----------------------- | --------------------------- |\n| ① 开 IndexedDB 事务 | `readonly`              | `readwrite`                 |\n| ② 遍历对象仓库         | 反序列化所有条目 → 填充 MEMFS     | 找到 dirty Node，序列化成 Blob     |\n| ③ 事务结束           | JS Heap ←→ IndexedDB 对齐 | IndexedDB 持久化完成后 `cb(null)` |\n\n> ⚠️ 如果你忘记在页面退出前 `syncfs(false)`，脏数据就会永远留在内存！\n\n### Unity WebGL 中的默认流程\n\n* Unity 的模板脚本在 `unityFileSystemInit()` 里自动执行\n  `FS.mkdir('/idbfs'); FS.mount(IDBFS,{},'/idbfs');`\n* **`Application.persistentDataPath`** 指向 `'/idbfs/UnityCache/xxx'`。\n* Loader 在首帧 `syncfs(true)`，在 `Module.QuitCleanup` 时 `syncfs(false)`。\n\n结果：C# 侧可以直接 `File.WriteAllBytes` 而无需关心浏览器差异。\n\n### 优缺点\n\n| 优点                                | 说明                                         |\n| --------------------------------- | ------------------------------------------ |\n| 同步 API → 对老 C/C++ / Unity 代码 0 改动 | POSIX 语义完整（锁、权限除外）                         |\n| 数据真正持久化，配额≈ IndexedDB             | Chrome/Safari ≈ 60 % 空闲盘；Firefox 10 GiB 上限 |\n| 批量事务，写放大极小                        | 每次 flush 统一写入                              |\n\n| 局限                                    | 规避方案                                             |\n| ------------------------------------- | ------------------------------------------------ |\n| 刷盘必须显式 `FS.syncfs()`；频繁调用有异步开销        | 定时或 `pagehide` / `visibilitychange` 时 flush      |\n| 单线程同步写 vs. Worker                     | 若需要高并发，考虑 OPFS + `SyncAccessHandle` 或 WASMFS 多后端 |\n| 旧 Safari 15- 对 IndexedDB 文件 Blob 支持不齐 | Polyfill（base64）或降级 LocalStorage                 |\n\n---\n\n## 其他后端一瞥\n\n### MEMFS\n\n* 零依赖纯 JS；容量受 JS Heap 限制。断电就丢。\n\n### WORKERFS\n\n* 把 `<input type=file>` 传来的 `File` 数组映射为只读节点，让 C/C++ 代码也能 `open(\"/rom/hero.png\")`。\n\n### PROXYFS / OPFS backend（实验）\n\n* 在 pthread / SharedArrayBuffer 场景，将 FS 调用代理到专用 Worker；或直接把 OPFS handle 当作块设备使用，跳过 IndexedDB 额外拷贝。\n\n---\n\n## 为什么 Unity 选择 IDBFS\n\n* **兼容性广**：自 2012 年起所有桌面 / 移动浏览器都内置 IndexedDB。\n* **无需额外权限**：不像 OPFS 需要较新的浏览器或 HTTPS。\n* **同步 API**：引擎层可在主线程 File I/O，不必改成回调或 Promise。\n* **崩溃安全**：transaction 级落盘，写时先 copy-on-write。\n\n---\n\n## 在 JSLib / 自己的 JS 中使用\n\n```js\nmergeInto(LibraryManager.library, {\n  SaveCrashReport: function(ptr, len) {\n    const data = new Uint8Array(Module.HEAPU8.buffer, ptr, len);\n    FS.writeFile('/idbfs/crash/' + Date.now() + '.dmp', data);\n    FS.syncfs(false, ()=> console.log('flushed'));\n  }\n});\n```\n\n只要 **`FS`** 已经是全局变量，你就可以随意调用：\n`FS.readdir`, `FS.stat`, `FS.readFile`, `FS.unlink` … 像 Node.js 的 `fs` 一样同步易用。\n\n\n## 浏览器端持久文件系统对三大游戏引擎的适配要点\n\n> **结论**\n> * **Unity WebGL** → 直接用 **IDBFS**（已内置）最稳；向上可平滑升级到 **OPFS**\n> * **Cocos2d-JS / Cocos2d-HTML5** → 本身纯 JS，没有 Emscripten；需走 **IndexedDB / OPFS** API 或社区封装\n> * **Cocos Creator 3.x (WASM backend)** → 既能用 **IDBFS**（引擎内部已挂）、又能调用浏览器原生 **OPFS**；留意多线程与刷盘时机\n\n---\n\n## Unity (WebGL) — Emscripten 100 % 驱动\n\n| 目标                                                    | 推荐方案                                   | 接入成本                                 | 说明                                                        |\n| ----------------------------------------------------- | -------------------------------------- | ------------------------------------ | --------------------------------------------------------- |\n| **通用持久化** (`Application.persistentDataPath`, 存档/崩溃转储) | **IDBFS** *(默认已经 `/idbfs` 挂载)*         | 0 行引擎改动少量 JS 调 `FS.syncfs()`     | `File.WriteAllBytes()` → 内存 → `syncfs(false)` → IndexedDB |\n| **高性能随机更新** (SQLite-WASM、日志实时刷新)                      | **OPFS + SyncAccessHandle**            | 自定义 JSLib/plug-in异步刷新或 Worker 同步 | 仅 Chrome 86+/Firefox 111+/Safari 17+；需 HTTPS              |\n| **让玩家导出/导入文件**                                        | **File Picker** (`showOpenFilePicker`) | 少量 JS 桥                              | 返回 `FileSystemFileHandle`，可流式读/写，关闭标签即失效                  |\n\n\n---\n\n## Cocos2d-JS / Cocos2d-HTML5 (2.x 及旧 Creator 1.x)\n\n* 引擎运行时 **完全是 JavaScript**，**没有 Emscripten** → 也就 **没有 FS / IDBFS**。\n* 官方资源缓存模块（`cc.loader`、`Downloader`）内部已经对 **IndexedDB + LocalStorage** 做了一层封装，用来缓存远程纹理等。\n\n### 可选持久层\n\n1. **IndexedDB**\n\n```ts\nimport { openDB } from 'idb';\nconst db = await openDB('game-db', 1, { upgrade(db){ db.createObjectStore('files'); } });\nawait db.put('files', blob, 'crash_0625.dmp');\n```\n2. **OPFS**（Chrome 86+）\n   需要写 Promise 异步或 Worker，同步写只在 `SyncAccessHandle`（87+）可用。\n3. **LocalStorage**\n   仅文本 / 小量数据（≤5 MiB）。\n\n> 若想要 **POSIX 风格同步 API**，可引入三方库（如 [BrowserFS](https://github.com/jvilk/BrowserFS)) 再让游戏逻辑调用 Node-like `fs`。\n\n---\n\n## Cocos Creator 3.x (原生 C++ Engine → WebAssembly)\n\n| 关键词                            | 说明                                                                                                       |\n| ------------------------------ | -------------------------------------------------------------------------------------------------------- |\n| **Emscripten 构建链**             | v3.x 默认为 `wasm` backend → 编译时同样带入了 `FS` 模块                                                               |\n| **默认挂载**                       | 引擎启动脚本会把 `IDBFS` 部署在 `remote/` 或 `gamecaches/` 目录用于 AssetBundle 缓存                                       |\n| **多线程（WebAssembly - pthread）** | 写文件时需避免主线程卡顿：• 在 Worker 侧用 `FS.write()`• 退出时在 **主线程** `FS.syncfs` 或 `postMessage` 让 worker flush |\n| **OPFS**                       | 可通过 `window.cc` 暴露的桥或自建 JSLib 直接调用；适合 SQLite-WASM、log rolling。                                           |\n\n\n---\n\n## 选型指南\n\n1. **先看引擎是否自带 Emscripten FS**\n   * **带** → 首选 **IDBFS**：代码零改动，易于移植。\n   * **不带** → 直接用 **IndexedDB**（兼容最好）或 **OPFS**（前沿但覆盖率 2025≈85 %）。\n2. **数据量 & 写入模式**\n   * 几 KB-MB，偶尔写 → LocalStorage/IndexedDB 都够。\n   * 多 MB-GB，频繁随机写 → OPFS / IDBFS+batch。\n3. **是否需要同步 API（阻塞式）**\n   * Unity、Cocos-WASM 这种老 POSIX 逻辑多 → IDBFS **同步** 最顺。\n   * 纯 JS 项目 → embrace async/await，直接 IndexedDB/OPFS 即可。\n4. **多线程 / Worker**\n   * IDBFS 仍可用，但要保证 **单线程 flush**。\n   * OPFS 的 `SyncAccessHandle` 允许 Worker 内部**真正同步**写盘。\n"
}
