--- title: Threading type: docs weight: 4 --- Comprendre le threading dans Hytale est crucial pour écrire des plugins sûrs et performants. Contrairement à de nombreux serveurs de jeu, Hytale utilise un **modèle de threading par monde** où chaque monde s'exécute sur son propre thread dédié. ## Threading Par Monde Chaque `World` dans Hytale étend `TickingThread` et s'exécute sur son propre thread dédié à 30 TPS (ticks par seconde). Cela signifie : - Chaque monde tick indépendamment - Les opérations de monde doivent être appelées depuis le thread de ce monde - Les joueurs dans différents mondes s'exécutent sur différents threads ```java World world = player.getWorld(); // Vérifier si on est sur le thread de ce monde if (world.isInThread()) { // Sûr de modifier l'état du monde world.setBlock(position, blockType); } ``` {{< callout type="warning" >}} N'effectuez jamais d'opérations bloquantes (I/O, réseau, base de données) sur un thread de monde. Cela causera du lag pour ce monde. {{< /callout >}} ## Opérations Asynchrones Pour les opérations longues, utilisez `CompletableFuture` : ```java PlayerRef ref = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture.supplyAsync(() -> { // Ceci s'exécute sur un worker thread return loadDataFromDatabase(ref.getUuid()); }).thenAccept(data -> { // PlayerRef.sendMessage() est thread-safe ref.sendMessage(Message.raw("Données chargées : " + data)); }); ``` ## Classes Thread-Safe Certaines classes Hytale sont conçues pour la sécurité des threads : | Classe | Sécurité Thread | |--------|-----------------| | `PlayerRef` | Sûr à utiliser entre threads | | `World` | Doit être accédé sur son propre thread (`world.isInThread()`) | | `Entity` | Doit être accédé sur le thread de son monde | | `ItemStack` | Immuable, thread-safe | ### PlayerRef `PlayerRef` est une référence persistante et thread-safe vers un joueur : ```java import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.Message; // Stocker la référence (thread-safe) PlayerRef playerRef = player.getPlayerRef(); // Sûr d'accéder sur n'importe quel thread : UUID uuid = playerRef.getUuid(); String username = playerRef.getUsername(); String language = playerRef.getLanguage(); // Obtenir l'UUID du monde actuel (peut changer si le joueur change de monde) UUID worldUuid = playerRef.getWorldUuid(); // Envoyer un message directement (thread-safe) playerRef.sendMessage(Message.raw("Bonjour !")); // Pour les opérations ECS, obtenir la référence d'entité Ref entityRef = playerRef.getReference(); // null si pas dans un monde ``` ### Vérifier le Contexte Thread ```java World world = player.getWorld(); // Vérifier si on est sur le thread du monde if (!world.isInThread()) { throw new IllegalStateException("Doit être appelé depuis le thread du monde !"); } // Debug : afficher le thread courant System.out.println("Thread courant : " + Thread.currentThread().getName()); ``` ## Patterns Courants ### Opérations Base de Données ```java public void savePlayer(Player player) { PlayerRef ref = player.getPlayerRef(); PlayerData data = collectPlayerData(player); CompletableFuture.runAsync(() -> { // S'exécute sur un worker thread database.save(ref.getUuid(), data); }).thenRun(() -> { // Notifier le joueur (PlayerRef.sendMessage est thread-safe) ref.sendMessage(Message.raw("Sauvegardé !")); }); } ``` ### Chargement des Données à la Connexion ```java getEventRegistry().register(PlayerConnectEvent.class, event -> { PlayerRef ref = event.getPlayerRef(); World world = event.getWorld(); CompletableFuture.supplyAsync(() -> { // Charger les données sur un worker thread return database.load(ref.getUuid()); }).thenAccept(data -> { if (world != null && data != null) { world.execute(() -> { // Retour sur le thread du monde - accès ECS sécurisé Ref entityRef = ref.getReference(); if (entityRef != null) { applyData(entityRef, data); } }); } }); }); ``` ## Événements Async Certains événements supportent la gestion asynchrone. Les événements avec une clé typée (comme `PlayerChatEvent` qui a une clé `String`) doivent utiliser `registerAsyncGlobal` : ```java getEventRegistry().registerAsyncGlobal( PlayerChatEvent.class, future -> future.thenApply(event -> { // Ceci s'exécute de façon asynchrone // Peut effectuer des opérations lentes ici String filtered = filterMessage(event.getContent()); event.setContent(filtered); return event; }) ); ``` ## Opérations Inter-Mondes Lors du travail avec des entités entre mondes ou du transfert de joueurs : ```java // Le transfert de joueur retourne CompletableFuture PlayerRef ref = player.getPlayerRef(); World targetWorld = universe.getWorld("target_world"); // Note : les opérations joueur qui changent de monde utilisent des patterns async // Toujours utiliser PlayerRef pour suivre le joueur entre changements de monde ``` ## Bonnes Pratiques {{< callout type="tip" >}} - Utilisez toujours `PlayerRef` pour passer des références de joueurs à travers les limites async - Vérifiez `world.isInThread()` avant de modifier l'état du monde - Utilisez `CompletableFuture` pour les opérations async base de données/réseau - Gardez les opérations du thread de monde rapides (< 33ms par tick) - Utilisez des pools de connexions pour l'accès base de données - Rappelez-vous : chaque monde a son propre thread - pas de "thread principal" unique {{< /callout >}} ## Déboguer les Problèmes de Threading Si vous rencontrez des problèmes de threading : 1. Vérifiez `world.isInThread()` avant les modifications de monde 2. Utilisez `PlayerRef` au lieu de références directes `Player` pour le travail async 3. Loguez le thread courant : `Thread.currentThread().getName()` 4. Les noms de threads de monde suivent le pattern : `World-{worldName}`