Extensions Yanyang
Ce que cette branche (le JCM modifié par l’équipe technique Yanyang) ajoute aux API JS de JCM 2.3.0-prerelease.3 amont. Tout ce qui existe déjà côté amont fonctionne comme avant ; cette page ne consigne que les apports incrémentaux.
Aperçu des extensions
| Extension | Point d’entrée | En résumé |
|---|---|---|
| Entrées mixtes JS + C++ | entrées de déclaration | mé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 portes | VehicleWrapper.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 transport | VehicleWrapper.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 directe | GraphicsTexture.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éclaration | accepte 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 | Écriture | Ré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)
| Valeur | Effet |
|---|---|
| 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.
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
canOpenDoorsque 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.
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),unknownpour l’inconnu — jamais null.Stop.routeInterchanges[i](RouteInterchange) s’enrichit :getTransportMode(): TransportMode?(mode de transport de la ligne de correspondance,nullpossible 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’appel | Côté | Détails |
|---|---|---|
vehicle.getTransportType() | JS · scripts de véhicule | L’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éhicule | Le 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 plus | Lisent 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. ppmest 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 :
| JS | C++ (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++.