Performance et débogage
Des scripts rapides et stables : règles du chemin chaud, hygiène mémoire, et où regarder quand ça se passe mal.
Modèle de performance
Structure des coûts par frame et par instance :
| Étape | Coût |
|---|---|
| Marshalling de l’instantané (hôte → module) | l’unique traversée Java imposée : l’hôte écrit l’instantané POD dans les pages partagées / un tampon direct à l’aide de constructeurs réutilisés |
Lecture des données (Vehicle / Pids / …) | simples lectures mémoire, sans coût |
| Écriture des enregistrements de dessin | allocation par incrément (bump) dans la zone, zéro allocation, zéro boxing |
Rastérisation des pixels (Gfx2D) | boucles C++ pures ; chemin rapide src-over pour les couleurs opaques |
| Rejeu par l’hôte | le même chemin de rendu que JS ; le téléversement de textures ne déplace que les rectangles modifiés |
Appel Api | aller-retour encodage JSON + réflexion + décodage JSON — coûteux, à réserver aux canaux basse fréquence |
Règles du chemin chaud
- Pas d’
Apidans render. Les données à haute fréquence sont toutes dans l’instantané ; le dessin à haute fréquence passe par le flux d’enregistrements. Les à-coups GC périodiques de l’ère JS venaient précisément d’« un appel réflexif par frame et par véhicule ». - Créez les ressources uniquement dans
create: modèles, textures, handles d’objets Java, callbacks — construisez-les, stockez-les dansState, etrenderse contente de les utiliser. - Évitez les lectures de ressources / HTTP à chaque frame : le réseau va dans
BackgroundWorker, les résultats dans votre propre cache synchronisé, que render lit. - Téléversez une texture seulement quand son contenu change : le fond statique se dessine une fois ; pour le contenu dynamique, restreignez la zone redessinée avec
push_clipafin que le rectangle modifié ne couvre que les bandes réellement touchées. - Déphasez les objets : carrousels / clignotements de plusieurs instances ne doivent pas bouger tous dans la même frame — un ordonnancement à phases décalées répartit les zones modifiées sur des frames différentes (utilisez
in_game_time()ougame_time_millis()comme horloge). - Mesurez les objets chaînes temporaires : les comparaisons
StrViewévitent toute copie ; en cas de concaténation réellement nécessaire, attention aux allocations destd::string(la SSO dispense d’allocation pour les chaînes courtes).
Mémoire et récupération différée
La discipline mémoire côté hôte (à titre d’information pour les auteurs de scripts ; le SDK en bénéficie automatiquement) :
- Les cinq zones de sortie sont une par module et réutilisées entre frames ; la zone de pixels démarre à 1 Mo, croît à la demande (16 Mo au maximum) et se rétracte de façon déterministe vers son plancher après les pics — fini la résidence fixe de 16 Mo par module.
- Une instance qui sort du frustum n’est pas détruite (elle reste chaude, ré-entrée en vue sans reconstruction) ; elle n’est retirée qu’environ 5 secondes après avoir réellement quitté la liste des véhicules du client.
- Les gros blocs libérés passent par
defer_free(ABI 10) : les tampons de plusieurs méga-octets morts dansrender/disposesont confiés au thread de récupération en arrière-plan de l’hôte, évitant d’imputer tout le coût dufreeà la frame courante et d’amplifier l’attente des points sûrs du GC. Contrat : le bloc doit être mort, sans destructeur, et aucun thread ne doit plus y toucher ensuite (rejeu de l’hôte compris). La rétraction de la zone de pixels du SDK s’y conforme déjà automatiquement ; pour vos propres gros tampons, appelezhost->defer_freeselon les mêmes règles — si l’hôte est trop ancien (emplacement null), une libération synchrone suffit.
Observation et journaux
| Quoi regarder | Où | Contenu |
|---|---|---|
| census des modules | section de démarrage de logs/latest.log | nombre et types de modules chargés ; ligne récapitulative du canal de transport (mentionnant transport: : shared-memory / panama-ffm, etc.) — pour confirmer que shm / FFM est réellement actif et détecter une éventuelle rétrogradation silencieuse |
| raison d’un échec de chargement | ligne census + mode debug | un côté manquant ou en échec (JS ou C++) est simplement ignoré avec un message, l’autre continue de fonctionner |
| garde-fou anti-plantage | latest.log | native script faulted: code=… module disabled — la raison précise de la désactivation du module |
| trafic de nettoyage | latest.log (une ligne par minute) | journal agrégé du rythme de destruction des instances, pour observer directement le comportement de durée de vie |
| octets téléversés | GraphicsTexture::last_upload_bytes() | instrumentation dans votre code : l’efficacité de la stratégie de rectangles modifiés saute aux yeux |
Après modification d’une bibliothèque, F3+T (ou le raccourci du mod « Recharger tous les scripts MTR ») recharge les scripts C++ — déchargement et redécouverte suivent le chemin complet, sans redémarrer le jeu.
Paramètres JVM
| Paramètre | Défaut | Effet |
|---|---|---|
-Djcm.nativeIdleMillis | 90000 | lorsqu’une instance existe mais n’a plus été rendue depuis longtemps, la libération n’intervient qu’au-delà de ce nombre de millisecondes ; 0 = pas de libération sur critère temporel |
-Djcm.nativeIdleMax | 32 | plafond d’instances chaudes ; au-delà, on libère en priorité celles restées le plus longtemps sans rendu ; 0 = pas de plafond |
-Djcm.ffm.disable | désactivé | commutateur de débogage : désactive le chemin de chargement Panama FFM |
Le retrait et la destruction d’instances obéissent en outre à un budget par tick (2 par défaut, la plus ancienne d’abord) et à un vidage ponctuel lors du déchargement du monde / changement de dimension / déconnexion.
Problèmes courants
Le module ne se charge pas du tout ?
- Vérifiez que vous utilisez un jar Release ou Complete Build — les artefacts de la CI rapide ne contiennent pas les ponts natifs et le journal affiche
native bridge unavailable(ce n’est pas un problème de votre module). - Vérifiez le répertoire de plateforme : le système courant ne charge que les bibliothèques de sa plateforme ; une déclaration par racine (stem) sans extension se développe automatiquement en chemins pour chaque plateforme (voir Intégration dans un pack de ressources).
- Vérifiez que
IDest identique, caractère pour caractère, au champidde l’entrée déclarée. - Vérifiez l’ABI : l’hôte accepte 6–11 ; une bibliothèque ABI 8+ exige un hôte 2.3.0-beta-2.yanyang.7 ou supérieur.
Des carrés de substitution (tofu) à l’écran ?
Les glyphes CJK se dégradent quand la police de l’hôte ne couvre pas le point de code — transmettez un id de police de ressource à Text::font() / au constructeur de Gfx2D (par ex. jsblock:shanghai), ou vérifiez que le jeu de polices utilisé contient bien ces caractères.
Chutes de framerate ponctuelles ?
- Vérifiez d’abord s’il s’agit d’un « redessin complet de l’écran à chaque frame » : dessinez le contenu statique une fois, redessinez le contenu dynamique par bandes clippées, et validez avec
last_upload_bytes(). - Vérifiez ensuite si plusieurs instances bougent dans la même frame : déphasez par objet les carrousels / clignotements / animations d’ouverture des portes.
- Enfin, cherchez tout appel
Apidans render (y compris indirect : quelques méthodes de contexte telles quectx.set_auto_z_orderingprovoquent un aller-retour en ABI 8+).
Comportement différent sous Windows ?
Les versions historiques antérieures à l’ABI 6 reposaient sur « une mémoire remplie de zéros directement utilisable comme objet » — cela ne survécut que par hasard à la disposition MSVC. Le contrat mtrInit() du SDK actuel a éradiqué ce problème ; si vous écrivez un hôte personnalisé, appelez impérativement mtrInit() après l’allocation du bloc d’état.
Retour à : l’accueil de la documentation.