资源管线(Asset)
资源管线把「一个虚拟路径」变成「一个可消费的值」。消费者拿到的始终是句柄,而不是 URL、协议或文件系统路径。契约版本为 asset@1,声明见 src/core/asset/contract.ts,入口见 src/core/asset/index.ts。
当前进度:Phase 2「演出与音频」为已完成;bytes/text/json 等基础路径随 Phase 1 通过两端验收,图像、音频与字体等解码形态已有真实宿主复跑记录。
边界:什么走资源管线
| 内容类型 | 走哪条路 | 原因 |
|---|---|---|
图像、音频、字体、UI 皮肤、lang 文本表、地图表现 | 资源包 → 资源管线 | 只被消费,不需要被解释 |
| 剧本、地图逻辑、道具与数值 | 数据包 → registry | 要被剧本 VM 解释 |
| 安全关键 UI 的字体与样式 | 随产物内置 | 不经资源管线,见下文 |
两条禁令:
- 资源管线不得承载 registry 条目;
- registry 不得用来寻址资产。
资源管线与数据包的完整分界见分发包与包结构,插件级接口见 Asset 插件参考。
VPath:唯一的寻址方式
VPath 的形状是 assets/<命名空间>/<类型>/<路径>:
assets/
<命名空间>/
<类型>/
<路径>| 规则 | 说明 |
|---|---|
| 段数 | 至少四段,assets 为第一段 |
| 路径安全 | 不得以 / 开头,不得含反斜杠或空字节;任何一段都不得是空串、. 或 .. |
| 与平台无关 | 不映射协议、盘符或 URL;由来源栈决定实际读哪里 |
| 全局唯一 | 同一 VPath 在所有消费者眼中指向同一逻辑资源 |
示例:
assets/main/bg/classroom.png
assets/main/cast/character-a/smile@2x.png
assets/core/font/ui.ttf
assets/main/skin/tokens.json命名空间与类型用于组织资源,具体取值由包作者决定;无论怎么取,都必须符合上述形状与安全规则。上层消费者不应自行拼接文件系统路径。
来源栈:一个路径的多层候选
来源栈(SourceSet)是某个 VPath 的有序候选来源列表,后挂者优先:
┌──────────────────────────────┐
高 │ 后装载的资源包 │
├──────────────────────────────┤
│ 先装载的资源包 │
├──────────────────────────────┤
低 │ 内置资源包 builtin │
└──────────────────────────────┘三条硬规则:
| 编号 | 规则 |
|---|---|
| A1 | 翻译产物必须伪装成资源包。离线产物落成标准资源包;在线产物注册为内存包,生命周期绑定翻译器 fiber。管线不认任何特例入口。 |
| A2 | 内置资源也算一个资源包,位于来源栈最底。 |
| A3 | 来源栈只在 ① 段构造。② 段之后不得回看来源栈;换包等于重跑 ① 段。 |
其他约束:
- 只有
resourcepack进入来源栈,随kernel:on-unit-loaded/kernel:on-unit-unloaded压入与移除;内置资源包在根插件之后最先装载,自然位于最底层。 - 候选来源带有
{ scheme, unit, version, path };unit是资源包的稳定 ID。 asset:exists、asset:list、asset:fingerprint与取用共享同一套解析与重定向;asset:list枚举的是实际包内路径,不凭空生成别名。
五段管线
VPath
│
├─ ① 解析来源与重定向 ──> 有序候选来源
├─ ② 读取字节 ──> Uint8Array
├─ ③ 预算核对 ──> 文件大小判定
├─ ④ 解码或生成 ──> 可消费的值
└─ ⑤ 缓存与交付 ──> Handle| 段 | 职责 | 实现位 | 机制插槽 |
|---|---|---|---|
| ① 解析 | VPath → 候选来源;变体候选、别名、包内重定向 | asset:resolver@1 | asset:resolve-path、asset:resolve-source-order |
| ② 读取 | 按 scheme 分派读取字节 | asset:fetcher@1 | — |
| ③ 预算 | 文件阶段判定大小;解码体积在 ④ 之后复核 | asset:budget-policy@1 | — |
| ④ 解码 | 字节 → 可消费资源;或交给生成器 | asset:decoder@1、asset:variant-producer@1 | asset:resolve-decoding |
| ⑤ 缓存与交付 | 缓存键、引用计数、失效通知 | asset:cache-policy@1 | — |
- 管线结构不可变。插件只能替换实现位,Mod 只能改机制插槽;两者都不能增删段。
pack:来源由asset:fetcher@1调用kernel:unit-files读取,Kernel 对每次读取复核 sha256,不另建一套包读取器。- ④ 段先由
asset:resolve-decoding决定解码器名或{ producer },再执行;生成器内部经decode复用解码器。每个阶段的大小判定都由管线强制执行,超限即中止并显式报错。
六种交付形态
as | 默认解码 | 句柄里的值 | 副本策略 |
|---|---|---|---|
bytes | bytes | ArrayBuffer | 每个句柄一份 |
text | text | string | — |
json | json | 深冻结对象 | 共享 |
texture | image(浏览器解码) | ImageBitmap | 每个句柄一份副本,释放即 close();主份随缓存条目回收 |
stream | audio;.hca 走 hca-pcm | { sampleRate, channels, loop? } | 每个句柄一份 PCM |
font | font;带 { subset } 时走 font-subset | 已 load()、未装入文档的 FontFace | 每个句柄重新解码一份 |
补充规则:
stream的channels是各声道Float32Array;loop为采样位置{ start, end },end不含。标准音频解码会重采样到上下文采样率(内置实现取 48 kHz),设备采样率换算交给 Web Audio。font的family由字体字节的 sha256 前 12 位生成,形如asset-<12位十六进制>;消费者只能读value.family,不得假定固定名字。- 解码体积用于预算与缓存计量:纹理按宽 × 高 × 4,PCM 按各声道字节之和,
bytes按长度,其余按文件字节。 - 消费者必须在自身 effect 中释放句柄;引用归零后由缓存策略决定立即丢弃还是保留。收到失效通知后不得继续缓存旧句柄。
变体
| 变体形式 | 适用形态 | 查找方式 | 回退 |
|---|---|---|---|
字符串 v | 所有形态 | 同目录、扩展名前插入 @v:a.png → a@2x.png | 当前来源栈没有该文件时回退原文件 |
{ subset: "<字符>" } | 仅 font | 字符去重、按码位排序后生成子集 | 无子集时按完整字体交付 |
- 变体名不能为空、不能含
/,插入后必须仍是合法 VPath;新装载的资源包带来某个变体文件时,此前按该变体取用(可能回退到原文件)的缓存结果一并失效。 - 字体子集只保留用到的字形;复合字形的部件一并保留,
cmap只含保留字符。CJK 的 CID 字体因子程序不裁,子集可能仍然较大。
包内逻辑地址重定向
重定向允许经准入的 Mod 调整包内资源定位,但不增加磁盘或网络权限,也不向消费者暴露 URL。
| 环节 | 行为 |
|---|---|
| 执行位置 | 均在 ① 段内:先经 asset:resolve-path 与 asset:resolve-source-order 得到候选,再对每个 pack 候选经 Platform 重定向 |
| 调用方式 | Asset 经 Cordis 调用 platform:host.resolveAssetUrl({ vpath, as, source });Platform 内部执行 platform:resolve-asset-url,默认原样返回 |
| 目标范围 | 只能是本次解析快照中任一已装载资源包的文件,返回 { scheme: 'pack', unit, version, path } |
| 失败处理 | 目标包不在快照、版本不符、路径不合法或文件不存在,一律报 redirect-invalid,不静默改读其他文件 |
| 查询一致性 | asset:acquire、asset:exists、asset:fingerprint 使用同一重定向;asset:list 仍列实际路径 |
| 失效 | 缓存键使用重定向后的实际目标;来源包或目标包卸载都令相关逻辑路径失效 |
platform:on-asset-url-change 在重定向钩子登记、撤销或生效宿主后端切换时发出。Asset 收到后保守清空缓存,并对已缓存路径发 asset:on-invalidate。读取期间发生相关失效时,本次结果以 invalidated 拒绝交付,由调用方重新取用。
缓存与失效
缓存键包含:
来源包稳定 ID + 版本 + 实际路径 + VPath + 变体 + 形态 + 解码器(或生成器)条目名与版本失效触发:
| 触发 | 影响范围 |
|---|---|
| 新资源包装载并遮住已有路径 | 被遮住的路径与已缓存路径 |
| 新资源包带来某个字符串变体文件 | 该变体的原路径缓存 |
| 资源包卸载 | 它提供过的路径,以及来源或目标指向它的条目 |
platform:on-asset-url-change | 保守清空全部缓存 |
| 解码器、生成器或相关实现位变化 | 全部已缓存结果 |
asset:invalidate(target) | 指定路径,或以 / 结尾的指定前缀 |
- 失效后广播
asset:on-invalidate,载荷为{ vpaths };消费者必须重新acquire,不得继续使用旧句柄。 - 进行中的读取若跨越了失效,不交付结果,明确报
invalidated。
预算与四项安全硬约束
媒体解码使用隔离会话:媒体 Worker 会话中同时在途的请求最多 16 个,主页面最多排队 64 个。超出只拒绝当前请求;取消会通知 Worker 丢弃未开始请求,迟到结果不交付,已发出的外部读取仍可能完成但其结果会被忽略。
默认预算:
| 项 | 上限 |
|---|---|
| 单个文件 | 256 MiB |
| 解码后的单个资源 | 512 MiB |
| 缓存空闲总量 | 512 MiB |
| 单条缓存 | 超过 512 MiB 不保留 |
安全硬约束:
| 编号 | 约束 |
|---|---|
| S1 | ④ 段的二进制解析一律在 Rust 侧完成(acgcore);acgcore 必须同源编译到 wasm32,网页端经 wasm 运行同一份解析代码。标准图像格式仍可交浏览器自带解码器。acgcore 及其依赖不得假设 tokio、std::fs 或多线程。 |
| S2 | asset:decoder@1 与 asset:variant-producer@1 只能由受审插件注册;资源包与数据包不得影响解码路径。经 asset:resolve-decoding 改写解码路径者,审计强度不得低于经实现位注册者,两个入口不得形成审计落差。 |
| S3 | ③ 段预算由管线强制;超限即中止并显式告警。 |
| S4 | 管线不向消费者暴露原始 URL;只有 host/ 层 fetcher 内部可见。 |
资源包约束
资源包是表现层扩展,不是内容或逻辑扩展。
| 允许 | 禁止 |
|---|---|
| 通过 VPath 覆盖既有表现资产 | 声明或改变任何控件的位置、尺寸、层级、命中区域、事件绑定、文本断行规则 |
提供图像、音频、字体、UI 皮肤、lang 文本表、地图表现 | 影响解码路径或资源管线的实现选择 |
| 在来源栈中按装载顺序覆盖低优先级包 | 承载 registry 条目或任何要被 VM 解释的数据 |
- 唯一例外:安全关键 UI 连表现层也不受资源包影响。
assets/sui/为保留命名空间,任何单元只要含该前缀下的文件即被拒绝装载(GUARD_SECURE_UI_OVERRIDE);安全关键 UI 只用随产物内置的字体与样式,不经资源管线取任何资源。 - 地图必须拆成两层:地图表现属资源包,地图逻辑属数据包;地图定义不得携带执行语义。
- 资源包整体替换的出口条件尚在逐项核验,当前状态为测试中。
现状
| 能力 | 状态 | 说明 |
|---|---|---|
精确路径解析、来源栈、bytes/text/json | 已完成 | Phase 1 通过两端验收;本地 Node 与系统 Edge 用例通过 |
| 文件与解码预算、缓存与失效通知 | 测试中 | 契约用例在 Node 与系统 Edge 通过;宿主端复跑待完成 |
texture/stream/font | 测试中 | 浏览器解码用例只在网页端跑,宿主端待复跑 |
字符串变体与 { subset } | 测试中 | 同上 |
hca-pcm 与 font-subset | 测试中 | 在隔离环境的常驻会话中经 acgcore 完成;网页端用例通过 |
| 包内逻辑地址重定向 | 测试中 | 契约与失效路径已接入,宿主端复跑待完成 |
priority/cache 选项 | 未实现 | 接收但暂不生效 |
| 全部资产可被资源包整体替换 | 测试中 | Phase 2 出口条件尚未逐项核验 |
相关阅读:Asset 插件参考、网页与 Tauri、隔离运行、翻译层、装载管线与 guard。