C ABI 与宿主服务

模块导出

每个脚本库导出 7 个 C 函数(mtr_native.h;注册宏自动生成全部):

导出说明
uint32_t mtrNativeAbiVersion()返回编译时的 ABI 版本(当前 13);宿主拒绝不兼容的版本——与 JCM 对 JS 脚本的版本失配重载策略一致
const char* mtrScriptType()"vehicle" / "pids" / "eyecandy" / "lift" / "psd"
const char* mtrScriptId()脚本 id(须与资源包声明一致)
size_t mtrStateSize()逐实例状态块大小;宿主为每个实例分配一块
void mtrInit(const JcmFrameInput*)在宿主刚分配状态块后调用一次,placement-new 构造 State(ABI 6 起必需;宿主漏调时模块惰性构造兜底)
int32_t mtrCreate / mtrRender / mtrDispose(const JcmFrameInput*, …)生命周期入口;返回 0 成功、-1 空入参、-2 ABI 不匹配、-3 资源类型不符。宿主在每模块单一后台线程上按实例串行调用

帧输入与帧输出

JcmFrameInput(宿主 → 模块)

typedef struct JcmFrameInput {
    uint32_t abi_version;              /* 协商后的 ABI 版本 */
    int32_t  resource_kind;            /* MtrResourceKind:vehicle/pids/eyecandy/lift/psd */
    const void* snapshot;              /* 对应的 Jcm*Snapshot */
    void*    state;                    /* 宿主分配的逐实例状态块 */
    size_t   state_size;
    const struct JcmHostServices* host;/* 渲染线程可调用的宿主服务 */
} JcmFrameInput;

快照内部约定:所有 *_offset 字段都是相对快照首字节的字节偏移;string_pool_offset/len 描述共享 UTF-8 字符串池的位置。SDK 包装器替你完成了全部寻址。

JcmFrameOutput(模块 → 宿主)

输出是扁平记录流 + 五个输出内存区——「内存区」(arena)指一大块预先申请的内存,帧内按顺序分配、帧末整体交还,mtrRender 返回后宿主立即读取并回收帧(模块间双缓冲由宿主完成):

内存区内容容量(SDK 默认)
records顺序排列的绘制记录(变长,按 record_size 步进遍历)256 条
matrix_arena矩阵流(列主序 4×4,每矩阵 16 个 float)128 组
string_arenaUTF-8 字符串流(贴图 id、文本、声音 id…)32 KB
pixel_arenaBGRA8 像素流(纹理上传)初始 1 MB,按需增长至 16 MB,闲置后衰减回落
float_arenafloat 流(体素碰撞箱等)1024 个

ABI 9 起输出同时给出各内存区的完整容量——宿主为每个内存区只建一个 ByteBuffer、每帧改 limit 复用,不再逐帧分配五个包装对象。

绘制记录

每条记录以 JcmRecordHeader{kind, flags, record_size} 开头,变长顺序排列。flags 承载记录级开关:目前定义 MTR_RECORD_FLAG_NATURAL_LIGHT(Text / Texture / Rect 的 natural_light()——取环境光照而非最大渲染亮度)。该字节由原 padding 就地升格:旧 SDK 编译的模块恒写 0、旧宿主忽略之——双向二进制兼容,无 ABI 跳版。8 种类型:

kind结构用途 / 镜像的 JS 调用
JCM_DRAW_TEXTJcmDrawTextPIDS / 通用文本(TextWrapper 字段逐一对齐:对齐、溢出模式、跑马灯、z 序、层、字体 id)
JCM_DRAW_TEXTUREJcmDrawTexture贴图面片(UV、染色、z 序)
JCM_DRAW_MODELJcmDrawModel车辆车厢 / 转向架 / 装饰模型(宿主模型句柄 + 姿态矩阵组)
JCM_DRAW_SOUNDJcmDrawSound定位音效(车厢局部坐标偏移)
JCM_DRAW_LOCAL_SOUNDJcmDrawSound报站广播(仅乘坐玩家可闻)
JCM_DRAW_TEXTURE_UPLOADJcmDrawTextureUploadGraphicsTexture 像素上传(BGRA8 + 脏矩形)
JCM_DRAW_OUTLINE_SHAPEJcmDrawShape装饰轮廓 / 碰撞箱(每箱 6 个 float;碰撞箱上限 1.5 格)
JCM_DRAW_BLOCK_EVENT—装饰 onBlockUse 事件握手

宿主回放这些记录用的是与 JS 脚本完全相同的渲染代码路径(PIDSDrawCall / ScriptRenderManager)——画出来就是同一个东西。

宿主服务

JcmHostServices 是一张函数指针表(in->host),渲染线程内可调用。旧宿主上新增槽位为 null,调用前判空即可降级:

服务起版说明
acquire_model / release_modelABI 1模型 JSON 句柄生命周期(镜像 ModelJS 句柄)
create_texture / release_textureABI 1GraphicsTexture 生命周期(镜像 Java 类)
rasterize_textABI 1宿主 TTF 栅格化回调(CJK 等),BGRA8 写入调用方缓冲;返回 -1 表示字体不覆盖该码点(应回退内置位图字体)
logABI 1console.log / console.error 通道
acquire_quad_modelABI 4宿主构建贴图面片(4 顶点 + UV + 渲染阶段 + 纹理句柄)——JS DisplayHelper 槽位模型的原生等价物;顶点为模型空间、UV 以贴图左上为原点、y 向下。无头驱动返回 -1,此时跳过绘制
measure_font_text / draw_font_textABI 7资源字体的度量与基线栅格化(与 JS Graphics2D 同一字体、同一压缩与裁剪语义;脏边界出参)
invoke_apiABI 8mtr::Api 的底层通道:UTF-8 JSON 请求进、结果出到线程局部缓冲(下次调用前有效)
defer_freeABI 10把已死亡的大块内存交给宿主后台回收线程(详见 性能与调试)

ABI 版本演进

宿主接受 ABI 6–13 模块(各自按其时代的布局喂数、回报其自己的 ABI 号)。用当前 SDK 编译即产出 ABI 13:

ABI引入内容
6mtrInit():宿主必须构造状态对象(零填充块对非平凡类型是未定义行为)
7资源字体度量 + 基线文本栅格化加入宿主服务
8invoke_api(完整 Java API 通道)、电梯快照、换乘类型化模式;ABI 8 库要求 JCM 2.3.0-beta-2.yanyang.7 及以上宿主
9换乘 / 车辆的规范交通类型 id(MAGIC MTR 扩展);宿主停止逐帧复制快照、内存区容量出参
10defer_free 延迟回收钩子(脚本自有大缓冲的离 JVM 线程释放)
11车辆快照 door_direction(开门方向 0..3 —— 由原 _pad0 字节就地升格,布局与尺寸不变,老模块照常工作)
12MTR_RESOURCE_PSD(第 5 类脚本)与 JcmPsdSnapshot / JcmPsdArrival / JcmPsdRoute / JcmPsdDoor 快照——纯增量,既有结构零变化;ABI 12 库要求晏阳 hotfix.2 以上宿主
13JcmPsdSnapshot.station_offset(字节偏移 124;-1 = 无——该字段在 ABI 12 中是填充位)与其后 96 字节的 JcmPsdStation 记录,支撑 psd.stop() / mtr::Psd::stop():站名、颜色(ARGB + 十六进制)、票价区 1–3、停靠本站的全部线路、邻站线路分组、物理连通站名、出口及去向、站台编号——纯增量,既有结构零变化;ABI 6–12 模块照常加载
为什么必须拒绝旧宿主

函数指针表随 ABI 增长:新模块会调用旧宿主不存在的槽位。宿主按模块报告的 ABI 号选择对应的结构布局与喂料方式,不匹配就拒载——这是刻意的失败快策略。

传输层(宿主内部,脚本不可见)

自 2.3.0-beta-2.yanyang.6(layout v2)起模组内完全无 JNI:两个调用平面承载全部跨语言流量——Panama FFM 平面(JDK 22+,libraryLookup + jcm_* downcall + upcall vtable)与共享内存轮询平面(任意 JDK ≥ 16,桥以构造器启动的后台 poller 线程经文件系统握手发现区域,一切经共享页服务)。脚本代码对此完全无感——模块只看到上述 C ABI。


接下来:性能与调试 —— 把脚本跑得又快又稳。