6 min

Scheduling & Tasks

How to schedule code execution, delayed tasks, and async operations in Hytale plugins.

threading required
Most scheduling operations require understanding the threading model. Read the Threading Model page first.

World as Executor

The World class implements java.util.concurrent.Executor. This means you can use standard Java executor patterns to schedule code on the world thread.

1// World implements Executor
2public class World extends TickingThread implements Executor {
3 public void execute(Runnable command); // Queue for next tick
4 public boolean isInThread(); // Check if on world thread
5 public long getTick(); // Current tick number
6 public int getTps(); // Ticks per second (default 20)
7}

Basic Scheduling

Execute on World Thread

Use world.execute() to run code on the world thread. This is the primary way to safely modify entities from event handlers.

1// Execute code on the world thread
2World world = player.getWorld();
3
4world.execute(() -> {
5 // This runs on the world thread
6 // Safe to modify entities here
7 player.setModel("Mouse");
8});

Check Current Thread

1// Check if already on world thread
2if (world.isInThread()) {
3 // Already safe, execute directly
4 player.setModel("Mouse");
5} else {
6 // Need to switch threads
7 world.execute(() -> {
8 player.setModel("Mouse");
9 });
10}

World Tick Information

The world runs at a configurable tick rate (default 20 TPS = 50ms per tick).

1World world = player.getWorld();
2
3// Get current tick
4long currentTick = world.getTick();
5
6// Get TPS setting
7int tps = world.getTps(); // Default: 20
8
9// Change TPS (server-wide effect)
10world.setTps(30); // 30 ticks per second
ticksreal time (at 20 tps)
1 tick50ms
20 ticks1 second
1200 ticks1 minute
24000 ticks20 minutes

Task Registration

For more complex task management, use the TaskRegistry system. This integrates with CompletableFuture and ScheduledFuture.

1// TaskRegistry API
2public class TaskRegistry {
3 // Register a CompletableFuture task
4 public TaskRegistration registerTask(
5 CompletableFuture future
6 );
7
8 // Register a ScheduledFuture task
9 public TaskRegistration registerTask(
10 ScheduledFuture future
11 );
12}
13
14// TaskRegistration - handle to a registered task
15public class TaskRegistration {
16 public Future getTask(); // Get the underlying future
17}

Async Patterns

CompletableFuture Integration

Use Java's CompletableFuture for async operations, then switch back to the world thread for entity modifications.

1// Async operation with CompletableFuture
2CompletableFuture.supplyAsync(() -> {
3 // Do heavy work off the world thread
4 return loadDataFromDatabase(playerId);
5}).thenAccept(data -> {
6 // Back on common pool - still need world.execute()!
7 world.execute(() -> {
8 // Now safe to modify entities
9 applyDataToPlayer(player, data);
10 });
11});

Chunk Loading (Async)

Some operations like chunk loading are inherently async:

1// Async chunk loading
2long chunkKey = /* packed chunk coordinates */;
3
4CompletableFuture future = world.getChunkAsync(chunkKey);
5
6future.thenAccept(chunk -> {
7 if (chunk != null) {
8 // Chunk is loaded
9 world.execute(() -> {
10 // Process chunk on world thread
11 });
12 }
13});

Common Patterns

Delayed Action

1// Simple delayed action using tick counting
2public class DelayedAction {
3 private final World world;
4 private final long targetTick;
5 private final Runnable action;
6
7 public DelayedAction(World world, int delayTicks, Runnable action) {
8 this.world = world;
9 this.targetTick = world.getTick() + delayTicks;
10 this.action = action;
11 }
12
13 // Call this each tick from an event listener
14 public boolean tryExecute() {
15 if (world.getTick() >= targetTick) {
16 world.execute(action);
17 return true; // Action executed
18 }
19 return false; // Still waiting
20 }
21}

Repeating Task

1// Using ScheduledExecutorService for repeating tasks
2import java.util.concurrent.*;
3
4public class RepeatingTask {
5 private final ScheduledExecutorService scheduler =
6 Executors.newSingleThreadScheduledExecutor();
7
8 public void startRepeatingTask(World world, Player player) {
9 scheduler.scheduleAtFixedRate(() -> {
10 // This runs on scheduler thread
11 world.execute(() -> {
12 // Safe entity modification
13 healPlayer(player, 1);
14 });
15 }, 0, 1, TimeUnit.SECONDS); // Every 1 second
16 }
17
18 public void stop() {
19 scheduler.shutdown();
20 }
21}

Best Practices

dodon't
Use world.execute() for entity changesModify entities from scheduler threads directly
Check isInThread() before execute()Assume you're on the world thread
Clean up schedulers in plugin stop()Leave schedulers running after plugin stops
Use CompletableFuture for async workBlock the world thread with long operations
Keep world.execute() blocks shortPut heavy computation inside world.execute()
scheduler cleanup
Always shut down your ScheduledExecutorService instances in your plugin's stop() method to prevent resource leaks.

Quick Reference

SchedulingExample.javajava
1public class SchedulingExample extends JavaPlugin {
2 private ScheduledExecutorService scheduler;
3
4 @Override
5 protected void start() {
6 scheduler = Executors.newSingleThreadScheduledExecutor();
7
8 getEventRegistry().register(
9 PlayerConnectEvent.class,
10 this::onPlayerConnect
11 );
12 }
13
14 @Override
15 protected void stop() {
16 // Always clean up!
17 if (scheduler != null) {
18 scheduler.shutdown();
19 }
20 }
21
22 private void onPlayerConnect(PlayerConnectEvent event) {
23 Player player = event.getPlayer();
24 World world = player.getWorld();
25
26 // Immediate action on world thread
27 world.execute(() -> {
28 player.sendMessage("Welcome!");
29 });
30
31 // Delayed action (5 seconds later)
32 scheduler.schedule(() -> {
33 world.execute(() -> {
34 player.sendMessage("Enjoy your stay!");
35 });
36 }, 5, TimeUnit.SECONDS);
37 }
38}