Extensions Yanyang

Aperçu des extensions

ExtensionPoint d’entréeEn résumé
Entrées mixtes JS + C++entrées de déclarationmélangez librement modules JS et C++ dans une même entrée ; les deux moteurs rendent simultanément
Mémoire du sens d’ouverture des portesVehicleWrapper.getDirectionOfDoorOpening()quel côté s’ouvrira à l’arrivée — à quai, en prédiction sur ligne principale, au-delà de la distance de rendu, c’est le serveur qui s’en souvient
Subdivision des types de transportVehicleWrapper.getTransportType(), etc.id de type étendu des lignes (en lien avec MAGIC MTR) et champs d’extension des correspondances
Téléversement de pixels par écriture directeGraphicsTexture.uploadRawABGR()écriture directe d’un tableau d’octets + téléversement de la seule zone modifiée : le canal économe en bande passante des mises à jour pixel par pixel à haute fréquence
Alias de compatibilitéentrées de déclarationaccepte le nom de clé script_input des exemples de la documentation NTE ; nativeLibrary / nativeLibraries cohabite avec du .js

Entrées mixtes JS + C++

Dans le format amont, une entrée de script est intégralement en JS. Yanyang confie la décision aux caractéristiques du fichier : chaque élément de scriptLocations est examiné isolément — *.js (insensible à la casse) part côté JavaScript, tout le reste est analysé comme module C++. Au sein d’une même entrée, mélangez les deux à volonté, sans limite de nombre :

"vehicleScripts": [
    {
        "id": "demo:sp1900",
        "scriptLocations": [
            "demo:js/sp1900_ui.js",
            "demo:natives/sp1900_motor",
            {"windows": "demo:natives/win-x64/libmotor.dll",
             "linux":   "demo:natives/linux-x64/libmotor.so",
             "macos":   "demo:natives/macos-arm64/libmotor.dylib"}
        ],
        "prependExpressions": ["print('JS 侧照常执行')"],
        "input": { "era": 2004 }
    }
]

La déclaration C++ prend trois formes :

FormeÉcritureRésolution
stem indépendant de la plateforme"demo:natives/sp1900_motor"se développe pour chaque plateforme : natives/linux-x64/libsp1900_motor.so, natives/windows-x64/sp1900_motor.dll, natives/macos-arm64/libsp1900_motor.dylib… (l’agencement des répertoires est détaillé dans Démarrage rapide)
chemin complet de bibliothèque"demo:natives/linux-x64/libfoo.so"lié au système correspondant d’après l’extension ; les autres systèmes ignorent l’élément
correspondance par plateforme{"windows": ..., "linux": ..., "macos": ...}les clés peuvent descendre à l’architecture (macos-arm64, linux-x64…), les clés précises ont priorité sur les clés génériques ; la valeur est elle-même un stem ou un chemin complet

Chaque moteur dessine de son côté : la partie JS (avec ses prependExpressions et son input) est assemblée en un script analysé normalement ; la partie C++ tout entière est chargée par le gestionnaire natif sous le même id de script. Au rendu, chaque camp capture et rejoue ses instructions de dessin, sans jamais se mélanger ; si un côté manque ou échoue au chargement, ce côté ne dessine simplement pas — l’autre n’en souffre pas.

Le champ language (la solution de repli)

ValeurEffet
absent (recommandé)détection automatique d’après les caractéristiques du fichier — zéro configuration dans l’immense majorité des cas
"cpp"entrée purement native (ancienne sémantique) : les .js de scriptLocations sont ignorés, avec un message laissé dans le journal ; à utiliser avec nativeLibrary / nativeLibraries
"javascript" / "js"force l’analyse de tout en JS — la porte de sortie pour les noms de fichiers JS sans extension

Le nom du champ dépend du format : language pour MTR 4, scriptLang (camelCase) / script_lang (snake_case) pour les anciens formats. nativeLibrary / nativeLibraries (ce dernier accroche plusieurs bibliothèques à un même id) est toujours traité comme C++, et cohabite aussi avec du .js dans une même entrée.

Relation avec les entrées JS pures

La signification des champs JS prependExpressions / input / rateLimit / dataFetchMode, etc. ne change pas ; une entrée JS pure (uniquement des .js) se comporte exactement comme en amont. La façon dont le côté C++ lit input est décrite dans la documentation C++.

Mémoire du sens d’ouverture des portes

VehicleWrapper.getDirectionOfDoorOpening(): String répond à « quel côté ce train ouvrira-t-il » ; valeurs : "not" (aucun côté) / "left" (gauche de la tête) / "right" (droite de la tête) / "all" (deux côtés) :

  • À quai : que les portes soient non ouvertes, entrouvertes, en fermeture ou fermées, la réponse est la même que portes entièrement ouvertes — le côté « s’ouvrira » est celui où se trouvent les blocs de quai (ou les portes PSD / APG déverrouillées). Quand la valeur d’ouverture est > 0, on lit directement l’évaluation voiture par voiture de la frame courante.
  • Sur ligne principale : prédiction du côté d’ouverture à la prochaine station — le train est repositionné à son point d’arrêt sur son propre chemin, puis analysé avec le même balayage de blocs canOpenDoors que MTR. Disponible dès que le chunk du point d’arrêt est chargé ; au-delà de la distance de rendu, c’est le serveur qui répond : le serveur a balayé toute la carte une fois et persisté le résultat (toute création / suppression / modification de bloc de quai déclenche une réanalyse incrémentielle), et sert depuis un cache par ligne les réponses des deux côtés — embarquement en cours de route, redémarrage du jeu ou première visite n’y changent rien.
  • Serveur sans ce mod ou sans réponse : repli sur le balayage du client dans sa distance de rendu — la prédiction pour des quais lointains peut alors manquer.
  • Règle d’agrégation : une seule voiture dont une porte jouxte un bloc de quai suffit à marquer tout le côté comme ouvrable (agrégation par côté, pas par porte). Gauche et droite se rapportent à la tête (extrémité bogie 1) : chaque voiture est évaluée dans son propre repère (-X = gauche de la tête, convention des modèles MTR) ; le sens de marche comme le retournement ne modifient jamais la lecture.
  • Purement consultatif : ne sert que la logique d’affichage (indicateur du côté d’ouverture, flèches des écrans de bord), sans intervenir sur l’ouverture réelle des portes par MTR.
Exemple : voyants de côté d’ouverture
function render(ctx, state, vehicle) {
    let dir = vehicle.getDirectionOfDoorOpening();   // "not"/"left"/"right"/"all"
    if (dir == "left" || dir == "all") drawLamp(ctx, state, "left", 0x00FF00);
    if (dir == "right" || dir == "all") drawLamp(ctx, state, "right", 0x00FF00);
}

Subdivision des types de transport

Le TransportMode de MTR n’offre que quatre modes de base (train / boat / cable_car / airplane), insuffisant pour l’affichage. L’addon MAGIC MTR permet au joueur de subdiviser encore le type d’affichage des « trains » (conservés dans son route_types.json, indexé par id de ligne). Cette branche fait circuler ces types jusqu’aux scripts :

  • VehicleWrapper.getTransportType(): String — l’id de type canonique de la ligne courante : la subdivision MAGIC attribuée (train_normal / train_light_rail / train_high_speed / train_metro / train_bus / train_tram / train_sbahn) si elle existe, sinon repli sur l’id du mode de base (train / boat / cable_car / airplane), unknown pour l’inconnu — jamais null.
  • Stop.routeInterchanges[i] (RouteInterchange) s’enrichit : getTransportMode(): TransportMode? (mode de transport de la ligne de correspondance, null possible avec d’anciennes données), getTransportType(): String (le même id canonique), getRouteId(): long (id de la ligne d’origine, 0 si la donnée manque côté serveur) — les panneaux de correspondance peuvent enfin choisir icône / couleur selon le type, au lieu de ne lire que la couleur.
  • Sans MAGIC installé, ou sans subdivision attribuée à la ligne, tout retombe comme avant sur le mode de base — le script n’a pas à détecter la présence de l’addon. Le résultat des requêtes de type est mis en cache brièvement par ligne (environ 1 seconde) : un type modifié dans MTR est visible très vite.

Autres points d’appel

L’id de type canonique n’est pas l’apanage de vehicle.getTransportType() — l’hôte l’appelle aussi en interne, et les scripts disposent d’entrées sœurs :

Point d’appelCôtéDétails
vehicle.getTransportType()JS · scripts de véhiculeL’entrée principale ci-dessus ; le constructeur d’instantanés de l’hôte l’appelle aussi — une fois par véhicule et par frame — dès que le véhicule embarque un script C++, et place la chaîne dans le pool de chaînes de l’instantané
stop.routeInterchanges[i].getTransportType()
stop.connectingInterchanges[nomDeStationConnectée][i].getTransportType()
JS · scripts de véhiculeLe même id pour chaque correspondance ; en ligne, le type arrive pré-résolu avec les données de correspondance synchronisées (l’affectation du serveur prime — reçu que le client ait MAGIC ou non), la requête client via getRouteId() ne servant de repli qu’aux anciennes données
mtr::Vehicle::getTransportType() (ancien nom mtr::Train)
mtr::Stop::interchange(i).getTransportType()
Instantané C++ · ABI 9 et plusLisent la chaîne que l’hôte a préalablement placée dans le pool de l’instantané — identique au résultat JS ; voir Instantanés de données

Tous ces points d’appel lisent la même résolution par ligne — l’id vu à un endroit est exactement celui vu aux autres.

Téléversement de pixels par écriture directe

GraphicsTexture.uploadRawABGR(source: byte[], dstOffsetX: int, dstOffsetY: int, width: int, height: int, ppm: int[]): void

Écrit directement dans le tampon de texture un tableau de pixels de width × height × 4 octets, en ordre des lignes, composantes en ordre BGRA, puis ne téléverse vers le GPU que ce rectangle :

  • Positionnement : le canal économe en bande passante des mises à jour pixel par pixel à haute fréquence. Le upload() de la voie AWT ordinaire déplace l’image entière ; un écran de 4 Mpx intégralement transféré à chaque frame, ce sont plus de dix Mo de copie, alors qu’une animation point à point (barre de progression, chiffres qui s’animent) ne modifie le plus souvent qu’une bande — ici, quelques centaines de Ko suffisent.
  • ppm est une table de recherche de prémultiplication de 4 × 256 entrées (quatre tranches de 256 : B / G / R / A dans l’ordre), détenue par l’appelant ; les octets sont développés via la table au moment de l’écriture, évitant une multiplication par pixel.
  • Après écriture, seul ce rectangle est téléversé automatiquement, le reste de la texture demeure intact — un pas de gagné sur upload(x, y, w, h), qui commence par « modifier sur le canevas AWT ».
  • Utilisable depuis JS (assemblez le tableau d’octets avec Java.newArray), mais la raison d’être est le retour des pixels de frame du pont C++ ; les auteurs purement JS se contentent d’AWT + téléversement de sous-zone.

Parité de vocabulaire JS / C++

Les mêmes données portent le même nom dans les deux langages — quand les deux côtés d’une entrée mixte dessinent la même chose, personne ne perd le nord :

JSC++ (SDK)Introduit en
vehicle.getTransportType()mtr::Vehicle::getTransportType()ABI 9
Stop.routeInterchanges[i].getTransportType(), etc.le champ de type de mtr::Stop::interchange(i)ABI 9
vehicle.getDirectionOfDoorOpening()mtr::Vehicle::doorDirection() / getDirectionOfDoorOpening()ABI 11

Le lecteur purement JS n’a qu’une chose à retenir : les extensions de cette page existent aussi côté C++ (lecture directe des champs de l’instantané, coût de marshalling nul) — voir Instantanés de données.


Retour à : Types de scripts JS · API communes JS ; les mêmes données sans le moindre coût JVM ? Voyez les scripts natifs C++.