--- 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 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 playerData = new ConcurrentHashMap<>(); private final Set 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 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 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 players = new ArrayList<>(); world.getEntityStore().forEach((ref, store) -> { PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); if (playerRef != null) { players.add(playerRef); } }); CompletableFuture.runAsync(() -> { Map results = new HashMap<>(); for (PlayerRef playerRef : players) { results.put(playerRef.getUuid(), computeResult(playerRef)); } world.execute(() -> { for (PlayerRef playerRef : players) { Ref 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` 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 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 >}}