This commit is contained in:
2026-01-20 20:33:59 +01:00
commit b16a40e431
583 changed files with 87339 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
---
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<EntityStore> 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<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` :
```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}`