C ABI 與宿主服務
模組與宿主之間的完整契約。用 script.hpp 的基類與註冊巨集時,這些細節全部由 SDK 代勞——本頁寫給想理解邊界、撰寫測試驅動程式或排查載入問題的人。
模組匯出
每個腳本程式庫匯出 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_arena | UTF-8 字串流(貼圖 id、文字、聲音 id…) | 32 KB |
pixel_arena | BGRA8 像素流(紋理上傳) | 初始 1 MB,按需增長至 16 MB,閒置後衰減回落 |
float_arena | float 流(體素碰撞箱等) | 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_TEXT | JcmDrawText | PIDS / 通用文字(TextWrapper 欄位逐一對齊:對齊、溢位模式、跑馬燈、z 序、層、字型 id) |
JCM_DRAW_TEXTURE | JcmDrawTexture | 貼圖面片(UV、染色、z 序) |
JCM_DRAW_MODEL | JcmDrawModel | 車輛車廂 / 轉向架 / 裝飾模型(宿主模型控制代碼 + 姿態矩陣組) |
JCM_DRAW_SOUND | JcmDrawSound | 定位音效(車廂區域座標偏移) |
JCM_DRAW_LOCAL_SOUND | JcmDrawSound | 報站廣播(僅乘坐玩家可聽見) |
JCM_DRAW_TEXTURE_UPLOAD | JcmDrawTextureUpload | GraphicsTexture 像素上傳(BGRA8 + 髒矩形) |
JCM_DRAW_OUTLINE_SHAPE | JcmDrawShape | 裝飾輪廓 / 碰撞箱(每箱 6 個 float;碰撞箱上限 1.5 格) |
JCM_DRAW_BLOCK_EVENT | — | 裝飾 onBlockUse 事件交握 |
宿主回放這些記錄用的是與 JS 腳本完全相同的渲染程式碼路徑(PIDSDrawCall / ScriptRenderManager)——畫出來就是同一個東西。
宿主服務
JcmHostServices 是一張函式指標表(in->host),渲染執行緒內可呼叫。舊宿主上新增槽位為 null,呼叫前先檢查是否為 null 即可降級:
| 服務 | 起始版本 | 說明 |
|---|---|---|
acquire_model / release_model | ABI 1 | 模型 JSON 控制代碼生命週期(對應 ModelJS 控制代碼) |
create_texture / release_texture | ABI 1 | GraphicsTexture 生命週期(對應 Java 類別) |
rasterize_text | ABI 1 | 宿主 TTF 光柵化回呼(CJK 等),BGRA8 寫入呼叫方緩衝區;傳回 -1 表示字型未涵蓋該碼點(應回退到內建點陣字型) |
log | ABI 1 | console.log / console.error 通道 |
acquire_quad_model | ABI 4 | 宿主建構貼圖面片(4 頂點 + UV + 渲染階段 + 紋理控制代碼)——JS DisplayHelper 槽位模型的原生等價物;頂點為模型空間、UV 以貼圖左上角為原點、y 向下。無頭驅動傳回 -1,此時跳過繪製 |
measure_font_text / draw_font_text | ABI 7 | 資源字型的度量與基準線光柵化(與 JS Graphics2D 同一字型、同一壓縮與裁切語意;髒邊界輸出參數) |
invoke_api | ABI 8 | mtr::Api 的底層通道:UTF-8 JSON 請求進入、結果輸出到執行緒區域緩衝區(下次呼叫前有效) |
defer_free | ABI 10 | 把已死亡的大塊記憶體交給宿主背景回收執行緒(詳見 效能與除錯) |
ABI 版本演進
宿主接受 ABI 6–13 模組(各自按其時代的佈局餵資料、回報自己的 ABI 版號)。用目前 SDK 編譯即產出 ABI 13:
| ABI | 引入內容 |
|---|---|
| 6 | mtrInit():宿主必須建構狀態物件(零填充區塊對非平凡型別是未定義行為) |
| 7 | 資源字型度量 + 基準線文字光柵化加入宿主服務 |
| 8 | invoke_api(完整 Java API 通道)、電梯快照、型別化的轉乘模式;ABI 8 程式庫要求 JCM 2.3.0-beta-2.yanyang.7 及以上宿主 |
| 9 | 轉乘 / 車輛的標準化交通類型 id(MAGIC MTR 擴充);宿主停止逐幀複製快照、記憶體區容量輸出參數 |
| 10 | defer_free 延遲回收鉤子(腳本自有大緩衝於 JVM 執行緒之外的釋放) |
| 11 | 車輛快照 door_direction(開門方向 0..3 —— 原 _pad0 位元組就地升格,佈局與尺寸不變,舊模組照常運作) |
| 12 | MTR_RESOURCE_PSD(第 5 类腳本)與 JcmPsdSnapshot / JcmPsdArrival / JcmPsdRoute / JcmPsdDoor 快照——纯增量,既有結構零變化;ABI 12 库要求晏阳 hotfix.2 以上宿主 |
| 13 | JcmPsdSnapshot.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。
接下來:效能與除錯 —— 把腳本跑得又快又穩。