ABI¶
契约就是一份文件:
packages/pier-abi/include/sdk/abi.h。
它能被 C11 和 C++20 各解析一遍,CI 每次推送都两种都编。它同时也是参考文档: 每个槽位都写着参数含义、失败时返回什么、对线程有什么要求。任何语言的绑定作者 除了它没有别的可读——这正是那份头文件不受仓库其余部分注释预算约束的原因。
本页讲形状。槽位本身请读头文件。
形状¶
| C | |
|---|---|
从这个头部推出三条性质:
布局在所有构建目标下相同。 PierApi 里任何地方都没有条件编译。客户端那组和维度那组
永远占着位置,只在对应包没编进来时为 NULL。
能力缺席就是槽位为 NULL。 有没有某个能力,就是它的槽是不是空——一个运行期的问题, 有运行期的答案。
struct_size 是前向兼容的全部依据。 宿主报出自己编出来的长度,模组只读这个长度以内的东西。
两道闸¶
调用任何非核心槽之前:
| C | |
|---|---|
缺一不可,顺序也不能反。只查非空,会在表短时读越界,而越界读回来的东西常常看起来像 一个合法的函数指针。只查长度,会在能力包没编进来时调一个空指针。
Rust 侧的 require_slot! 宏两道一起做,并产出一条指明是哪一道失败的错误。
所有权¶
任何跨边界的缓冲区,由产出方分配、产出方释放,接收方在回调期间读、要留就拷。
| 形状 | 谁分配 | 接收方能做什么 |
|---|---|---|
PierStr(指针 + 长度) |
调用方 | 返回前读完,要留自己拷 |
| Sink 回调 | 宿主 | 在 sink 内拷走,返回后指针失效 |
车道的 data / vtable |
提供方 | 只在存活标志为真时调用 |
ABI 上永远不会返回一个需要对方释放的指针。那需要跨边界的分配器契约,而两边的 分配器不是同一个。所有输出都走 sink。
结构化数据是 SNBT¶
这条 ABI 上没有按值传递的结构体。事件载荷、表单、物品、方块状态、实体快照、 命令参数、服务请求,全都是 SNBT 字符串。
Rust SDK 用 NbtValue 替你解析。别的语言的绑定,要么自己写一个 SNBT 解析器,
要么走 nbt_snbt_to_binary 再用现成的 NBT 库。
线程¶
默认每个槽都只能在服务器线程上调,例外逐槽注明。标了线程安全的是
schedule_for、log 和 kvdb_* 这一族。
数据包拦截回调是反方向的例外:它根本不在服务器线程上跑。
演进¶
只增不减。新增能力就是在表尾追加,版本号不动。重排、删除或改签名,
则 PIER_ABI_VERSION 和 PIER_ABI_MIN_SUPPORTED 一起推进到同一个数。
这件事发生过一次。ABI 目前是 v2:26.32.2 删掉了 26.20.3 起废弃的四个维度槽, 两个数一起推进到 2,按 v1 编的模组在装载时被拒绝,需要用 v2 的 SDK 重编。 之后新增的东西都是追加,没有再动版本号。
tools/abi-v1.slots 是基线,abi-additive 检查每次推送都对着它比,
所以非追加式的变更没法悄悄落地。文件名沿用最初那个,版本记在文件自己里。
在真实宿主上看一眼¶
tools/pier-probe 是一个走完整张表的 mod,逐个槽位报告宿主有没有、答什么。
它只读,所以有玩家在线也能跑。
它同时也在证明本页的说法:这个 mod 只包含 sdk/abi.h,不链任何库,也不需要构建系统,
build-msvc.bat 就是一条 cl 命令。哪天这条不再成立,说明 ABI 长出了一个它没承认的依赖。
入口点要写 PIER_MAIN_EXPORT,这个宏由头文件提供。给符号起名不等于把它导出:
Windows 的 DLL 不主动要求就什么都不导出,光声明能编过、加载时才被拒。
ELF 构建默认全导出,所以这一条值得写出来,而不是留给读者自己踩。
四种判定。ABSENT 按原因分两种:超出 struct_size 是宿主比 mod 旧,
槽位为 NULL 是宿主没编进填它的那个包。REFUSED 是槽位被调用了、报告失败,
在 BDS 1.26.32 上这是一批能力的既定答案。THREW 是唯一一种永远算缺陷的结果。