mtr::Api et interopérabilité Java
Toutes les API Java publiques accessibles aux scripts JavaScript le sont aussi en C++, sans la moindre exception. Api appelle les mêmes objets publics que JS — il ne s’agit pas de faire exécuter du JavaScript par C++.
Chaque appel Api = un aller-retour encodage JSON + réflexion + décodage JSON. À n’utiliser que dans create(), les tâches d’arrière-plan ou des appels basse fréquence se comptant en quelques unités par seconde ; jamais dans un appel de dessin — historiquement, « un appel Api par frame et par véhicule » était précisément la source des à-coups GC périodiques. Pour les données à haute fréquence, lisez l’instantané ; pour le dessin à haute fréquence, passez par le flux d’enregistrements.
Quand utiliser Api
| Besoin | Canal |
|---|---|
| État frame par frame des véhicules / arrivées / ascenseurs / eyecandy | enveloppes d’instantané (sans coût) |
| Dessin de texte / textures / modèles / sons | flux d’enregistrements de dessin (zéro traversée de JVM) |
Configuration input du script, création de ressources ponctuelle, handles d’objets conservés entre frames | Api (appel dans create, handles stockés dans State) |
| Requêtes réseau, fichiers, BackgroundWorker, gestionnaire de sons et autres interfaces profondes | Api (appel depuis une tâche d’arrière-plan) |
Objets et appels
Construisez Api avec l’entrée de la frame courante ; naviguez depuis les objets racine (context() / wrapper()), les classes statiques ou new :
struct State { mtr::Api::Object tracker; };
struct Example : mtr::PidsScript<State> {
static constexpr auto ID = "example:arrivals";
void create(mtr::PidsContext& ctx, State& state, const mtr::Pids&) override {
mtr::Api api(ctx.input());
state.tracker = api.create("StateTracker"); // new StateTracker()
api.context().call("setZOrderStep", {0.0002}); // ctx.setZOrderStep(0.0002)
}
void render(mtr::PidsContext& ctx, State& state, const mtr::Pids&) override {
mtr::Api api(ctx.input());
auto arrivals = api.wrapper().call("arrivals"); // wrapper.arrivals()
if (arrivals.call("size").as<int>() == 0) return;
auto arrival = arrivals.call("get", {0});
if (arrival.is_null()) return;
auto text = api.call("Text", "create"); // new Text()
text.call("text", {arrival.call("destination")});
text.call("pos", {2.0, 3.0});
text.call("draw", {api.context()});
}
};
MTR_REGISTER_PIDS_SCRIPT(Example)
| Membre Api | Rôle |
|---|---|
api.context() / api.wrapper() | contexte / enveloppe du script courant (les mêmes objets que côté JS) |
api.input() | le JSON input de la déclaration du pack de ressources (= SCRIPT_INPUT en JS) |
api.create(class, args) | instancie (new) un objet Java |
api.call(class, member, args) / api.get(class, member) | méthode statique / champ statique |
api.root(name) | récupère un objet racine par son nom : api.root("console"), api.root("Timing")… |
| Membre Object | Rôle |
|---|---|
obj.call(member, args) | appel de méthode d’instance, retourne un nouvel Object |
obj.get(member) / obj.set(member, value) | lecture / écriture de champ (y compris membres publics hérités et collections standard) |
obj.as<T>() | extrait la valeur brute (int, double, bool, std::string…) |
obj.is_null() | test de vacuité pour les valeurs annulables |
obj.enum_name() | nom d’une énumération Java (par ex. "BOAT") |
obj.close() | appelle explicitement close() (le nettoyage de session ferme aussi, en dernier recours, les ressources oubliées) |
Les objets Java sont gérés par la session avec un comptage de références, les handles étant représentés en chaînes décimales ; la destruction d’un Object libère la référence — stockez dans State les objets à conserver entre frames. Les id numériques (y compris 64 bits) ne perdent jamais en précision. Champs et méthodes sont résolus selon les vrais types Java, y compris les membres publics hérités et les collections Java standard.
Tableau de correspondance JS ↔ C++
| JavaScript | C++ |
|---|---|
Resources.id("example:file") | api.call("Resources", "id", {"example:file"}) |
new Matrices() | api.create("Matrices") |
ctx.getRenderManager() | api.context().call("getRenderManager") |
obj.method(a, b) | obj.call("method", {a, b}) |
obj.field / obj.field = v | obj.get("field") / obj.set("field", v) |
TransportMode.BOAT | api.get("TransportMode", "BOAT") |
SCRIPT_INPUT | api.input() (valeur JSON) |
console.log(...) / print(...) | api.root("console").call("log", {...}) |
Timing.delta() | api.root("Timing").call("delta").as<double>() |
array.length / array[i] | array.call("length") / array.call("get", {i}) |
include(...) | #include à la compilation côté C++ |
Les classes AWT s’utilisent avec leur nom complet : java.awt.Color, java.awt.Font, java.awt.BasicStroke, java.awt.geom.AffineTransform. Les champs publics graphics / bufferedImage de GraphicsTexture exposent les véritables objets Java2D. Résolution des chemins de ressources : Resources.idr se base sur le chemin déclaré du module courant (en cas de déclarations multi-bibliothèques, chacune utilise le sien) ; Resources.manager() vaut null, comme en JS sur MTR 4. Les nouveaux membres de l’amont v2.3.0-prerelease.2 — la forme de concaténation Resources.id(id, suffix), l’alias Resources.mgr() (compatibilité des packs de ressources ANTE), les variantes à argument chaîne comme VanillaText.withFont(String) — fonctionnent tels quels via le même canal JSON-RPC. Les capacités signalées comme non implémentées dans la documentation amont (drawConnModel / drawConnStretchTexture pour les attelages de véhicules) ne sont pas davantage inventées par ce mod.
Callbacks en arrière-plan
Api::Callback encapsule une fonction C++ en interface fonctionnelle Java (Runnable / Consumer / Function…). L’objet de callback doit rester en vie tant que Java peut encore l’appeler — placez-le dans State :
struct State {
std::unique_ptr<mtr::Api::Callback> task; // conservé jusqu'à la destruction
};
void create(mtr::PidsContext& ctx, State& s, const mtr::Pids&) {
mtr::Api api(ctx.input());
s.task = std::make_unique<mtr::Api::Callback>([api](mtr::Json) {
auto response = api.call("Networking", "fetch",
{"https://example.org/data"});
// publier le résultat dans votre propre état synchronisé ; lu dans render
return mtr::Json();
});
api.root("BackgroundWorker").call("submit", {*s.task});
}
- render et les callbacks peuvent être concurrents — verrouillez vous-même l’état C++ mutable partagé.
- Au rechargement, les callbacks sont d’abord arrêtés et les appels en cours attendus, avant la destruction des états et le déchargement du code.
- Les modèles GPU préparés en arrière-plan se téléversent via
DynamicModelHolder.uploadLater;ModelManager.uploaden direct exige toujours le thread de rendu Java (appelez-le depuiscreateou un callback du thread principal).
Couverture
Surface d’API usuelle : console, Resources, ModelManager / Model / RawModel / RawMeshBuilder / DynamicModelHolder, Vector3f, Matrices, GraphicsTexture et Java2D, RenderManager / QuadDrawCall, SoundManager / TickableSoundInstance, MinecraftClient, PlayerEntity / ItemStack / VanillaText / VoxelShape, Files, Networking, BackgroundWorker, TextUtil / Timing / StateTracker / CycleTracker / RateLimit, DataReader, MTRClientData, les classes de données TSC et DisplayHelper. La liste des membres appelables de PIDS / eyecandy / véhicule est consignée dans bench/api-coverage.json du dépôt ; pour l’ascenseur, l’enveloppe Java fait foi.
Après compilation, vous pouvez auditer votre surface d’appels face au véritable jar MTR :
python native/bench/api_coverage.py \
--classes fabric/build/classes/java/main \
--jar path/to/mtr.jar
Les champs internes de MTRClientData sont, comme côté JS, soumis à des garanties de stabilité limitées entre versions ; les méthodes marquées deprecated restent appelables. Privilégiez les données équivalentes des instantanés et enveloppes — plus rapides et plus stables.
Ensuite : ABI C et services de l’hôte — le contrat complet de la frontière du module.