Init
This commit is contained in:
295
content/core-concepts/tasks/async-operations.fr.md
Normal file
295
content/core-concepts/tasks/async-operations.fr.md
Normal file
@@ -0,0 +1,295 @@
|
||||
---
|
||||
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 >}}
|
||||
Reference in New Issue
Block a user