Modèle de script et cycle de vie
Les classes de base des quatre catégories de scripts, l’état par instance, l’ordonnancement du cycle de vie, ainsi que la sémantique des threads et de l’isolation des plantages.
Les quatre catégories de scripts
Incluez <mtr/script.hpp>, héritez de la classe de base correspondante et exportez-la avec la macro d’enregistrement. Le paramètre modèle de la classe de base est le type d’état par instance :
| Ressource | Classe de base | Macro d’enregistrement | Enveloppe de données |
|---|---|---|---|
| Véhicule | mtr::VehicleScript<State> | MTR_REGISTER_VEHICLE_SCRIPT | mtr::Vehicle |
| PIDS | mtr::PidsScript<State> | MTR_REGISTER_PIDS_SCRIPT | mtr::Pids |
| Portes palières | mtr::PsdScript<State> | MTR_REGISTER_PSD_SCRIPT | mtr::Psd |
| Ascenseur | mtr::LiftScript<State> | MTR_REGISTER_LIFT_SCRIPT | mtr::Lift |
| Eyecandy | mtr::EyecandyScript<State> | MTR_REGISTER_EYECANDY_SCRIPT | mtr::EyeCandy |
Le squelette d’un script de véhicule (pour les trois autres catégories, seuls la classe de base et les types de paramètres changent) :
#include <mtr/script.hpp>
struct MyVehicleState {
mtr::CycleTracker dest{"KOWLOON|TSUEN WAN"}; // n'importe quel type C++ à vous
mtr::Api::Object tracker; // handle d'objet Java
std::unique_ptr<mtr::GraphicsTexture> lcd; // ressource détenue
};
struct MyVehicleScript : mtr::VehicleScript<MyVehicleState> {
static constexpr auto ID = "demo:vehicle";
void create(mtr::VehicleContext& ctx, MyVehicleState& state,
const mtr::Vehicle& vehicle) override {
// appelé une fois avant la première frame : créer les ressources, enregistrer les callbacks
}
void render(mtr::VehicleContext& ctx, MyVehicleState& state,
const mtr::Vehicle& vehicle) override {
// appelé à chaque frame : lire l'instantané, écrire les enregistrements de dessin
}
void dispose(mtr::VehicleContext& ctx, MyVehicleState& state,
const mtr::Vehicle& vehicle) override {
// appelé avant la destruction de l'instance : libérer les ressources
}
};
MTR_REGISTER_VEHICLE_SCRIPT(MyVehicleScript)
IDest l’identifiant nommé du script (par ex.demo:vehicle) ; il doit être identique au champidde l’entrée déclarée dans lemtr_custom_resources.jsondu pack de ressources.- Les trois fonctions du cycle de vie peuvent toutes être surchargées au besoin (la classe de base fournit des implémentations vides) ; leurs signatures sont fixes.
- Une unité de traduction enregistre un seul script ; pour plusieurs scripts, répartissez-les dans plusieurs bibliothèques modules.
State : l’état par instance
Chaque instance de script (chaque véhicule, chaque bloc-entité PIDS…) possède son propre bloc State indépendant, alloué et construit par l’hôte :
- L’hôte alloue le bloc d’état selon
mtrStateSize()(généré par la macro d’enregistrement à partir desizeof(State)) et le construit viamtrInit()par placement-new avant la première utilisation — ne supposez pas qu’une mémoire remplie de zéros est utilisable (les types non triviaux commestd::stringont un comportement indéfini sur de la mémoire nulle). - Le contenu du bloc d’état persiste d’une frame à l’autre : placez-y les textures, les handles de modèles, les callbacks et les caches.
- Les membres de la classe de script (les champs que vous écrivez dans
MyVehicleScript) sont en revanche partagés par toutes les instances du module — n’y placez que des constantes en lecture seule et de la configuration globale. - Le bloc d’état est détruit dès que
disposeretourne ; une instance est retirée environ 5 secondes après sa disparition du côté client, et toutes sont détruites au rechargement du pack de ressources (F3+T).
Les objets de callback en arrière-plan (mtr::Api::Callback) doivent être conservés dans State jusqu’à leur destruction — le côté Java peut les appeler à tout moment.
Ordonnancement du cycle de vie
Allocation + construction
L’hôte alloue le bloc d’état de l’instance et appelle mtrInit() → construction de State().
create
Appelé une seule fois avant le premier rendu de l’instance. Les enregistrements de dessin soumis pendant create sont conservés et réutilisés par le premier render — placez-y le contenu statique à usage unique (formes, textures permanentes), render ne dessinera que la partie dynamique. L’acquisition de ressources, les appels Api et l’enregistrement de callbacks conviennent également ici.
render (à chaque frame)
Lecture de l’instantané → calculs → écriture des enregistrements de dessin dans la zone de frame. L’instantané et ses enveloppes ne sont valables que durant cet appel ; dès que render retourne, l’hôte lit la sortie et récupère la frame. Pendant les périodes où l’instance est invisible, render n’est pas appelé, mais l’état est conservé tel quel.
dispose
Appelé lors du retrait de l’instance ou d’un rechargement. Fermez ici les ressources détenues (textures, modèles…) ; la destruction de State suit immédiatement. Même si dispose lève une exception, l’état est détruit en toute sécurité.
Ce modèle create / render / dispose + état par instance est la transcription native exacte du modèle que JCM offre aux scripts JS (un objet state par ScriptInstance) ; lors de la migration d’un script JS, la logique se traduit segment par segment.
Modèle de threading
- L’hôte appelle en série toutes les fonctions du cycle de vie d’un module donné sur un unique thread d’arrière-plan par module (miroir de l’exécuteur mono-thread par script que JCM applique aux scripts JS) — les
create / render / disposed’une même instance ne sont jamais concurrents. - En revanche, render et les callbacks asynchrones peuvent être concurrents (une tâche soumise au
BackgroundWorkerpeut s’exécuter à cet instant sur un autre thread) : la synchronisation de l’état mutable partagé incombe au script. - Au rechargement du pack de ressources, les callbacks sont d’abord arrêtés et les appels en cours attendus, puis les états sont détruits et le code déchargé.
Isolation des plantages
Les macros d’enregistrement enveloppent chaque fonction exportée dans un garde-fou anti-plantage (crash_guard.hpp) :
- Défauts matériels du type segmentation fault → seul le module est désactivé, le processus du jeu continue de tourner ;
latest.logconserve une lignenative script faulted … module disabled. - Les exceptions C++ non interceptées sont elles aussi bloquées par le garde-fou — mais faites vos propres try/catch pour garder une sémantique claire.
- Les signaux POSIX ne sont pas « récupérés » à la longjmp : sauter par-dessus le gestionnaire de signal est plus dangereux que le plantage lui-même ; la mise hors ligne immédiate du module est un choix délibéré.
Codes de retour
| Valeur retournée par mtrCreate / mtrRender / mtrDispose | Signification |
|---|---|
0 | succès |
-1 | paramètre d’entrée nul |
-2 | version d’ABI incompatible (échec de la négociation entre l’hôte et le module) |
-3 | type de ressource inadéquat (par ex. un module véhicule appelé comme PIDS) |
Ces codes de retour sont générés par le SDK ; un code de script ordinaire n’a pas à s’en soucier. Si vous écrivez un hôte personnalisé ou un pilote de test, interprétez-les selon le tableau ci-dessus (détails dans ABI C et services de l’hôte).
Ensuite : Instantanés de données — ce que render peut lire ; Dessin et canevas — comment le dessiner.