Init
This commit is contained in:
260
content/core-concepts/tasks/task-registry.fr.md
Normal file
260
content/core-concepts/tasks/task-registry.fr.md
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
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<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 :
|
||||
|
||||
```java
|
||||
// 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 :
|
||||
|
||||
```java
|
||||
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 :
|
||||
|
||||
```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<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 :
|
||||
|
||||
```java
|
||||
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
|
||||
|
||||
```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<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 >}}
|
||||
|
||||
```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
|
||||
// });
|
||||
```
|
||||
Reference in New Issue
Block a user