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
PlayerRefpour passer des références de joueurs à travers les limites async - Vérifiez
world.isInThread()avant de modifier l'état du monde - Utilisez
CompletableFuturepour 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 :
- Vérifiez
world.isInThread()avant les modifications de monde - Utilisez
PlayerRefau lieu de références directesPlayerpour le travail async - Loguez le thread courant :
Thread.currentThread().getName() - Les noms de threads de monde suivent le pattern :
World-{worldName}