Init
This commit is contained in:
190
content/core-concepts/threading.fr.md
Normal file
190
content/core-concepts/threading.fr.md
Normal 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}`
|
||||
Reference in New Issue
Block a user