外部帧接口
概述与应用场景
Section titled “概述与应用场景”Migo 通常在自己的脚本运行时内执行 JavaScript 并直接渲染;外部帧接口适用于另一种部署模式:内容脚本在独立进程中运行,例如 iOS 上的 WebKit WebContent 进程,该进程出于安全和 JIT 限制运行在宿主进程之外。这种模式下,内容侧将一帧的 GPU 绘制工作编码成一个有界二进制包,通过自定义传输层发送到宿主进程,再由宿主调用 migo_session_submit_external_frame 将其注入 Migo session;Migo 在此负责渲染和合成,而不运行任何 JavaScript 引擎。
注意:此模式仅在 Performance+ 产品中可用。 其他产品不导出本页所列的入口点;链接错误产品会在构建期得到未解析符号,这是故意设计的快速失败,而不是一个运行时返回失败的存根符号。
本接口分为三条功能通道:
| 通道 | 作用 |
|---|---|
| 帧注入通道(Ingress) | 将跨进程帧包提交给 session,并获取接受/拒绝决策与背压信号 |
| 同步屏障通道(Sync barrier) | 为 readPixels、getImageData、toDataURL 等必须阻塞等待结果的调用提供请求/回复邮箱 |
| 资源通道(Resource lane) | 在帧包之外预留并分块上传纹理图集等大型资产;验证通过后帧才可引用 |
| 服务流(Service stream) | 绘制以外的一切请求——读文件、写存档、加载图片、播放声音——按序准入,在自己的返回流上应答 |
| 内容源(Content origin) | 宿主以引擎自己的模块加载规则提供游戏脚本,使 WebKit 求值的源码与内嵌运行时一致 |
launch_nonce 与 session 身份
Section titled “launch_nonce 与 session 身份”外部帧模式下,宿主在创建 session 时需向 MigoSessionConfig.launch_nonce 填入一个 128 位随机密钥;传输层在每个帧包的信封中携带相同的 nonce,Migo 以此验证包属于正确的 session。
launch_nonce 必须由宿主生成(例如 iOS 上用 SecRandomCopyBytes),不由 Migo 库生成,原因是宿主同时拥有传输层和 session 两端,是唯一可以安全保管这一共享密钥的一方。全零值被视为“无外部生产者”,不会验证任何帧包——这也是旧版本宿主零初始化结构体时的默认语义。nonce 不得出现在日志、URL 或查询字符串中。
使用
MigoExternalSessionDescriptor创建外部帧 session 时,launch_nonce[16]字段同样存在于该 descriptor 中,语义与MigoSessionConfig.launch_nonce完全相同。
MigoFrameIngressDecision
Section titled “MigoFrameIngressDecision”typedef uint32_t MigoFrameIngressDecision;
#define MIGO_FRAME_INGRESS_ACCEPTED 1U#define MIGO_FRAME_INGRESS_WOULD_BLOCK 2U#define MIGO_FRAME_INGRESS_REJECTED 3U#define MIGO_FRAME_INGRESS_GENERATION_LOST 4Umigo_session_submit_external_frame 调用成功后,MigoFrameIngressOutcome.decision 字段持有此类型的值,表示本次帧包的处置结果。
| 值 | 含义 |
|---|---|
MIGO_FRAME_INGRESS_ACCEPTED |
帧包已被接受,消耗一个信用额度,直到渲染器报告完成后归还。 |
MIGO_FRAME_INGRESS_WOULD_BLOCK |
包格式合法但信用额度已耗尽,生产者必须等待;不可丢弃该包,因为包内可能含有后续帧依赖的状态变更。 |
MIGO_FRAME_INGRESS_REJECTED |
包格式错误或目标 session 不匹配;不消耗信用,生产者不得重发相同字节。 |
MIGO_FRAME_INGRESS_GENERATION_LOST |
字节合法,但对应的运行时代(runtime generation)已不存在(WebContent 进程已替换或 session 已重载);不是错误,重试无效。 |
MigoFrameIngressOutcome
Section titled “MigoFrameIngressOutcome”typedef struct MigoFrameIngressOutcome { uint32_t struct_size; uint32_t abi_version; uint64_t accepted_sequence; MigoFrameIngressDecision decision; uint32_t remaining_credits; uint32_t wire_error_code; uint32_t reserved0;} MigoFrameIngressOutcome;库写入,append-only:调用方必须在每次调用前将 struct_size 和 abi_version 填写完毕,否则调用返回 MIGO_ERROR_INVALID_ARGUMENT。建议初始化一次后复用该结构体,而非每帧重建。
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
调用方输入。调用前设为 sizeof(MigoFrameIngressOutcome),限定库的写入范围。 |
abi_version |
uint32_t |
调用方输入。设为 MIGO_ABI_VERSION_CURRENT。 |
accepted_sequence |
uint64_t |
仅 ACCEPTED 时非零;为本次帧包分配的单调序列号。 |
decision |
MigoFrameIngressDecision |
帧包处置结果,见上表。 |
remaining_credits |
uint32_t |
当前剩余可用信用额度;生产者可据此决定是否继续发送。 |
wire_error_code |
uint32_t |
仅 REJECTED 时非零;稳定的错误编号,供遥测区分“包太短”与“目标 session 不匹配”等情况(1–1000 为信封错误,1001+ 为身份/顺序错误)。 |
reserved0 |
uint32_t |
保留,忽略。 |
同步屏障通道
Section titled “同步屏障通道”MigoSyncState
Section titled “MigoSyncState”typedef uint32_t MigoSyncState;
#define MIGO_SYNC_STATE_FREE 0U#define MIGO_SYNC_STATE_PENDING 1U#define MIGO_SYNC_STATE_READY 2U#define MIGO_SYNC_STATE_FAILED 3U#define MIGO_SYNC_STATE_CANCELLED 4U每个 session 同一时刻只允许一条同步请求处于飞行中(生产者是单一代理,阻塞等待时无法再发起第二条)。
| 值 | 含义 |
|---|---|
MIGO_SYNC_STATE_FREE |
邮箱空闲,只有此状态下可以投递新请求。 |
MIGO_SYNC_STATE_PENDING |
已投递,生产者正在阻塞等待。 |
MIGO_SYNC_STATE_READY |
已回复,reply_bytes 指示回复长度。 |
MIGO_SYNC_STATE_FAILED |
不会回复,error 字段说明原因。 |
MIGO_SYNC_STATE_CANCELLED |
生产者在收到回复前主动撤回了请求。 |
MigoSyncError
Section titled “MigoSyncError”typedef uint32_t MigoSyncError;
#define MIGO_SYNC_ERROR_ALREADY_PENDING 1U#define MIGO_SYNC_ERROR_REQUEST_ID_MISMATCH 2U#define MIGO_SYNC_ERROR_STALE_GENERATION 3U#define MIGO_SYNC_ERROR_REPLY_TOO_LARGE 4U#define MIGO_SYNC_ERROR_TIMED_OUT 5U#define MIGO_SYNC_ERROR_SESSION_ENDED 6U#define MIGO_SYNC_ERROR_UNSUPPORTED_OPERATION 7U#define MIGO_SYNC_ERROR_LATE_REPLY 8U#define MIGO_SYNC_ERROR_BAD_DEADLINE 9U#define MIGO_SYNC_ERROR_BAD_REPLY_RESERVATION 10U这些错误码在版本间保持稳定,生产者可将其映射为对应的异常类型。
| 值 | 含义 |
|---|---|
ALREADY_PENDING |
已有请求在飞行中,不可重叠投递。 |
REQUEST_ID_MISMATCH |
回复携带的请求 ID 与飞行中的不匹配。 |
STALE_GENERATION |
运行时代已更换,此请求不再有效。 |
REPLY_TOO_LARGE |
回复超出了 max_reply_bytes 预留的空间。 |
TIMED_OUT |
请求在 deadline_nanos 之前未得到回复。 |
SESSION_ENDED |
Session 在回复到来前已销毁。 |
UNSUPPORTED_OPERATION |
请求的 operation 字段在当前构建中不支持。 |
LATE_REPLY |
回复在截止时间之后到达。 |
BAD_DEADLINE |
deadline_nanos 为零或不合理。 |
BAD_REPLY_RESERVATION |
max_reply_bytes 为零或超出允许范围。 |
MigoSyncRequestDescriptor
Section titled “MigoSyncRequestDescriptor”typedef struct MigoSyncRequestDescriptor { uint32_t struct_size; uint32_t abi_version; uint64_t runtime_generation; uint64_t surface_generation; uint64_t resource_epoch; uint64_t triggering_sequence; uint64_t deadline_nanos; uint32_t operation; uint32_t max_reply_bytes;} MigoSyncRequestDescriptor;调用方写入。64 位字段统一排在 32 位字段前,保证 LP64 与 ILP32 下布局相同,无内部填充(56 字节)。
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
设为 sizeof(MigoSyncRequestDescriptor)。 |
abi_version |
uint32_t |
设为 MIGO_ABI_VERSION_CURRENT。 |
runtime_generation |
uint64_t |
生产者所在的运行时代标识。 |
surface_generation |
uint64_t |
生产者引用的 surface 代标识。 |
resource_epoch |
uint64_t |
生产者引用的资源 epoch。 |
triggering_sequence |
uint64_t |
生产者阻塞时已提交的最后一帧序列号。 |
deadline_nanos |
uint64_t |
单调时钟读数(非墙上时钟),超时后库返回 TIMED_OUT。 |
operation |
uint32_t |
要执行的同步操作类型(由协议层定义)。 |
max_reply_bytes |
uint32_t |
生产者预留的回复缓冲区大小;回复超出此值将失败而非截断。 |
MigoSyncOutcome
Section titled “MigoSyncOutcome”typedef struct MigoSyncOutcome { uint32_t struct_size; uint32_t abi_version; uint32_t request_id; MigoSyncState state; uint32_t reply_bytes; MigoSyncError error;} MigoSyncOutcome;库写入,append-only。request_id 单调递增且永不为零;清零的邮箱持有零值,因此可以通过检查此字段判断是否有新结果。
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
调用方输入,限定写入范围。 |
abi_version |
uint32_t |
调用方输入。 |
request_id |
uint32_t |
本次请求的单调 ID;非零。 |
state |
MigoSyncState |
当前请求状态。 |
reply_bytes |
uint32_t |
仅 READY 时非零;实际回复字节数。 |
error |
MigoSyncError |
仅 FAILED 时非零;失败原因。 |
大型资产(纹理图集等)不通过帧包传输:帧包有大小上限,而纹理没有。资源通道允许生产者预留槽位 → 分块上传 → 等待摘要验证通过 → 帧中引用。验证在 GPU 对象创建之前完成,避免将摘要错误变成已绑定的脏纹理。
MigoResourceState
Section titled “MigoResourceState”typedef uint32_t MigoResourceState;
#define MIGO_RESOURCE_STATE_RESERVED 0U#define MIGO_RESOURCE_STATE_UPLOADING 1U#define MIGO_RESOURCE_STATE_VERIFYING 2U#define MIGO_RESOURCE_STATE_READY 3U#define MIGO_RESOURCE_STATE_FAILED 4U| 值 | 含义 |
|---|---|
RESERVED |
槽位已分配,尚未开始上传。 |
UPLOADING |
正在接收数据块。 |
VERIFYING |
所有块已接收,正在核验 SHA-256 摘要。 |
READY |
已验证通过;帧包方可引用此资源。 |
FAILED |
验证失败或超时,error 字段说明原因。 |
MigoResourceError
Section titled “MigoResourceError”typedef uint32_t MigoResourceError;
#define MIGO_RESOURCE_ERROR_TOO_MANY_RESERVATIONS 1U#define MIGO_RESOURCE_ERROR_BAD_SIZE 2U#define MIGO_RESOURCE_ERROR_BAD_CHUNK_COUNT 3U#define MIGO_RESOURCE_ERROR_UNKNOWN_RESERVATION 4U#define MIGO_RESOURCE_ERROR_NON_CONTIGUOUS_CHUNK 5U#define MIGO_RESOURCE_ERROR_CHUNK_OUT_OF_BOUNDS 6U#define MIGO_RESOURCE_ERROR_DIGEST_MISMATCH 7U#define MIGO_RESOURCE_ERROR_TIMED_OUT 8U#define MIGO_RESOURCE_ERROR_EPOCH_ADVANCED 9U#define MIGO_RESOURCE_ERROR_INCOMPLETE 10U#define MIGO_RESOURCE_ERROR_NOT_UPLOADING 11U| 值 | 含义 |
|---|---|
TOO_MANY_RESERVATIONS |
已达到同时存在的预留数量上限。 |
BAD_SIZE |
total_bytes 为零或超出允许范围。 |
BAD_CHUNK_COUNT |
chunk_count 为零或超出允许范围。 |
UNKNOWN_RESERVATION |
reservation_id 在表中不存在。 |
NON_CONTIGUOUS_CHUNK |
上传的块编号不连续。 |
CHUNK_OUT_OF_BOUNDS |
块偏移 + 长度超出 total_bytes。 |
DIGEST_MISMATCH |
所有块已到达,但 SHA-256 与声明不符。 |
TIMED_OUT |
在 deadline_nanos 前未完成上传。 |
EPOCH_ADVANCED |
资源 epoch 已推进,此预留已失效。 |
INCOMPLETE |
验证时块尚未全部到达。 |
NOT_UPLOADING |
对不处于 UPLOADING 状态的预留发送了块。 |
MigoResourceReservationDescriptor
Section titled “MigoResourceReservationDescriptor”typedef struct MigoResourceReservationDescriptor { uint32_t struct_size; uint32_t abi_version; uint64_t total_bytes; uint64_t deadline_nanos; uint32_t chunk_count; uint32_t format; uint8_t sha256[32];} MigoResourceReservationDescriptor;调用方写入。reservation_id 由库分配(写入 MigoResourceOutcome),不由调用方指定,以避免生产者选择的 ID 与已有预留碰撞。
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
设为 sizeof(MigoResourceReservationDescriptor)。 |
abi_version |
uint32_t |
设为 MIGO_ABI_VERSION_CURRENT。 |
total_bytes |
uint64_t |
资源总字节数;必须与实际上传数据匹配。 |
deadline_nanos |
uint64_t |
单调时钟截止时间;超时后预留失效。 |
chunk_count |
uint32_t |
预期块数;上传必须按顺序且连续。 |
format |
uint32_t |
生产者声明的格式标签;对协议不透明,原样传递给渲染器。 |
sha256[32] |
uint8_t[32] |
所有块合并后应有的 SHA-256 摘要;验证失败则整个预留失效。 |
MigoResourceOutcome
Section titled “MigoResourceOutcome”typedef struct MigoResourceOutcome { uint32_t struct_size; uint32_t abi_version; uint64_t reservation_id; uint64_t received_bytes; MigoResourceState state; MigoResourceError error; uint32_t next_chunk; uint32_t reserved0;} MigoResourceOutcome;库写入,append-only。
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
调用方输入,限定写入范围。 |
abi_version |
uint32_t |
调用方输入。 |
reservation_id |
uint64_t |
库分配的预留标识;非零表示预留已建立。 |
received_bytes |
uint64_t |
已成功接收的字节数。 |
state |
MigoResourceState |
当前预留状态。 |
error |
MigoResourceError |
仅 FAILED 时非零。 |
next_chunk |
uint32_t |
下一次上传必须携带的块编号;块必须连续。 |
reserved0 |
uint32_t |
保留,忽略。 |
MigoExternalSessionDescriptor
Section titled “MigoExternalSessionDescriptor”typedef struct MigoExternalSessionDescriptor { uint32_t struct_size; uint32_t abi_version; uint8_t launch_nonce[16]; uint32_t max_packet_bytes; uint32_t max_credits;} MigoExternalSessionDescriptor;创建外部帧 session 的配置描述符。与普通 MigoSessionConfig 路径不同——外部帧 session 没有脚本运行时,生命周期模型也不同;用此 descriptor 创建的 session 调用 migo_session_load_content 会返回 MIGO_ERROR_INVALID_STATE。
参数:
| 字段 | 类型 | 含义 |
|---|---|---|
struct_size |
uint32_t |
设为 sizeof(MigoExternalSessionDescriptor)。 |
abi_version |
uint32_t |
设为 MIGO_ABI_VERSION_CURRENT。 |
launch_nonce[16] |
uint8_t[16] |
128 位小端序随机密钥,由宿主用加密随机源生成。全零值被拒绝——全零是未初始化结构体的自然值,接受它等于接受任何人发来的零字节。不得出现在日志或 URL 中。 |
max_packet_bytes |
uint32_t |
单包最大字节数,0 表示使用库的默认上限。超出上限的值向下截断,不向上扩展——内存受限设备可以要求更小的包,但任何人都不能要求更大的包。 |
max_credits |
uint32_t |
生产者可同时拥有的最大飞行帧数,0 表示使用库默认值。同样向下截断到编译期范围。 |
migo_session_submit_external_frame
Section titled “migo_session_submit_external_frame”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_submit_external_frame( MigoSession *session, const uint8_t *bytes, size_t byte_count, MigoFrameIngressOutcome *out_outcome);将跨进程生产的帧包提交给 session。bytes 仅在调用期间借用:库在返回前将被接受的包复制到自有缓冲区,因此 Swift 传输层可以立即释放或重用其 Data 的内部指针。out_outcome 由调用方分配和拥有,调用前必须填写 struct_size 和 abi_version;建议初始化一次后跨帧复用,而非每帧重建。
参数:
| 参数 | 说明 |
|---|---|
session |
目标 session 句柄,不可为空。 |
bytes |
帧包字节,调用期间借用。 |
byte_count |
帧包长度(字节)。 |
out_outcome |
调用方分配的结果结构体;struct_size 和 abi_version 必须预填。 |
返回:
MIGO_OK:outcome 已写入——包括 outcome 内部显示被拒绝的情况;一个非 OK 的 MigoResult 意味着调用本身无法完成。MIGO_ERROR_INVALID_ARGUMENT:session为空、bytes为空、out_outcome的头部字段未填写。MIGO_ERROR_INVALID_STATE:session 已销毁,或尚未 attach surface。
线程模型:
可在传输线程调用;库保证被接受的包在返回前完成内部拷贝,调用方无需保持 bytes 的生命周期。
migo_session_request_external_frame
Section titled “migo_session_request_external_frame”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_request_external_frame(MigoSession *session);向外部生产者请求渲染一帧。外部帧模式中,生产者仅在被驱动时渲染——Migo 的 requestAnimationFrame 在所有平台上都由宿主 vsync 驱动,此入口点是该信号跨进程边界的等价物。宿主收到 vsync 回调后调用此函数,生产者进程接收到信号后渲染并通过 migo_session_submit_external_frame 回传帧包。
参数:
| 参数 | 说明 |
|---|---|
session |
目标 session 句柄,不可为空。 |
返回:
MIGO_OK:请求已记下。多个请求合并为一帧;渲染器尚未就绪时请求被锁存,渲染器起来时立即武装,不会丢。MIGO_ERROR_INVALID_STATE:尚未 attach surface(此时没有帧时钟)。MIGO_ERROR_INVALID_ARGUMENT:session为空。
生产方自己的请求走 socket 上的控制消息(见 migo_session_submit_uplink_control);此入口供宿主代生产方请求。
线程模型: 任意线程可调用,不阻塞。
migo_uplink_message_kind
Section titled “migo_uplink_message_kind”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_uplink_message_kind( const uint8_t *bytes, size_t byte_count, MigoUplinkMessageKind *out_kind);生产方的 socket 上有三种消息:帧包、控制消息(目前只有“请求下一帧”)和服务消息。传输层问库这条消息该进哪扇门,而不是自己比较 magic——宿主自己编码这条规则,就是 wire 格式又多一份会漂移的实现。
MIGO_UPLINK_MESSAGE_CONTROL:交给migo_session_submit_uplink_control。MIGO_UPLINK_MESSAGE_SERVICE:交给migo_session_submit_service。MIGO_UPLINK_MESSAGE_FRAME:交给migo_session_submit_external_frame。凡不是控制消息或服务消息的都是这个,包括连帧也不是的字节——由帧 ingress 以生产方看得到的判决拒绝它。多一个“未知”答案就需要多一个上报它的地方。
不需要 session,最多读前 4 个字节。byte_count 为 0 时 bytes 可为 NULL。走内容源(custom scheme)上行的消息一律是帧,无需询问。
返回:out_kind 为空,或 bytes 为空而 byte_count 非 0,返回 MIGO_ERROR_INVALID_ARGUMENT;否则 MIGO_OK。
线程模型:无状态,任意线程。
migo_session_submit_uplink_control
Section titled “migo_session_submit_uplink_control”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_submit_uplink_control( MigoSession *session, const uint8_t *bytes, size_t byte_count, uint32_t *out_refusal_code);读取一条控制消息并执行。Migo 的 requestAnimationFrame 在所有平台上都由宿主 vsync 驱动,生产方对下一帧的需求要跨进程到达这里,帧钟 tick 才会产生。格式见 contracts/frame-wire/wire-v1.md 的 Uplink control messages。
规则:
- 整条先校验再执行:任何一处不合法,整条消息都不生效。
*out_refusal_code为 0 表示已读取——包括其中的请求属于别的运行时代际(忽略而非拒绝:发出它的生产方已经不在了);非 0 为 3001 起的拒绝码(见 wire 契约的 Control refusals),按拒绝帧的方式上报。- 渲染器尚未就绪时的帧请求被锁存,渲染器起来时武装——生产方的第一次请求与宿主启动赛跑,没有任何东西会让它再问一次。
- 请求合并:一次 tick 回应之前的所有请求。
返回:
MIGO_OK:*out_refusal_code已写入。MIGO_ERROR_INVALID_ARGUMENT:session、bytes或out_refusal_code为空,或byte_count为 0。MIGO_ERROR_INVALID_STATE:尚未 attach surface。
线程模型:在传输线程调用,与 migo_session_submit_external_frame 相同;不阻塞。
migo_session_take_external_gl_error
Section titled “migo_session_take_external_gl_error”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_take_external_gl_error( MigoSession *session, uint32_t canvas_id, uint32_t *out_code);从指定 canvas 的错误队列中取出一条待处理的 WebGL 错误码。gl.getError() 是一个同步调用,生产者在另一个进程中发起;解码器在宿主进程记录错误,此入口点让生产者按需取回。队列为空时写入 0(GL_NO_ERROR),与 WebGL 规范一致。每次调用只消耗一条错误,生产者需循环调用直到返回 GL_NO_ERROR。
参数:
| 参数 | 说明 |
|---|---|
session |
目标 session 句柄,不可为空。 |
canvas_id |
目标 canvas 的标识符;由协议层分配,对 C ABI 不透明。 |
out_code |
调用方分配的 uint32_t;队列为空时写入 0。 |
返回:
MIGO_OK:out_code已写入(可能为 0)。MIGO_ERROR_INVALID_ARGUMENT:任一指针为空,或canvas_id无效。MIGO_ERROR_INVALID_STATE:session 已销毁。
migo_session_post_sync_request
Section titled “migo_session_post_sync_request”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_post_sync_request( MigoSession *session, const MigoSyncRequestDescriptor *request, const uint8_t *params, size_t param_bytes, uint64_t now_nanos, MigoSyncOutcome *out_outcome);投递一条同步屏障请求并就地应答:生产方正阻塞等待,传输层把请求带到这里,本函数完成应答,字节再由 migo_session_take_sync_reply 取回。每个 session 同时只允许一条未完成请求;第二条被以 MIGO_SYNC_ERROR_ALREADY_PENDING 拒绝。
参数:
| 参数 | 说明 |
|---|---|
session |
目标 session 句柄,不可为空。 |
request |
请求描述结构体:调用方所写,struct_size 和 abi_version 同样必填,未初始化的头部在判请求之前就被拒。 |
params |
操作参数字节;仅当 param_bytes 为 0 时才可为空。布局按上方各操作定义(如 readPixels 的 32 字节记录)。 |
param_bytes |
参数字节数。 |
now_nanos |
调用方的单调时钟读数,与 deadline_nanos 必须同源;库自身不读钟——两个今天一致的钟是平台的隐患,只有宿主才知道生产方正阻塞在哪个时钟上。 |
out_outcome |
调用方分配,头部(struct_size/abi_version)是输入:全零的头部会以 MIGO_ERROR_UNSUPPORTED_ABI 被拒,而此时生产方正在阻塞等待——等于让它把整段 deadline 耗在一条永远送不到的拒绝上。 |
返回:
MIGO_OK:应答已就座——包括应答内部报MIGO_SYNC_STATE_FAILED的情况;产出方在被拒未得上进入请求 id 前看到request_id为 0。MIGO_ERROR_INVALID_ARGUMENT:指针为空、头部字段未填。MIGO_ERROR_UNSUPPORTED_ABI:abi_version不识或struct_size声称了本构建不认识的更大的记录。
线程模型:
会阻塞到请求结算:先等它点名的那一帧被接纳,再等读回完成。库在 session 锁里取出同步句柄、释放锁后才阻塞,因为它等的那一帧要经 migo_session_submit_external_frame 进来,而那个入口需要这把锁。所以要在允许阻塞的线程上调用,且调用时不能持有帧提交路径需要的任何锁。返回的 outcome 是这条请求自己的结算结果,不是返回时邮箱里恰好放着的那条。
migo_session_call_sync
Section titled “migo_session_call_sync”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_call_sync( MigoSession *session, const uint8_t *call, size_t call_bytes, uint64_t now_nanos, uint8_t *header, size_t header_capacity, MigoSyncReply **out_reply);用一个请求体进、一个应答体出的方式完成同步调用,给无法和宿主共享内存的生产方用。Apple 上内容 origin 是自定义 scheme,WebKit 在那里不提供 SharedArrayBuffer,所以 Worker 用同步请求阻塞。传输层把请求体原样交给这里,再把这里写出的应答头、后面接上回复字节,原样作为一个响应体发回。两种布局都由 wire 格式定义(contracts/frame-wire/wire-v1.md 的 “A request as one body” 和 “An answer as one body”),传输层两个都不解析。
请求体里带的是生产方愿意等多久,不是 deadline,因为生产方的时钟不是宿主的。deadline = now_nanos + 这段等待时间,所以 now_nanos 和 migo_session_post_sync_request 一样,是调用方的单调时钟读数。
回复不经拷贝:*out_reply 拿到的是渲染器自己分配的那块缓冲,所有权直接移交,库里不再有第二份。一次读回可能是设备分辨率下的整屏,而这条路径上生产方正阻塞着。用 migo_sync_reply_bytes 取地址和长度,字节发出去之后用 migo_sync_reply_release 释放。
应答在结算这条请求的同一把锁下读出,回复来自这条请求自己的读回,同一步里释放槽位。没有单独的 take 步骤,也就不存在把别的请求的应答当成这条取走的窗口。
参数:
| 参数 | 说明 |
|---|---|
session |
目标 session 句柄,不可为空。 |
call / call_bytes |
请求体,原样来自生产方;超过 MIGO_SYNC_CALL_MAX_BYTES 的请求体应在传输层读之前就拒绝。 |
now_nanos |
调用方的单调时钟读数。 |
header / header_capacity |
应答头写到这里,至少 MIGO_SYNC_ANSWER_HEADER_BYTES 字节;不够时在投递之前就被拒,不会白做一次读回。 |
out_reply |
带字节应答时拿到回复句柄,其他情况(包括所有失败,结论在应答头里)写 NULL。 |
返回:
MIGO_OK:应答头已写出。请求失败、解析不了也算在内:生产方无论如何都在等响应,结论应该写在响应里。MIGO_ERROR_INVALID_ARGUMENT:指针为空,或header_capacity不够。*out_reply为NULL。MIGO_ERROR_INVALID_STATE:session 还没有渲染器(未 attach 表面)或已销毁。不写应答头。
线程模型:
与 migo_session_post_sync_request 相同:会阻塞,释放 session 锁后才阻塞;不要在持有帧提交路径所需锁的线程上调用。一个生产方同一时间只有一个 agent 在阻塞,所以一个串行队列就够了;但这个队列不能和帧上行共用,否则读等的那一帧会排在读后面。
migo_sync_reply_bytes
Section titled “migo_sync_reply_bytes”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_sync_reply_bytes( const MigoSyncReply *reply, const uint8_t **out_bytes, size_t *out_length);回复字节的地址和长度,在释放之前一直有效。失败时 *out_bytes 写 NULL、*out_length 写 0。
migo_sync_reply_release
Section titled “migo_sync_reply_release”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_sync_reply_release(MigoSyncReply *reply);释放回复,之后句柄和字节都失效。传 NULL 以 MIGO_ERROR_INVALID_ARGUMENT 拒绝而不是忽略:释放一个从没收到过的回复,说明调用方已经分不清哪些应答带了字节。
migo_session_poll_sync
Section titled “migo_session_poll_sync”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_poll_sync( MigoSession *session, uint64_t now_nanos, MigoSyncOutcome *out_outcome);查询未完成请求所处状态。同时它也是期限可见性的唯一路径:一条请求未完成期间没有别的东西在跑,已过期的 deadline 在这里结算。now_nanos 与 out_outcome 的语义与 migo_session_post_sync_request 完全相同——含“全零头部先被拒,生产方白等整段 deadline”这一条。
返回集合同上(MIGO_OK / MIGO_ERROR_INVALID_ARGUMENT / MIGO_ERROR_UNSUPPORTED_ABI)。
线程模型:与 session 串行;可在传输层的 regular tick 上调用。
migo_session_take_sync_reply
Section titled “migo_session_take_sync_reply”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_take_sync_reply( MigoSession *session, uint8_t *buffer, size_t capacity, size_t *out_written);把就绪的应答字节拷贝出来并释放槽位。永不截断:容量小于应答时整体拒绝且应答保持 MIGO_SYNC_STATE_READY,调用方换上更大的缓冲再来即可。*out_written 成功时为实际字节数,失败时写 0——因此调用方忽略返回值时不会把上一个陈旧的计数当长度用。
返回:
MIGO_OK:字节已拷贝,槽位已空出(可以投递下一条请求)。MIGO_ERROR_INVALID_STATE:当前没有 READY 的应答可取。MIGO_ERROR_INVALID_ARGUMENT:指针为空。
线程模型:与 session 串行;通常紧跟在 poll_sync 报告 MIGO_SYNC_STATE_READY 之后。
migo_session_cancel_sync
Section titled “migo_session_cancel_sync”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_cancel_sync( MigoSession *session, uint64_t now_nanos, MigoSyncOutcome *out_outcome);生产方撤回了请求:把未完成请求结算为 MIGO_SYNC_STATE_CANCELLED 并释放槽位。已完成的应答不受影响——cancel 不是丢掉一个还没被读的应答的手段。out_outcome 的头部规则与其他 sync 入口一致。
线程模型:与 session 串行。
migo_session_take_downlink
Section titled “migo_session_take_downlink”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_take_downlink( MigoSession *session, uint8_t *buffer, size_t capacity, size_t *out_written);取宿主欠生产者的下一条消息。字节是一个下行信封,承载两类记录:对每个已提交帧的判决(decision、剩余 credit、已接受序列号)和驱动生产方 requestAnimationFrame 的帧钟 tick。宿主不要自己拼装信封——同一 wire 格式的第三份实现就是这个项目设置门要防的漂移;纯拷贝字节的传输层不会漂移。
规则:
- 整条整出:容量不足以装信封加一条记录时,一个字节也不写、队列原样保留;换上更大缓冲再调不丢任何东西。4096 字节足以装下该队列产出的任何消息。
out_written为 0 是正常答案,不是错误,也不要空转:帧与帧之间的空隙里就是“没有东西要发”。- 何时调用:每次 submit 之后,以及下行唤醒回调(
migo_session_set_downlink_waker)触发之后;队列有界且会合并 tick,传输层落后的代价是生产方调度延迟,不是内存——每条记录都是绝对的,下一条读到的就是当前正确值。 - tick 携带窗口:
(remaining_credits, accepted_sequence)。判决只为帧而发,一个最后判决为 0 的生产方只能从它请求的 tick 得知 credit 已归还。
返回:指针为空为 MIGO_ERROR_INVALID_ARGUMENT;其余合法调用均为 MIGO_OK(含“没东西要发”)。
线程模型:与 session 串行;适合放在传输发送线程上。
migo_session_set_downlink_waker
Section titled “migo_session_set_downlink_waker”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */typedef void (MIGO_CALL *MigoDownlinkWakerFn)(void *user_data);
MigoResult migo_session_set_downlink_waker( MigoSession *session, MigoDownlinkWakerFn waker, void *user_data);安装(或以 NULL 清除)下行唤醒回调。库排入一条不是传输层引起的下行记录——帧钟 tick——时调用它。判决是在传输层自己的 submit 里排入的,submit 之后本来就会排空;tick 不是,没有唤醒它就会一直等到生产方发点什么,而一个在等 tick 的生产方什么也不会发。
规则:
- 在 session 自己的线程上调用。只调度排空,不要在回调里执行排空:立即返回,不要在回调里回调库——尤其不要调用
migo_session_set_downlink_waker,它会等正在进行的回调返回。 - 每个 session 一个回调,再次安装即替换。清除在任何进行中的调用返回后才返回,所以返回后即可释放
user_data。 - 在
migo_session_destroy之前清除。
返回:
MIGO_OK:已安装或清除。MIGO_ERROR_INVALID_ARGUMENT:session为空。MIGO_ERROR_INVALID_STATE:尚未 attach surface。
线程模型:任意线程调用;回调在 session 线程上被调用。
migo_owned_bytes_view
Section titled “migo_owned_bytes_view”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */typedef struct MigoOwnedBytes MigoOwnedBytes;
MigoResult migo_owned_bytes_view( const MigoOwnedBytes *owned, const uint8_t **out_bytes, size_t *out_length);MigoOwnedBytes 是库交给调用方、在 migo_owned_bytes_release 之前一直由库持有的字节。服务应答可能是一整个文件,所以是移交而不是拷贝进调用方缓冲区。本函数给出字节的位置和长度,在释放之前有效。
返回:
MIGO_OK:*out_bytes、*out_length已写入。MIGO_ERROR_INVALID_ARGUMENT:任一指针为空;此时两个输出(若可写)分别被置为NULL和 0。
线程模型:无状态,任意线程;同一句柄不得与 migo_owned_bytes_release 并发。
migo_owned_bytes_release
Section titled “migo_owned_bytes_release”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_owned_bytes_release(MigoOwnedBytes *owned);释放字节,句柄随即失效。每个句柄恰好释放一次。
返回:owned 为空返回 MIGO_ERROR_INVALID_ARGUMENT;否则 MIGO_OK。
线程模型:任意线程。
migo_session_submit_service
Section titled “migo_session_submit_service”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */#define MIGO_SERVICE_MESSAGE_MAX_BYTES 67108864U
MigoResult migo_session_submit_service( MigoSession *session, const uint8_t *bytes, size_t byte_count, uint32_t *out_refusal_code);准入一条服务消息。服务流承载内容请求宿主做的、绘制以外的一切:读文件、写存档、加载图片、播放声音、打开 socket。消息带序号、严格按序准入,应答走自己的返回流。格式见 contracts/frame-wire/wire-v1.md 的 The service stream;传输层不解析其中任何内容。
传输:
- 不超过 65536 字节的消息走生产方 socket(
migo_uplink_message_kind答MIGO_UPLINK_MESSAGE_SERVICE);更大的消息以 POST 发到内容源的服务端点。读取 POST 的传输层应在读之前拒绝超过MIGO_SERVICE_MESSAGE_MAX_BYTES的消息。 - 两条路径互相乱序:先到的后续消息被暂存(至多 256 条、合计不超过
MIGO_SERVICE_MESSAGE_MAX_BYTES),等前一条到达后一起准入。无论哪种情况调用都立即返回,POST 可以马上应答。
规则:
bytes仅在调用期间借用;被准入的记录在返回前已拷贝。*out_refusal_code为 0 表示已准入、已暂存,或因属于别的运行时代际而被忽略;否则为 4001 起的拒绝码(见 wire 契约的 Service refusals)。拒绝同时经返回流告知生产方:服务流从此中断。- 会话已准入工作的队列满时阻塞,以此对 socket 施加背压;请在允许阻塞的线程上调用。
返回:
MIGO_OK:*out_refusal_code已写入。MIGO_ERROR_INVALID_ARGUMENT:session、bytes或out_refusal_code为空,或byte_count为 0。MIGO_ERROR_INVALID_STATE:尚未 attach surface,或 session 已结束。
线程模型:传输线程(socket 读线程或内容源的请求线程);可能阻塞。
migo_session_take_service_message
Section titled “migo_session_take_service_message”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_take_service_message( MigoSession *session, MigoOwnedBytes **out_message);取宿主欠生产方的下一条应答与事件消息。*out_message 为 NULL 表示没有排队的消息——这是正常答案,不是错误。否则把字节原样发到 socket,然后用 migo_owned_bytes_release 释放。
在所有调用 migo_session_take_downlink 的地方也调用它:下行唤醒回调对两者都会触发。
返回:
MIGO_OK:*out_message已写入(可能为NULL)。MIGO_ERROR_INVALID_ARGUMENT:session或out_message为空。MIGO_ERROR_INVALID_STATE:尚未 attach surface。
线程模型:与 migo_session_take_downlink 相同,适合放在传输发送线程上。
migo_session_take_parked_reply
Section titled “migo_session_take_parked_reply”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_take_parked_reply( MigoSession *session, uint32_t generation, uint32_t request_id, MigoOwnedBytes **out_reply);取一条停放的应答。大到不能随返回流内联发送的应答会被停放,生产方按代际和请求 id 向内容源索取,内容源用本函数取出。只能取一次:库在交出这份时释放自己的副本。
*out_reply 为 NULL 表示没有这条应答——生产方重复索取,或索取别的代际的应答;请以 404 回应该请求。
返回:
MIGO_OK:*out_reply已写入(可能为NULL)。MIGO_ERROR_INVALID_ARGUMENT:session或out_reply为空。MIGO_ERROR_INVALID_STATE:尚未 attach surface。
线程模型:内容源的请求线程;不阻塞。
migo_session_copy_content_root
Section titled “migo_session_copy_content_root”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */MigoResult migo_session_copy_content_root( MigoSession *session, char *buffer, size_t capacity, size_t *out_length);已加载内容的代码所在目录,即宿主在内容源根路径上提供的目录,以 NUL 结尾的 UTF-8 写出。
在外部帧执行中,migo_session_load_content 返回前就已挂载内容——入口模块由另一个进程里的 WebKit 求值,那个进程的宿主需要这个目录来提供它——所以该调用成功后即可取得根目录。
返回:
MIGO_OK:已写入;*out_length为不含 NUL 的长度。MIGO_ERROR_INVALID_ARGUMENT:out_length为空、buffer为空而capacity非 0,或capacity装不下路径加 NUL。容量不足时*out_length仍被写入,调用方可按它重试。MIGO_ERROR_INVALID_STATE:尚未加载内容。MIGO_ERROR_INTERNAL:路径无法以 UTF-8 表示(拒绝而不是有损改写)。
线程模型:任意线程。
migo_session_read_content_module
Section titled “migo_session_read_content_module”/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 */#define MIGO_CONTENT_MODULE_SERVED 0U /* 字节是模块源码 */#define MIGO_CONTENT_MODULE_NOT_FOUND 1U /* 字节是原因:回 404 */#define MIGO_CONTENT_MODULE_REFUSED 2U /* 字节是原因:回 403 */#define MIGO_CONTENT_MODULE_UNREADABLE 3U /* 字节是原因:回 500 */
MigoResult migo_session_read_content_module( MigoSession *session, const char *path, size_t path_length, MigoOwnedBytes **out_module, uint32_t *out_status);内容源上 path(如 "/game.js")处内容模块的源码,与引擎求值的完全相同:经挂载的包解析(含分包覆盖层与打包格式的包)、限定在包内、必须是 UTF-8,并施加模块加载器唯一的一次改写——CommonJS 入口因此与内嵌运行时一样被包装后运行。内容源对游戏的每个脚本请求都用它应答,而不是直接回文件。
规则:
path是借用的path_length字节 UTF-8。*out_module收到字节——源码,或未提供的原因,由*out_status区分——调用方用migo_owned_bytes_release释放。- 在调用线程上读文件。
返回:
MIGO_OK:*out_module与*out_status已写入。MIGO_ERROR_INVALID_ARGUMENT:指针为空、path_length为 0,或path不是 UTF-8。MIGO_ERROR_INVALID_STATE:尚未加载内容。
线程模型:内容源的请求线程;会做文件 IO,不要在主线程调用。
结构体初始化约定
Section titled “结构体初始化约定”所有描述符结构体在填写前必须清零,然后设置 struct_size 和 abi_version:
/* 衍生示例:tests/c_host 暂无该路径覆盖;调用形态以头文件为准 *//* 帧注入结果结构体:初始化一次,跨帧复用 */MigoFrameIngressOutcome outcome = {0};outcome.struct_size = sizeof(outcome);outcome.abi_version = MIGO_ABI_VERSION_CURRENT;
while (has_frame()) { const uint8_t *pkt = next_packet(&pkt_len); MigoResult r = migo_session_submit_external_frame( session, pkt, pkt_len, &outcome); if (r != MIGO_OK) { /* 调用失败,非帧级拒绝 */ break; }
switch (outcome.decision) { case MIGO_FRAME_INGRESS_ACCEPTED: /* outcome.accepted_sequence 可用于追踪完成通知 */ break; case MIGO_FRAME_INGRESS_WOULD_BLOCK: /* 背压:等待信用恢复后重试,不可丢弃此帧 */ wait_for_credit(); break; case MIGO_FRAME_INGRESS_REJECTED: /* 包格式错误,记录 outcome.wire_error_code */ break; case MIGO_FRAME_INGRESS_GENERATION_LOST: /* 运行时代已消失,无需重试 */ break; }}所有权规则:
MigoFrameIngressOutcome、MigoSyncOutcome、MigoResourceOutcome由调用方分配,库只写入,不持有。MigoSyncRequestDescriptor、MigoResourceReservationDescriptor、MigoExternalSessionDescriptor由调用方填写并在调用期间借用,调用返回后可释放。migo_session_submit_external_frame的bytes指针仅在调用期间借用,返回前库已完成拷贝。MigoOwnedBytes由库分配并移交给调用方,调用方恰好用migo_owned_bytes_release释放一次。
- session.mdx —
MigoSessionConfig.launch_nonce及 session 生命周期 - surface.mdx — surface attach/detach 与 vsync 驱动
- engine.mdx — engine 创建与能力查询