Zig¶
Pier 的 Zig 绑定把模组写成 Zig 动态库,适用于 Zig 0.15.2。包名是 pier,放在 bindings/zig;它导出的模块叫 levilamina,
代码里写 @import("levilamina")。最小的可用模组是 examples/hello-pier-zig。
包名写的是 Pier,表明它属于哪套 ABI;模块名写的是你在做的东西,一个 LeviLamina 模组。
Rust 绑定也是这样:包 pier-rs 导出的 crate 叫 levilamina。
它是怎么搭起来的¶
translate-c 直接读 abi.h。 构建时 translate-c 处理这个头文件,类型、槽位和常量都是 C 编译器自己的解读,没有任何手写镜像。
包里带着一份头文件副本,因为依赖方只看得到这个包自己的目录;有一项检查保证副本与原件完全相同。
Zig 直接调用槽位。 levilamina.slot("名字") 返回槽位的函数指针;表不够长、或槽位为空时返回 null。
契约要求的两道检查在这一个调用里全部完成,适用于表里的每个槽位。其余功能都建在它之上。
zig build test 用头文件检查绑定。 测试让编译器对照 translate-c 生成的表,分析绑定里的每一个函数。
abi.h 里改了名的槽位或类型不对的参数,都会让构建失败;CI 会运行它。
需要什么¶
- Zig 0.15.2。
- 别的都不需要:Zig 能在任何系统上交叉编译出 Windows DLL。示例的默认目标
x86_64-windows-gnu,除系统自带的以外不需要任何 C 运行时。
API¶
| 范围 | 函数 |
|---|---|
| 生命周期 | exportMod,以及模组类型上的 load、enable、disable、unload |
| 日志 | log、logf、Context.info、warn、err |
| 任务 | schedule、scheduleAfter、cancel、pendingTasks |
| 事件 | subscribe、Event.cancel、Event.writeBack、Listener.unsubscribe |
| 命令 | registerCommand、executeCommand |
| 跨模组 | registerService、callService、serviceCaller、listServicesJson、busSubscribe、busPublish、busPublishVetoable |
| 属性和动作 | Player、Entity、BlockAt、Item 上每个常量一个方法,比如 Player.level、Player.setLevel、Entity.isOnFire |
| 数据 | nbt.parse,以及 get、getString、getInt、getFloat、getBool |
| 其余一切 | slot("名字"),配合 levilamina.c 里的类型和常量 |
错误是 levilamina.Error:NotProvided 表示宿主没有这个槽位,Refused 表示宿主拒绝了调用,
NoAnswer 表示宿主答不上来的读取:读不出来的值会以这个错误返回,你拿不到一个 0 或 false。返回文本的函数都接收一个 allocator,内存交给调用方。
跨模组通信¶
Zig 模组可以和任何语言的模组通信,也能通过 bridge 和原生插件通信,规则与 Rust、Go 绑定完全相同:
- 服务提供方发送回复并返回 true,或者发送原因并返回 false;调用方会原样收到这个原因,也就是
callService的provider_error及其内容。serviceCaller给出调用方模组的名字,原生插件调用时为 null。 - 总线订阅者返回的是否决:true 拒绝一次可否决的发布。普通发布会忽略它;
busPublish返回 0 表示没人在听,这不是错误。 - Lane 不提供:它只连接同一工具链构建的模组,而发布 Lane 的模组同时提供服务,Zig 模组用的就是这个服务。
值得知道的规则¶
- 线程。 宿主大部分功能只能在服务器线程上调用。生命周期、事件和命令回调在那里运行;服务提供方在调用方的线程上运行,总线订阅者在发布方的线程上运行。
- 借来的视图。
Event的snbt、Invocation的args和提供方收到的request只在回调期间有效;要保留就复制。 - panic。 Zig 不做栈展开:panic(比如 ReleaseSafe 构建里的安全检查失败)会结束服务器进程。请返回错误; 生命周期步骤返回的错误会被绑定记日志,并拒绝该步骤。
第一个 Zig 模组一步步讲怎么构建和安装。