Files
Documentation/content/core-concepts/threading.fr.md
2026-01-20 20:33:59 +01:00

6.1 KiB

title, type, weight
title type weight
Threading docs 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
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 :

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 :

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<EntityStore> entityRef = playerRef.getReference();  // null si pas dans un monde

Vérifier le Contexte Thread

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

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

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<EntityStore> 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 :

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 :

// 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}