--- title: TaskRegistry type: docs weight: 1 --- Le TaskRegistry permet aux plugins d'enregistrer et de suivre les tâches asynchrones. Hytale utilise les APIs de concurrence standard de Java pour la planification des tâches. ## Comprendre TaskRegistry {{< callout type="warning" >}} **Important :** TaskRegistry n'a PAS de méthodes `runAsync()`, `runLater()`, ou `runRepeating()`. Ce sont des idées fausses courantes venant d'autres plateformes. Hytale utilise les APIs standard Java `CompletableFuture` et `ScheduledExecutorService`. {{< /callout >}} ## API TaskRegistry ```java public class TaskRegistry extends Registry { // Enregistrer une tâche CompletableFuture public TaskRegistration registerTask(CompletableFuture task); // Enregistrer une tâche ScheduledFuture public TaskRegistration registerTask(ScheduledFuture task); } ``` Le TaskRegistry suit les tâches pour un nettoyage correct lors de l'arrêt du plugin. ## Tâches Asynchrones Utilisez `CompletableFuture` pour les opérations async : ```java // Exécuter une opération async CompletableFuture task = CompletableFuture.runAsync(() -> { // Calcul lourd, I/O, requêtes réseau String data = fetchFromDatabase(playerId); }); // Enregistrer avec TaskRegistry pour le suivi getTaskRegistry().registerTask(task); // Gérer l'achèvement task.thenAccept(result -> { // Traiter le résultat - NOTE : Ceci s'exécute sur un pool de threads, pas le thread du monde // Utilisez world.execute() pour exécuter du code sur le thread du monde }); ``` ### Retourner au Thread du Monde Après les opérations async, utilisez `world.execute()` pour retourner au thread du monde : ```java PlayerRef playerRef = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture task = CompletableFuture.runAsync(() -> { // Charger depuis la base de données (I/O bloquant est OK ici) PlayerData data = database.load(playerRef.getUuid()); // Retourner au thread du monde pour les changements d'état du jeu world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null) { Player p = ref.getStore().getComponent(ref, Player.getComponentType()); if (p != null) { applyData(p, data); playerRef.sendMessage(Message.raw("Données chargées !")); } } }); }); getTaskRegistry().registerTask(task); ``` ## Tâches Différées Utilisez `CompletableFuture.delayedExecutor()` pour les délais : ```java import java.util.concurrent.TimeUnit; // Exécuter après 3 secondes CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS) .execute(() -> { world.execute(() -> { playerRef.sendMessage(Message.raw("3 secondes se sont écoulées !")); }); }); ``` ## Tâches Répétitives Utilisez `ScheduledExecutorService` pour les tâches répétitives : ```java import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class MyPlugin extends JavaPlugin { private ScheduledExecutorService scheduler; @Override public void start() { scheduler = Executors.newSingleThreadScheduledExecutor(); // Exécuter toutes les 5 minutes ScheduledFuture saveTask = scheduler.scheduleAtFixedRate( () -> { saveAllData(); getLogger().at(Level.INFO).log("Auto-sauvegarde terminée"); }, 5, // Délai initial 5, // Période TimeUnit.MINUTES ); // Enregistrer pour le suivi getTaskRegistry().registerTask((ScheduledFuture) saveTask); } @Override public void shutdown() { if (scheduler != null) { scheduler.shutdown(); } } } ``` ## Timing Basé sur les Ticks Pour un timing basé sur les ticks, suivez les ticks manuellement dans un gestionnaire de tick du monde : ```java public class TickTimerPlugin extends JavaPlugin { private final Map timers = new ConcurrentHashMap<>(); public void scheduleAfterTicks(String id, int ticks, Runnable action) { timers.put(id, new TickTimer(ticks, action)); } // Appeler depuis votre gestionnaire de tick public void onWorldTick() { timers.entrySet().removeIf(entry -> { TickTimer timer = entry.getValue(); timer.ticksRemaining--; if (timer.ticksRemaining <= 0) { timer.action.run(); return true; // Supprimer } return false; }); } private static class TickTimer { int ticksRemaining; Runnable action; TickTimer(int ticks, Runnable action) { this.ticksRemaining = ticks; this.action = action; } } } ``` ## Patterns Courants ### Compte à Rebours ```java public void startCountdown(PlayerRef playerRef, int seconds) { AtomicInteger remaining = new AtomicInteger(seconds); World world = Universe.get().getWorld(playerRef.getWorldUuid()); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); ScheduledFuture countdown = scheduler.scheduleAtFixedRate(() -> { int count = remaining.decrementAndGet(); world.execute(() -> { if (count > 0) { playerRef.sendMessage(Message.raw("Démarrage dans : " + count)); } else { playerRef.sendMessage(Message.raw("Go !")); scheduler.shutdown(); } }); }, 0, 1, TimeUnit.SECONDS); } ``` ### Chargement de Données Async ```java public void loadPlayerData(PlayerRef playerRef, World world) { CompletableFuture.runAsync(() -> { // Charger depuis la base de données (I/O bloquant est OK ici) PlayerData data = database.load(playerRef.getUuid()); // Retourner au thread du monde world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null) { applyData(ref, data); playerRef.sendMessage(Message.raw("Données chargées !")); } }); }); } ``` ## Conversion de Temps Hytale tourne à **30 TPS** (ticks par seconde) : | Temps | Ticks (à 30 TPS) | Millisecondes | |-------|------------------|---------------| | 1 tick | 1 | ~33ms | | 1 seconde | 30 | 1 000ms | | 5 secondes | 150 | 5 000ms | | 1 minute | 1 800 | 60 000ms | | 5 minutes | 9 000 | 300 000ms | ## Bonnes Pratiques {{< callout type="info" >}} **Directives pour les Tâches :** - Utilisez `CompletableFuture.runAsync()` pour les opérations I/O, base de données et réseau - Utilisez `world.execute()` pour retourner au thread du monde pour les changements d'état du jeu - Utilisez `PlayerRef` dans les tâches async, pas `Player` ou `Ref` - Enregistrez les tâches longue durée avec `getTaskRegistry()` pour le nettoyage - Arrêtez les instances `ScheduledExecutorService` personnalisées dans `shutdown()` {{< /callout >}} {{< callout type="warning" >}} **Thread Safety :** Chaque World s'exécute sur son propre thread. Utilisez toujours `world.execute()` ou vérifiez `world.isInThread()` avant de modifier l'état du jeu depuis du code async. {{< /callout >}} ```java // Bien : Pattern async correct avec PlayerRef PlayerRef ref = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture.runAsync(() -> { String result = heavyComputation(); world.execute(() -> { ref.sendMessage(Message.raw(result)); }); }); // Mauvais : Utiliser Player directement en async // CompletableFuture.runAsync(() -> { // player.sendMessage("Pas sûr !"); // NE FAITES PAS ÇA // }); ```