---
title: Opérations Async
type: docs
weight: 2
---

Les opérations asynchrones permettent aux plugins d'effectuer des tâches lourdes sans bloquer le thread de tick du monde.

## Pourquoi Async ?

{{< callout type="info" >}}
Chaque World a un thread de tick dédié qui gère la logique du jeu, les interactions des joueurs et les mises à jour de tick. Le bloquer cause du lag et une mauvaise expérience joueur. Utilisez async pour :
- Les requêtes de base de données
- Les I/O de fichiers
- Les requêtes réseau
- Les calculs complexes
{{< /callout >}}

## Pattern Async de Base

Utilisez `CompletableFuture.runAsync()` et `world.execute()` :

```java
PlayerRef playerRef = player.getPlayerRef();
World world = player.getWorld();

CompletableFuture.runAsync(() -> {
    // Ceci s'exécute sur un thread d'arrière-plan
    // Faire le travail lourd ici
    Data result = computeData();

    // Retourner au thread du monde pour les changements d'état du jeu
    world.execute(() -> {
        playerRef.sendMessage(Message.raw("Résultat : " + result));
    });
});
```

## Sécurité des Threads

### Utiliser PlayerRef

```java
getEventRegistry().register(PlayerConnectEvent.class, event -> {
    PlayerRef playerRef = event.getPlayerRef();
    World world = event.getWorld();

    CompletableFuture.runAsync(() -> {
        // Charger les données de manière asynchrone
        PlayerData data = loadFromDatabase(playerRef.getUuid());

        // Retourner au thread du monde
        world.execute(() -> {
            Ref<EntityStore> ref = playerRef.getReference();
            if (ref != null) {
                applyData(ref, data);
            }
        });
    });
});
```

### Collections Thread-Safe

```java
// Utiliser des collections concurrentes pour les données partagées
private final Map<UUID, PlayerData> playerData = new ConcurrentHashMap<>();
private final Set<UUID> processing = ConcurrentHashMap.newKeySet();

public void processPlayer(PlayerRef playerRef, World world) {
    UUID uuid = playerRef.getUuid();

    // Empêcher le traitement en double
    if (!processing.add(uuid)) {
        return;  // Déjà en traitement
    }

    CompletableFuture.runAsync(() -> {
        try {
            PlayerData data = compute(uuid);
            playerData.put(uuid, data);

            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Traitement terminé !"));
            });
        } finally {
            processing.remove(uuid);
        }
    });
}
```

## Événements Async

Gérer les événements async avec CompletableFuture :

```java
getEventRegistry().registerAsync(AsyncEvent.class, event -> {
    return CompletableFuture.supplyAsync(() -> {
        // Traitement async
        return processEvent(event);
    });
});
```

## Patterns Courants

### Opérations de Base de Données

```java
public void savePlayerData(PlayerRef playerRef, World world) {
    // Capturer les données sur le thread du monde avant de passer en async
    PlayerData data = captureData(playerRef);

    CompletableFuture.runAsync(() -> {
        try {
            database.save(playerRef.getUuid(), data);
            getLogger().at(Level.INFO).log("Données sauvegardées pour " + playerRef.getUsername());
        } catch (Exception e) {
            getLogger().error("Échec de la sauvegarde des données", e);
        }
    });
}

public void loadPlayerData(PlayerRef playerRef, World world) {
    CompletableFuture.runAsync(() -> {
        PlayerData data = database.load(playerRef.getUuid());

        world.execute(() -> {
            Ref<EntityStore> ref = playerRef.getReference();
            if (ref != null && data != null) {
                applyData(ref, data);
                playerRef.sendMessage(Message.raw("Données chargées !"));
            }
        });
    });
}
```

### Requêtes HTTP

```java
public void fetchPlayerStats(PlayerRef playerRef, World world) {
    CompletableFuture.runAsync(() -> {
        try {
            String response = httpClient.get("https://api.example.com/stats/" + playerRef.getUuid());
            Stats stats = parseStats(response);

            world.execute(() -> {
                Ref<EntityStore> ref = playerRef.getReference();
                if (ref != null) {
                    displayStats(ref, stats);
                }
            });
        } catch (Exception e) {
            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Échec de la récupération des stats."));
            });
        }
    });
}
```

### Traitement par Lots

```java
public void processAllPlayers(World world) {
    // Capturer les refs de joueurs sur le thread du monde
    List<PlayerRef> players = new ArrayList<>();
    world.getEntityStore().forEach((ref, store) -> {
        PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
        if (playerRef != null) {
            players.add(playerRef);
        }
    });

    CompletableFuture.runAsync(() -> {
        Map<UUID, Result> results = new HashMap<>();

        for (PlayerRef playerRef : players) {
            results.put(playerRef.getUuid(), computeResult(playerRef));
        }

        world.execute(() -> {
            for (PlayerRef playerRef : players) {
                Ref<EntityStore> ref = playerRef.getReference();
                if (ref != null) {
                    Result result = results.get(playerRef.getUuid());
                    applyResult(ref, result);
                }
            }
        });
    });
}
```

## Gestion des Erreurs

```java
public void safeAsyncOperation(PlayerRef playerRef, World world) {
    CompletableFuture.runAsync(() -> {
        try {
            riskyOperation(playerRef.getUuid());

            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Opération réussie !"));
            });
        } catch (Exception e) {
            getLogger().error("L'opération async a échoué", e);

            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Opération échouée. Veuillez réessayer."));
            });
        }
    });
}
```

## Chaînage des Opérations Async

Utilisez le chaînage `CompletableFuture` pour les opérations async séquentielles :

```java
public void chainedOperations(PlayerRef playerRef, World world) {
    CompletableFuture
        .supplyAsync(() -> {
            // Première opération async
            return fetchFromDatabase(playerRef.getUuid());
        })
        .thenApplyAsync(data -> {
            // Deuxième opération async
            return processData(data);
        })
        .thenAccept(result -> {
            // Retourner au thread du monde avec le résultat final
            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Résultat final : " + result));
            });
        })
        .exceptionally(e -> {
            getLogger().error("La chaîne a échoué", e);
            world.execute(() -> {
                playerRef.sendMessage(Message.raw("Opération échouée."));
            });
            return null;
        });
}
```

## Bonnes Pratiques

{{< callout type="warning" >}}
**Règles Async :**
1. Ne jamais accéder à l'état du jeu directement depuis des threads async
2. Toujours utiliser `PlayerRef`, ne jamais stocker `Player` ou `Ref<EntityStore>`
3. Capturer les données nécessaires avant de passer en async
4. Utiliser `world.execute()` pour retourner au thread du monde
5. Gérer les exceptions correctement
6. Utiliser des collections concurrentes pour les données partagées
{{< /callout >}}

```java
// CORRECT : Capturer les données, traiter en async, appliquer sur le thread du monde
public void correctPattern(PlayerRef playerRef, World world) {
    String username = playerRef.getUsername();  // Capturer sur le thread du monde

    CompletableFuture.runAsync(() -> {
        String result = process(username);

        world.execute(() -> {
            playerRef.sendMessage(Message.raw(result));
        });
    });
}

// MAUVAIS : Utiliser les objets monde directement en async
// public void wrongPattern(Player player, Ref<EntityStore> ref) {
//     CompletableFuture.runAsync(() -> {
//         // NE FAITES PAS ÇA - ceux-ci peuvent être invalides depuis le contexte async
//         String name = player.getDisplayName();
//         player.sendMessage("Bonjour");
//     });
// }
```

## Comparaison avec d'Autres Plateformes

{{< callout type="info" >}}
**Note pour les développeurs d'autres plateformes :**
- Contrairement à Bukkit/Spigot, Hytale n'a PAS de méthodes `runSync()` ou `runLater()`
- Utilisez `CompletableFuture.runAsync()` au lieu des méthodes async spécifiques à la plateforme
- Utilisez `world.execute()` au lieu de `runSync()` pour retourner au thread du monde
- Utilisez `CompletableFuture.delayedExecutor()` pour les tâches différées
- Utilisez `ScheduledExecutorService` pour les tâches répétitives
{{< /callout >}}
