v0.1首个版本化设计基线查看版本范围
Developer

asset

asset 只访问应用包中由 manifest 声明的只读资源。它解决“guest 如何取得自己的静态文件”和“宿主渲染器如何使用包内字体”,不是通用文件系统或编解码服务。

适用场景

  • 读取应用随包发布的关卡、词典、模板或二进制模型。
  • 将包内字体注册给 canvas 的宿主文本渲染器。
  • 由 guest 自己解码包内图片、音频或压缩数据。

用户选择的文件、拖放内容、网络下载、任意 URL、目录遍历和持久写回都不属于 asset

manifest 声明

资源先在包清单中获得稳定的 assetId

{
  "assets": {
    "levels/main": "assets/level-main.bin",
    "fonts/ui": "assets/ui.woff2"
  },
  "capabilities": [
    "host:asset.read",
    "host:asset.loadFont"
  ]
}

assetId 是清单键,不是包内路径。guest 不能把运行时字符串当路径拼接。

Reference

方法

asset.read

asset.read(assetId, output: ByteSink, offset? = 0) -> {
  written: u32,
  required?: u64,
  eof: boolean
}

offset 开始把资源字节写入 ByteSinkrequired 在宿主知道完整资源大小时给出;调用方不能假定它永远存在。eof: false 表示输出空间不足,调用方应以新 offset 继续。

asset.loadFont

asset.loadFont(assetId) -> Handle<font, use, app-context, runtime>

验证、解码并注册包内字体,返回只能用于宿主文本渲染接口的句柄。字体句柄不能被 guest 当字节读取,也不能跨应用传递。

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', ['开始'], uiFont);

原生 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 或内容哈希之外的推断。
  • 字体句柄只在当前运行时有效;应用卸载、热重载或宿主字体后端重置都会使其失效。
  • 读取可以取消;取消不影响资源本身。

错误

code 含义 retryable
not-found assetId 未在本版本 manifest 声明 false
invalid-offset offset 超出资源或不受支持 false
invalid-format 字体格式、魔数或结构校验失败 false
sink-too-small 该绑定要求的最小输出空间不足 true
limit-exceeded 读取速率、解码内存或字体数超预算 true

安全与预算

  • 信任档位:green;provenance 为 app-context
  • 宿主必须按 manifest 查表,不能把 assetId 转成未规范化路径后直接打开。
  • 字体先经过大小、格式和结构校验,再交给平台字体后端。
  • 预算覆盖单次输出、时间窗总字节、并发读取、活跃字体数和字体解码内存。
  • 资源内容不得包含宿主密钥、平台配置或其他应用的数据。

不属于本 API

图片/音视频解码、压缩、数据库、网络缓存和场景资源管理属于 L2。若以后基准证明某种解码必须宿主加速,应设计只消费明确 ByteSource 的独立能力,不能重新引入能接受任意 file token 的旁路。

一致性测试

宿主至少覆盖:未知 ID、路径穿越字符串、零长度资源、分块边界、取消、超大 offset、包升级、损坏字体、字体炸弹、句柄跨应用使用,以及 JS/原生 guest 的字节完全一致性。

Host API 版本与分层概览Host API v0.1版本非正式提议提议canvas API呈现canvas.getInfo()方法canvas.draw()方法canvas.present()方法canvas.measureText()方法canvas.frame event事件canvas.resize event事件asset API呈现asset.read()方法asset.loadFont()方法playback API呈现playback.open()方法playback.write()方法playback.stop()方法playback.ready event事件playback.ended event事件input API输入input.focus()方法input.pointer event事件input.key event事件input.wheel event事件ime API输入ime.openSession()方法ime.updateState()方法ime.updateGeometry()方法ime.closeSession()方法ime.textUpdate event事件ime.composition event事件ime.formatUpdate event事件ime.characterBoundsRequest event事件capture API输入capture.requestCamera()方法capture.requestMic()方法capture.frame()方法capture.readAudio()方法capture.stop()方法capture.ended event事件store API数据store.get()方法store.put()方法store.delete()方法store.list()方法store.deletePrefix()方法clipboard API数据clipboard.readText()方法clipboard.writeText()方法clipboard.readImage()方法clipboard.writeImage()方法net API网络net.fetch()方法identity API身份identity.status()方法identity.login()方法identity.logout()方法env API平台集成env.snapshot()方法env.change event事件print API平台集成print.open()方法开始调用 Host API指南构建交互式绘图应用指南构建自绘文本编辑器指南管理媒体采集与播放指南组织本地数据与同步指南