Engine 初始化
MigoEngineConfig
Section titled “MigoEngineConfig”typedef struct MigoEngineConfig { uint32_t struct_size; uint32_t abi_version; MigoEngineFlags flags; uint32_t reserved0; const char *files_dir_utf8; const char *cache_dir_utf8; const char *code_cache_dir_utf8; uint8_t code_signing_public_key[32];} MigoEngineConfig;引擎创建参数。调用方用 memset 将整个结构体清零,再逐字段赋值;reserved0 保持零即可。三个字符串字段在 migo_engine_create 返回前已被引擎内部复制,调用结束后可以释放或回收。
字段:
| 字段 | 类型 | 语义 | 所有权 |
|---|---|---|---|
struct_size |
uint32_t |
结构体字节大小,传 (uint32_t)sizeof(MigoEngineConfig);引擎据此做向前兼容扩展 |
— |
abi_version |
uint32_t |
ABI 版本,传 MIGO_ABI_VERSION_CURRENT;与库版本不一致时 migo_engine_create 返回 MIGO_ERROR_UNSUPPORTED_ABI |
— |
flags |
MigoEngineFlags |
引擎行为标志,见 MigoEngineFlags;无特殊需求传 MIGO_ENGINE_FLAG_NONE |
— |
files_dir_utf8 |
const char * |
持久文件根目录(NUL 结尾 UTF-8);对应平台的应用文档目录(Android getFilesDir()、iOS NSDocumentDirectory);目录不存在时由引擎自动创建 |
borrowed,create 返回前复制 |
cache_dir_utf8 |
const char * |
可清除缓存根目录(NUL 结尾 UTF-8);操作系统低存储时可能被回收;目录不存在时自动创建 | borrowed,create 返回前复制 |
code_cache_dir_utf8 |
const char * |
编译字节码缓存根目录(NUL 结尾 UTF-8);同一 Engine 下的所有 Session 共享此目录,跨 Session LRU 淘汰;目录不存在时自动创建 | borrowed,create 返回前复制 |
code_signing_public_key |
uint8_t[32] |
内容签名校验用的 Ed25519 公钥(32 字节原始值);全零表示没有。v1 之后追加在末尾:传旧的较短 struct_size 的宿主会被零扩展,即“没有公钥” |
按值复制 |
三个存储根均属于宿主:引擎只在这些目录内读写,从不自行选择路径。每个 Session 的实际数据位于 <root>/migo/games/<content_id>/,因此只要 content_id 不同,多个 Session 的数据天然隔离。
MigoEngineFlags
Section titled “MigoEngineFlags”typedef uint64_t MigoEngineFlags;#define MIGO_ENGINE_FLAG_NONE 0ULL#define MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT (1ULL << 0)MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT 允许加载没有签名收据的内容包。这是显式 opt-in,而非默认行为——签名检查存在的目的正是防止静默接受未签名内容,因此默认应为拒绝。
清除此标志即启用签名强制,此时由 code_signing_public_key 提供校验公钥。签名的内容包带两个文件:manifest.json({"version": 1, "entry": "game.js", "timestamp": <Unix 秒>, "files": {"<路径>": "<sha256 十六进制>", ...}})与 manifest.sig(对 manifest.json 原始字节的 64 字节 Ed25519 签名)。引擎在内容首次启动时完整校验并封存结果,之后的启动不再重新哈希。
强制签名是失败关闭(fail-closed)的:既没有设置此标志、也没有提供公钥时,每次模块加载都会以 MIGO_ERROR_INTERNAL 终止,并在日志中打印:
code signing enabled but public key is missing(set InitOptions.code_signing_pubkey (hex Ed25519 public key))同时设置此标志与公钥是自相矛盾的配置,migo_engine_create 以 MIGO_ERROR_INVALID_ARGUMENT 拒绝。
migo_engine_create
Section titled “migo_engine_create”MIGO_API MigoResult MIGO_CALLmigo_engine_create(const MigoEngineConfig *config, MigoEngine **out_engine);从宿主提供的配置创建一个 Engine 实例。成功时 *out_engine 是一个有效句柄,调用方最终必须将其传给 migo_engine_destroy。引擎在任何可能失败的操作之前先将 *out_engine 置为 NULL,因此无论返回何种结果,读取该指针都是安全的。
参数:
| 名字 | 说明 |
|---|---|
config |
初始化好的 MigoEngineConfig 指针;不得为 NULL,struct_size 和 abi_version 必须有效 |
out_engine |
接收新建 Engine 句柄的指针;不得为 NULL |
返回:
MIGO_OK:Engine 创建成功,*out_engine有效。MIGO_ERROR_INVALID_ARGUMENT:out_engine或config为NULL;config->struct_size小于最小合法记录大小;字符串字段为NULL;flags包含未识别的位;同时设置了MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT与非零的code_signing_public_key。失败时*out_engine为NULL。MIGO_ERROR_UNSUPPORTED_ABI:config->abi_version与当前库构建版本不匹配,或struct_size声明的记录大于本构建所了解的大小。失败时*out_engine为NULL。MIGO_ERROR_INTERNAL:某个存储根目录不存在且文件系统拒绝创建(权限不足、只读挂载点、路径组件非目录)——路径字符串本身格式正确,因此这不属于参数错误。失败时*out_engine为NULL。
线程模型:
migo_engine_create 与 migo_session_create 可以从不同宿主线程并发调用。
migo_engine_destroy
Section titled “migo_engine_destroy”MIGO_API MigoResult MIGO_CALL migo_engine_destroy(MigoEngine *engine);销毁 Engine 并释放其所有资源。返回 MIGO_OK 后,engine 指针失效,不得再使用。
调用前,宿主必须已销毁该 Engine 拥有的所有子 Session;存在活跃 Session 时调用返回 MIGO_ERROR_INVALID_STATE,Engine 不被消耗。
线程完成屏障:成功的 migo_engine_destroy 是一个线程完成屏障——在其返回之前,所有由 Session 移交给 Engine 的工作线程(engine worker)都已退出并被 join。只有在此函数返回后,宿主才可以安全地销毁原生显示/窗口资源,或卸载 Migo 库。
若从某个 engine worker 线程内部调用此函数(例如在回调尚未展开时),则返回 MIGO_ERROR_INVALID_STATE 且 Engine 不被消耗;应在回调展开后从宿主线程重试。
JS Worker 已知缺口:上述屏障不覆盖内容通过 JS Worker API 创建的线程。销毁 Session 的 runtime 会中断各 isolate 并通知其消息循环停止,但不等待线程完成 unwind,因此 migo_engine_destroy 返回时可能仍有 Worker 线程正在退出。对于仅调用 exit 或在进程生命期内保持库加载的宿主,此行为无影响;对于在 migo_engine_destroy 返回后立即卸载库(dlclose / FreeLibrary)的宿主,需注意可能有 Worker 线程仍在执行库内代码。
返回:
MIGO_OK:Engine 销毁完成,所有 engine worker 已退出,句柄已释放。MIGO_ERROR_INVALID_ARGUMENT:engine为NULL。MIGO_ERROR_INVALID_STATE:Engine 仍拥有活跃 Session,或调用发生在 engine worker 线程内部;Engine 未被消耗,可在修正条件后重试。
以下片段取自 tests/c_host/linux/main.c,演示最小化的 Engine 初始化与析构:
/* 宿主负责提供三个存储根目录。 */MigoEngineConfig config;memset(&config, 0, sizeof(config));config.struct_size = (uint32_t)sizeof(config);config.abi_version = MIGO_ABI_VERSION_CURRENT;config.flags = MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT; /* 仅开发用 */config.files_dir_utf8 = "/data/migo/files";config.cache_dir_utf8 = "/data/migo/cache";config.code_cache_dir_utf8 = "/data/migo/code-cache";
MigoEngine *engine = NULL;MigoResult r = migo_engine_create(&config, &engine);if (r != MIGO_OK) { /* 处理错误 */ }
/* … 创建 Session,运行内容 … */
/* 析构顺序:先 Session,再 Engine */migo_session_destroy(session);migo_engine_destroy(engine);完整可运行示例:
tests/c_host/linux/main.c和tests/c_host/android/src/main/cpp/main.c。
多 Engine 实例
Section titled “多 Engine 实例”一个进程可以持有任意数量的 Engine 实例,migo_engine_create 与 migo_session_create 均可从不同宿主线程并发调用。
存储根与隔离:每个 Engine 拥有独立的三个存储根。Engine 下的每个 Session 都从同一组根目录出发,但实际路径按 content_id 划分为 <root>/migo/games/<content_id>/,因此不同 content_id 的 Session 天然隔离。宿主须保证并发活跃的 Session 使用不同的 content_id;共享相同 content_id 的两个 Session 也共享同一游戏目录(存储、缓存、临时文件均如此),引擎不拒绝这种用法,但隔离语义由宿主保证。
独立 Engine 的典型理由:平台通常只分配一个文档目录(Android 的 getFilesDir()、iOS 的 NSDocumentDirectory);若需要将两款游戏置于完全不同的根路径下,创建第二个 Engine 是正确的做法。
code cache 共享:code_cache_dir_utf8 在同一 Engine 的所有 Session 间共享是有意为之。编译后的字节码以源文件哈希为键,与产生它的 Session 无关;两个 Session 加载相同模块时只编译一次。淘汰策略为跨整个目录的 LRU——某个 Session 大量编译时可能挤出其他 Session 的缓存条目,代价仅为一次重新编译,而非错误结果。若希望两个 Engine 实例也共享字节码缓存,只需令它们的 code_cache_dir_utf8 指向同一目录即可。