--- title: TaskRegistry type: docs weight: 1 --- The TaskRegistry allows plugins to register and track asynchronous tasks. Hytale uses Java's standard concurrency APIs for task scheduling. ## Understanding TaskRegistry {{< callout type="warning" >}} **Important:** TaskRegistry does NOT have `runAsync()`, `runLater()`, or `runRepeating()` methods. These are common misconceptions from other platforms. Hytale uses Java's standard `CompletableFuture` and `ScheduledExecutorService` APIs. {{< /callout >}} ## TaskRegistry API ```java public class TaskRegistry extends Registry { // Register a CompletableFuture task public TaskRegistration registerTask(CompletableFuture task); // Register a ScheduledFuture task public TaskRegistration registerTask(ScheduledFuture task); } ``` The TaskRegistry tracks tasks for proper cleanup during plugin shutdown. ## Asynchronous Tasks Use `CompletableFuture` for async operations: ```java // Run async operation CompletableFuture task = CompletableFuture.runAsync(() -> { // Heavy computation, I/O, network requests String data = fetchFromDatabase(playerId); }); // Register with TaskRegistry for tracking getTaskRegistry().registerTask(task); // Handle completion task.thenAccept(result -> { // Process result - NOTE: This runs on a thread pool, not the world thread // Use world.execute() to run code on the world thread }); ``` ### Returning to World Thread After async operations, use `world.execute()` to return to the world's thread: ```java PlayerRef playerRef = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture task = CompletableFuture.runAsync(() -> { // Load from database (blocking I/O is OK here) PlayerData data = database.load(playerRef.getUuid()); // Return to world thread for game state changes 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("Data loaded!")); } } }); }); getTaskRegistry().registerTask(task); ``` ## Delayed Tasks Use `CompletableFuture.delayedExecutor()` for delays: ```java import java.util.concurrent.TimeUnit; // Run after 3 seconds CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS) .execute(() -> { world.execute(() -> { playerRef.sendMessage(Message.raw("3 seconds have passed!")); }); }); ``` ## Repeating Tasks Use `ScheduledExecutorService` for repeating tasks: ```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(); // Run every 5 minutes ScheduledFuture saveTask = scheduler.scheduleAtFixedRate( () -> { saveAllData(); getLogger().at(Level.INFO).log("Auto-save complete"); }, 5, // Initial delay 5, // Period TimeUnit.MINUTES ); // Register for tracking getTaskRegistry().registerTask((ScheduledFuture) saveTask); } @Override public void shutdown() { if (scheduler != null) { scheduler.shutdown(); } } } ``` ## Tick-Based Timing For tick-based timing, track ticks manually in a world tick handler: ```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)); } // Called from your tick handler public void onWorldTick() { timers.entrySet().removeIf(entry -> { TickTimer timer = entry.getValue(); timer.ticksRemaining--; if (timer.ticksRemaining <= 0) { timer.action.run(); return true; // Remove } return false; }); } private static class TickTimer { int ticksRemaining; Runnable action; TickTimer(int ticks, Runnable action) { this.ticksRemaining = ticks; this.action = action; } } } ``` ## Common Patterns ### Countdown Timer ```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("Starting in: " + count)); } else { playerRef.sendMessage(Message.raw("Go!")); scheduler.shutdown(); } }); }, 0, 1, TimeUnit.SECONDS); } ``` ### Async Data Loading ```java public void loadPlayerData(PlayerRef playerRef, World world) { CompletableFuture.runAsync(() -> { // Load from database (blocking I/O is OK here) PlayerData data = database.load(playerRef.getUuid()); // Return to world thread world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null) { applyData(ref, data); playerRef.sendMessage(Message.raw("Data loaded!")); } }); }); } ``` ## Time Conversion Hytale runs at **30 TPS** (ticks per second): | Time | Ticks (at 30 TPS) | Milliseconds | |------|-------------------|--------------| | 1 tick | 1 | ~33ms | | 1 second | 30 | 1,000ms | | 5 seconds | 150 | 5,000ms | | 1 minute | 1,800 | 60,000ms | | 5 minutes | 9,000 | 300,000ms | ## Best Practices {{< callout type="info" >}} **Task Guidelines:** - Use `CompletableFuture.runAsync()` for I/O, database, and network operations - Use `world.execute()` to return to the world thread for game state changes - Use `PlayerRef` in async tasks, not `Player` or `Ref` - Register long-running tasks with `getTaskRegistry()` for cleanup - Shut down custom `ScheduledExecutorService` instances in `shutdown()` {{< /callout >}} {{< callout type="warning" >}} **Thread Safety:** Each World runs on its own thread. Always use `world.execute()` or check `world.isInThread()` before modifying game state from async code. {{< /callout >}} ```java // Good: Proper async pattern with PlayerRef PlayerRef ref = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture.runAsync(() -> { String result = heavyComputation(); world.execute(() -> { ref.sendMessage(Message.raw(result)); }); }); // Bad: Using Player directly in async // CompletableFuture.runAsync(() -> { // player.sendMessage("Not safe!"); // DON'T DO THIS // }); ```