191 lines
6.1 KiB
Markdown
191 lines
6.1 KiB
Markdown
---
|
|
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}`
|