asset
asset 只访问应用包中由 manifest 声明的只读资源。它解决“guest 如何取得自己的静态文件”和“宿主渲染器如何使用包内字体”,不是通用文件系统或编解码服务。
适用场景
- 读取应用随包发布的关卡、词典、模板或二进制模型。
- 将包内字体注册给
canvas的宿主文本渲染器。 - 由 guest 自己解码包内图片、音频或压缩数据。
用户选择的文件、拖放内容、网络下载、任意 URL、目录遍历和持久写回都不属于 asset。
L1 边界
宿主独占的是已安装应用包的资源索引、只读字节和向宿主文本后端注册字体的效果。guest 无法仅靠计算打开壳层代持的包资源;格式解码、缓存、资源图和场景语义都可在取得字节后由 guest 实现,不进入 L1。
manifest 声明
资源先在包清单中获得稳定的 assetId:
{
"assets": {
"levels/main": "assets/level-main.bin",
"fonts/ui": { "path": "assets/ui.woff2", "mediaType": "font/woff2" }
},
"capabilities": [
"host:asset.read",
"host:asset.loadFont"
]
}
assetId 是清单键,不是包内路径。guest 不能把运行时字符串当路径拼接。清单中的包内路径按版本总则使用规范 ASCII 段,不接受百分号编码;因此同一个声明只有一个可请求 URL,不会在壳层 URL 解析时变成另一条路径。
Reference
方法
asset.read
asset.read(assetId, output: ByteSink, offset? = 0) -> {
written: u32,
totalSize: u64,
eof: boolean
}
从 offset 开始把资源字节写入非空 ByteSink。包资源大小固定且宿主已知,因此始终返回 totalSize。written 等于 min(output.capacity, asset.maxReadBytes, totalSize - offset);eof 当且仅当 offset + written == totalSize。offset === totalSize 合法并返回 written: 0, eof: true,更大则为 invalid-offset。调用方以新 offset 继续,不需要先探测大小。
asset.loadFont
asset.loadFont(assetId) -> Handle<font, use, app-context, runtime>
验证、解码并注册包内 WOFF2 字体,返回只能用于宿主文本渲染接口的句柄。v0.1 不接受 TTF、OTF、CSS font-face 或可加载外部资源的字体描述。字体句柄不能被 guest 当字节读取,也不能跨应用传递。同一运行时对同一 assetId 重复调用必须复用同一已验证字体资源,不增加 asset.maxLoadedFonts 计数。
JS 工作流示例
读取一个大小未知的包内二进制资源:
async function readAsset(assetId: string): Promise<Uint8Array> {
const chunks: Uint8Array[] = [];
let offset = 0;
for (;;) {
const buffer = new Uint8Array(64 * 1024);
const result = await host.asset.read(assetId, buffer, offset);
chunks.push(buffer.subarray(0, result.written));
offset += result.written;
if (result.eof) return concat(chunks, offset);
}
}
const levelBytes = await readAsset('levels/main');
const level = decodeLevel(levelBytes); // guest 库负责格式语义
注册字体并交给 canvas:
const uiFont = await host.asset.loadFont('fonts/ui');
const [metrics] = await host.canvas.measureText('main', ['开始'], {
font: uiFont,
size: 16,
align: 'start',
direction: 'ltr',
});
原生 WASM 绑定示例
原生 guest 可以用固定缓冲循环读取,无需一次分配完整资源:
uint8_t chunk[65536];
uint64_t offset = 0;
for (;;) {
AssetReadResult r = host_asset_read(
"levels/main",
byte_sink(chunk, sizeof(chunk)),
offset
);
level_decoder_push(chunk, r.written);
offset += r.written;
if (r.eof) break;
}
绑定层只暴露实际写入的字节;未写区域不得被当作宿主返回值。
生命周期
- 包内资源在应用版本生命周期内不可变。
- 更新应用版本可以替换同名
assetId的内容,因此 guest 不得跨版本缓存裸 offset 或内容哈希之外的推断。 - 字体句柄在当前 runtime 结束前保持有效;宿主不得在仍运行时主动失效它。应用卸载、热重载或 runtime 重启结束整个生命周期。v0.1 不提供提前释放;可加载字体集合由 manifest 有限 assets 和
asset.maxLoadedFonts共同封闭。 - 单次读取受
asset.maxReadBytes限制并在一次调用中完成;v0.1 不为它建立长任务或取消状态机。
错误
| code | 含义 | retryable |
|---|---|---|
not-found |
assetId 未在本版本 manifest 声明 |
false |
invalid-offset |
offset 超出资源或不受支持 | false |
invalid-format |
字体格式、魔数或结构校验失败 | false |
limit-exceeded |
读取速率、解码内存或字体数超预算 | true |
安全与预算
- 信任档位:green;provenance 为
app-context。 - 宿主必须按 manifest 查表,不能把
assetId转成未规范化路径后直接打开。 - 字体先经过大小、格式和结构校验,再交给平台字体后端。
- v0.1 预算键为
asset.maxReadBytes(单次写入 ByteSink 的字节)、asset.maxFontBytes(单个 WOFF2 文件字节)、asset.maxLoadedFonts(当前 runtime 中不同字体 assetId 的数量)、asset.callsPerMinute(方法调用数)。 - 资源内容不得包含宿主密钥、平台配置或其他应用的数据。
不属于本 API
图片/音视频解码、压缩、数据库、网络缓存和场景资源管理属于 L2。若以后基准证明某种解码必须宿主加速,应设计只消费明确 ByteSource 的独立能力,不能重新引入能接受任意 file token 的旁路。
一致性测试
宿主至少覆盖:未知 ID、路径穿越字符串、零长度资源、分块边界、空 sink、超大 offset、包升级、损坏字体、字体炸弹、句柄跨应用使用,以及 JS/原生 guest 的字节完全一致性。