腳本體例與生命週期

五類腳本

納 <mtr/script.hpp>,承其所應之基類,以註冊宏而導出之。基類模板之參,即逐實例狀態之型:

資源基類註冊宏數據包裝器
車輛mtr::VehicleScript<State>MTR_REGISTER_VEHICLE_SCRIPTmtr::Vehicle
PIDSmtr::PidsScript<State>MTR_REGISTER_PIDS_SCRIPTmtr::Pids
屏蔽之門mtr::PsdScript<State>MTR_REGISTER_PSD_SCRIPTmtr::Psd
電梯mtr::LiftScript<State>MTR_REGISTER_LIFT_SCRIPTmtr::Lift
裝飾mtr::EyecandyScript<State>MTR_REGISTER_EYECANDY_SCRIPTmtr::EyeCandy

車輛腳本之骨架一首(餘三類,惟易其基類與參數之型而已):

#include <mtr/script.hpp>

struct MyVehicleState {
    mtr::CycleTracker dest{"KOWLOON|TSUEN WAN"};   // 君自定之任意 C++ 類型
    mtr::Api::Object tracker;                        // Java 物件之柄
    std::unique_ptr<mtr::GraphicsTexture> lcd;    // 所持之資源
};

struct MyVehicleScript : mtr::VehicleScript<MyVehicleState> {
    static constexpr auto ID = "demo:vehicle";

    void create(mtr::VehicleContext& ctx, MyVehicleState& state,
                const mtr::Vehicle& vehicle) override {
        // 首幀之前一呼:造資源、註回呼
    }

    void render(mtr::VehicleContext& ctx, MyVehicleState& state,
                const mtr::Vehicle& vehicle) override {
        // 每幀一呼:讀快照、書繪製記錄
    }

    void dispose(mtr::VehicleContext& ctx, MyVehicleState& state,
                 const mtr::Vehicle& vehicle) override {
        // 實例銷毀前一呼:釋放資源
    }
};

MTR_REGISTER_VEHICLE_SCRIPT(MyVehicleScript)
  • ID 者,腳本命名空間之 id 也(如 demo:vehicle),必與資源包 mtr_custom_resources.json 中宣告條目之 id 相合。
  • 三生命週期函數,override 皆可省(基類固有虛而空之實現),惟參數之簽名不可易。
  • 一翻譯單元,惟註一腳本;腳本之眾者,分為數模塊之庫。

State:逐實例之狀態

每腳本實例(每車也,每 PIDS 方塊實體也……)各有獨立之 State 一塊,由宿主分而構之:

  • 宿主按 mtrStateSize() 之數(註冊宏自 sizeof(State) 生之)而配其塊;首用之前,經 mtrInit() 以 placement-new 構之——慎勿視零填充之記憶體為可用(std::string 諸非平凡之型,施於零記憶體,未定義之行也)。
  • 塊中之物,跨幀恆存:紋理之柄、模型之柄、回呼、緩存,悉置諸此。
  • 至若腳本類之成員(君於 MyVehicleScript 所書之欄),則該模塊諸實例所共——宜惟置唯讀之常數與全域之配置。
  • dispose 既返,狀態隨即而析;實例自客戶端之列而沒者,約五秒乃除役;資源包重載(F3+T)之時,則盡毀之。
要訣

後臺回呼之物(mtr::Api::Callback),必存於 State,以終其身——蓋 Java 側者,任意之時皆得呼之也。

生命週期之時序

分配+構造

宿主為實例配狀態之塊,呼 mtrInit(),則 State() 構焉。

create

實例未繪,先此一呼。create 之際所呈之繪製記錄,必留至首幀 render 而復用——一次之靜態內容(形狀也,常駐之貼圖也)置諸此;render 惟畫其動者。資源之取、Api 之呼、回呼之註,亦皆宜在焉。

render(每幀)

讀快照而運算,錄繪製記錄於幀之之區。快照與包裝器,惟本次一呼之內為效;宿主於 render 既返,即讀其輸出而收其幀。實例不可見,則 render 不呼,而狀態恆存焉。

dispose

實例除役或重載之時,乃呼之。於此閉其所持紋理、模型諸資源;State 之析構隨之。縱 dispose 擲異常,狀態亦必安然就析。

與 JS 相應

此 create / render / dispose + 逐實例 state 之制,即 JCM 為 JS 腳本所設之模型(每 ScriptInstance 一 state 物件)原生之映也;遷 JS 腳本之時,邏輯可逐段對譯焉。

線程模型

  • 宿主之於每模塊,立單一後臺之線程,串行呼其生命週期諸函(映 JCM 於 JS 腳本逐腳本單線程執行之器)——同一實例之 create / render / dispose,永不並發。
  • 然 render 與非同步之回呼,或得並發(BackgroundWorker 所呈之任務,方行於他線程者也)——共享可變之狀態而欲其安,腳本自任其責。
  • 資源包重載,必先止回呼,俟方行呼者既返,而後毀其狀態、卸其碼。

崩潰隔離

註冊宏者,裹每一導出函數於崩潰之防護(crash_guard.hpp):

  • 段錯誤(segmentation fault)諸硬體之疾,惟禁該模塊而已,遊戲行程行如故;latest.log 留 native script faulted … module disabled 一行。
  • 未獲之 C++ 異常,亦為防護所截——然請自為 try/catch,以保其語義之明。
  • POSIX 之訊號,不以 longjmp 之式「復」之:越訊號處理器而跳,其危甚於崩潰本身;故模塊逕下線者,蓋刻意之設也。

返回之碼

mtrCreate / mtrRender / mtrDispose 所返其義
0成功
-1入參為空
-2ABI 版本不合(宿主與模塊商議不成)
-3資源類型不合(如車輛之模塊,誤以 PIDS 呼之)

此諸返回之碼,SDK 所生也;尋常腳本,無足掛懷。惟君自撰宿主或測試之驅,則按表而解之可也(詳見 C ABI 與宿主服務)。


繼之:數據快照 —— render 之時,所可讀者何物;繪事與畫布 —— 何以繪而出之。