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