mtr::Api et interopérabilité Java
Interdit sur le chemin chaud

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

BesoinCanal
État frame par frame des véhicules / arrivées / ascenseurs / eyecandyenveloppes d’instantané (sans coût)
Dessin de texte / textures / modèles / sonsflux 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 framesApi (appel dans create, handles stockés dans State)
Requêtes réseau, fichiers, BackgroundWorker, gestionnaire de sons et autres interfaces profondesApi (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 ApiRô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 ObjectRô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)
Sémantique des handles

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++

JavaScriptC++
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 = vobj.get("field") / obj.set("field", v)
TransportMode.BOATapi.get("TransportMode", "BOAT")
SCRIPT_INPUTapi.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.upload en direct exige toujours le thread de rendu Java (appelez-le depuis create ou 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
Stabilité entre versions

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.