Files
Documentation/content/core-concepts/tasks/task-registry.fr.md
2026-01-20 20:33:59 +01:00

7.7 KiB

title, type, weight
title type weight
TaskRegistry docs 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

public class TaskRegistry extends Registry<TaskRegistration> {
    // Enregistrer une tâche CompletableFuture
    public TaskRegistration registerTask(CompletableFuture<Void> task);

    // Enregistrer une tâche ScheduledFuture
    public TaskRegistration registerTask(ScheduledFuture<Void> 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 :

// Exécuter une opération async
CompletableFuture<Void> 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 :

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

CompletableFuture<Void> 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<EntityStore> 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 :

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 :

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<Void>) 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 :

public class TickTimerPlugin extends JavaPlugin {
    private final Map<String, TickTimer> 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

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

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<EntityStore> 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<EntityStore>
  • 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 >}}

// 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
// });