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