7.2 KiB
title, type, weight
| title | type | weight |
|---|---|---|
| TaskRegistry | docs | 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
public class TaskRegistry extends Registry<TaskRegistration> {
// Register a CompletableFuture task
public TaskRegistration registerTask(CompletableFuture<Void> task);
// Register a ScheduledFuture task
public TaskRegistration registerTask(ScheduledFuture<Void> task);
}
The TaskRegistry tracks tasks for proper cleanup during plugin shutdown.
Asynchronous Tasks
Use CompletableFuture for async operations:
// Run async operation
CompletableFuture<Void> 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:
PlayerRef playerRef = player.getPlayerRef();
World world = player.getWorld();
CompletableFuture<Void> 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<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("Data loaded!"));
}
}
});
});
getTaskRegistry().registerTask(task);
Delayed Tasks
Use CompletableFuture.delayedExecutor() for delays:
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:
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<Void>) 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:
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));
}
// 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
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
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<EntityStore> 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
PlayerRefin async tasks, notPlayerorRef<EntityStore> - Register long-running tasks with
getTaskRegistry()for cleanup - Shut down custom
ScheduledExecutorServiceinstances inshutdown(){{< /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 >}}
// 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
// });