跳到主要内容
文档站副本

本页为语义契约的发布副本;请在上游 ZenTSTest/Docs/spec 修改后执行 npm run sync-spec。(源:10-LIFETIME.md)

10 — 生命周期、GC 与异常边界

ObjectRegistry、Struct 相关 Registry、Opaque 有效期、单 JSContext 与 C#↔JS 异常转换。 Opaque 细节 → marshal/04-OPAQUE.md;Registry 实现 → impl/marshal/。


1. 设计原则​

原则说明
JS 语义优先托管对象存活须与 JS exotic / ref 生命周期一致
Il2Cpp GC 集成ByObj 槽位数组注册 GC root;non-blittable struct 须扫描 struct 内存
Opaque 临时性仅同步 C#→JS 调用帧内有效;禁止持久化
单主上下文默认 JSRuntime + 域内一个主 JSContext;ref 释放经 JsFramePump 批量处理
异常可预测C# 异常 ↔ JS Error 在边界统一转换(zents: 前缀)

Mono 与 Il2Cpp 对外行为一致;内部机制可不同(GCHandle vs Il2CppObject*)。


2. ObjectRegistry(ByObj exotic)​

2.1 职责​

管理 class / string / array / delegate / boxed enum 等 ByObj exotic:

机制说明
槽位表每个 Push 的托管对象分配 slotIndex,写入 _registeredObjects[]
GC root槽位数组通过 GarbageCollector::RegisterRoot 注册,防止 JS 仍持有 exotic 时对象被 Il2Cpp GC 回收
弱值缓存(Il2CppObject*, viewKlass) → 内部 ref;避免同一 identity+门面重复 Push
释放JS 侧 exotic 句柄释放 → UnregisterObject(slot) + 移除缓存项

2.2 Push / Pop​

ObjectRegistry::Push(ctx, obj, viewKlass, dispatchProtoRef);
Il2CppObject* o = ObjectRegistry::Pop(ctx, idx);
  • viewKlass:声明类型门面(marshal/06-CLASS.md)
  • Pop:校验 ByObj kind;null → nullptr

2.3 生命周期​

C# 返回对象 → Push → exotic (slot 注册 + root 保活)
→ JS 持有期间:slot 非空,对象不被 Il2Cpp 单独回收
→ 句柄释放 → UnregisterObject → slot 清空
→ 若无其它 C# 引用:对象可被 Il2Cpp GC

注意: exotic 释放 不 保证立即运行 C# 终结器;仅解除 ZenTS 的 root 保活。

2.4 Shutdown​

ObjectRegistry::Shutdown(ctx):

  1. 清空 C++ (obj, view) 映射
  2. 释放弱值缓存表

须在释放主 JSContext 前、且无未完成的跨边界调用时调用。


3. Struct 与值类型 Registry​

3.1 ByVal exotic​

struct 实例 exotic 载荷为 值拷贝(或 pinned box)。释放回调释放 native 拷贝 / GCHandle;不 走 ObjectRegistry 槽位(除非 boxed 为 ByObj)。

3.2 NotBlittableStructRegistry(Il2Cpp)​

non-blittable struct 的 ByVal exotic:

项说明
存储exotic 内 struct 拷贝
GCRegisterPushRootCallback 扫描 struct 内存内 引用字段
释放Release(index) 与 Registry 对称

Blittable struct 默认 Opaque handle 路径无 exotic 长期 __gc;见 marshal/05-STRUCT.md。

3.3 Mono 等价​

Mono 使用 GCHandle / boxed 等等价机制,同一 JS 可见语义。


4. OpaqueValue 生命周期​

4.1 有效域​

项规则
产生C#→JS:GetFunction delegate、JsMethod bridge、标注 [JsMarshalAs(OpaqueValue)]
形态opaque internal handle / exotic(无 三表成员分派)
有效仅 产生它的那次 C#→JS 调用 尚未返回
失效C# 返回后;或 OpaqueParameterScope generation 推进

4.2 禁止行为​

  • 写入全局、闭包、表字段后在后续 JS_Call / 异步中使用
  • 对 opaque 使用 . 成员访问
  • 假定 handle 跨帧仍有效

失效后 → throw Error('zents: invalid opaque parameter handle')。

4.3 与 Registry 的区别​

OpaqueValueByObj / ByVal exotic
注册不进入 ObjectRegistryRegistry + 释放回调
存活调用帧exotic 句柄存活期
成员分派无IEO 三表

5. Delegate 与 JS function ref​

5.1 JS → C# delegate​

隐式或 zents.to_delegate 创建 delegate exotic 时:

  • native 持有 JS function ref(内部 registry / 等价机制)
  • C# 持有 delegate → 脚本 function 保活
  • delegate exotic 被释放 → 排队 延迟 unref(避免在 C# 栈上直接 unref)

5.2 C# → JS(JsMethod)​

C# delegate 传入 JS 后可直接 d(...)([[Call]])。若 C# 侧不再持有 delegate,关联的 JS ref 在终结 / Dispose 路径释放。

5.3 帧泵​

JsAppDomain.ProcessPendingRefReleases()(JsFramePump 驱动)处理延迟 unref 队列。须在 Unity 主线程、与 JS 调用同线程 执行(01-HOST-API.md §1.4)。


6. 单 JSContext 与线程​

6.1 默认模型​

ZenTS 宿主默认使用 JSRuntime + 域内一个主 JSContext:

  • 全局 CSharp、zents、Registry 缓存、ES module loader 均绑定该上下文
  • 不支持 多线程并发无锁访问同一 context

6.2 调用线程​

场景要求
Unity 主线程调 JS默认支持
后台线程调 JS须 宿主显式同步;否则未定义行为
GetFunction / delegate bridge / JS→C#应在初始化 context 的同一线程或受控队列

6.3 协程 / async​

JS async/await 或 Promise 回调中 不得 使用已失效的 OpaqueValue(仍受 §4 约束)。跨 await 保存 opaque → throw。


7. 初始化与整域 Reset 顺序​

宿主公开 API:JsAppDomain.Initialize / Reset(无 公开 Shutdown)。

7.1 Initialize(概念)​

1. 创建 JSRuntime + 主 JSContext
2. ZenTSLib::RegisterGlobals(zents 内部 hook)
3. 加载 zentslib.js
4. ObjectRegistry::Initialize
5. TypeRegistry / MetaBinding / Opaque scope 初始化
6. 创建全局 CSharp 根对象
7. 安装 ES module loader(`csharp:` 类型模块 → 原生 C 模块 → 宿主 `moduleLoader` / `GetFunction`)
8. (Il2Cpp)RegisterPushRootCallback for struct roots
9. 注册 JsFramePump

已存在主 context 时再次 Initialize → 抛 C# 异常(须 Reset)。

7.2 Reset(概念)​

Reset(loader) 先预约,在本帧 EndOfFrame 由 JsFramePump 真正执行:

调用当下:
1. 记录 pending loader
2. 不立刻释放 context

EndOfFrame:
1. ProcessPendingRefReleases 排空队列
2. ObjectRegistry::Shutdown / Struct / MetaTable 等
3. 释放 JSContext / JSRuntime(旧 GetFunction 委托一律作废)
4. 按 §7.1 重建并安装 loader

Il2Cpp:进程级 Bridge / XML 表 / InternalCall 保留;仅重建 state 级资源。

因 teardown 推迟到帧末,允许在 C#↔JS 调用中途 预约 Reset;EndOfFrame 之后 旧 delegate 必须 丢弃并重新 GetFunction(01-HOST-API.md §1.2)。


8. 异常边界​

8.1 C# 调用 JavaScript​

事件行为
JS throw捕获为 C# 异常(JsScriptException 或包装类型)
JS 栈不平衡native 断言 / 异常
C# 异常穿过 native禁止 泄漏;边界 translate 或 rethrow

GetFunction invoke 前后维护 OpaqueParameterScope,异常路径亦失效 opaque。

8.2 JavaScript 调用 C#​

事件行为
C# 抛异常转换为 throw new Error('zents: …');Mono / Il2Cpp 文案一致或等价
脚本try/catch 捕获

Editor Mono: 不得在托管 reverse-P/Invoke 帧内直接 JS_Throw 未包装路径;须经 native callback gate(实现见 impl/MONO.md)。Il2Cpp 遵守 QuickJS 与 C++ 析构约束。

8.3 错误消息​

Bind 失败、marshal 失败、重载无匹配、opaque 无效、成员 miss 等,Mono 与 Il2Cpp 须 对同一条件给出等价文案(zents: 前缀)。


9. GC 交互摘要​

flowchart TB
subgraph JsGC["JS 引擎 GC / 句柄释放"]
UD[exotic 释放]
end
subgraph ZenTS["ZenTS Registry"]
OR[ObjectRegistry Unregister]
SR[StructRegistry Release]
end
subgraph Il2CppGC["Il2Cpp GC"]
ROOT[GC roots 槽位数组]
SCAN[PushRootCallback struct 扫描]
end
UD --> OR
UD --> SR
OR --> ROOT
SR --> SCAN
对象类型JS 回收触发托管回收
ByObj classexotic 释放解除 root 后可 GC
ByVal blittableexotic 释放或 scope 结束拷贝释放
ByVal non-blittableexotic 释放扫描 + 释放拷贝
Opaquescope 结束(非 exotic GC)不涉及 root

10. 相关文档​

文档内容
01-HOST-API.mdGetFunction、Reset、JsFramePump
marshal/04-OPAQUE.mdOpaque API
marshal/06-CLASS.mdByObj、view
marshal/05-STRUCT.mdstruct GC
marshal/09-FUNCTION.mddelegate ref
00-OVERVIEW.md初始化流程