Scheduler & Tasks
Dokumentace k plánování úloh a periodickým taskům.
Obsah
| Soubor | Popis |
|--------|-------|
| COMPLETABLE_FUTURE.md | CompletableFuture, CompletableFutureUtil, async patterns, error handling |
| SCHEDULED_TASKS.md | TaskRegistry, ScheduledExecutorService, TickingThread, periodické tasky |
---
Přehled
Hytale běží na více vláknech, takže je důležité správně plánovat tasky:
Scheduler Thread - Async tasky, periodické úlohy
World Thread - Game logic, komponenty
Network Thread - Síťová komunikace
---
CompletableFuture (Doporučeno)
Jednorázový Async Task
CompletableFuture.runAsync(() -> {
// Těžká práce (I/O, databáze)
saveToDatabase();
});
S Callback na World Thread
CompletableFuture.runAsync(() -> {
// Async práce
PlayerData data = loadFromDatabase(uuid);
return data;
}).thenAccept(data -> {
// Callback - stále na async threadu!
// Pro komponenty potřebuješ world.execute()
world.execute(() -> {
applyData(player, data);
});
});
Řetězení
CompletableFuture
.supplyAsync(() -> {
// Krok 1: Načti data
return loadData();
})
.thenApply(data -> {
// Krok 2: Transformuj
return processData(data);
})
.thenAccept(result -> {
// Krok 3: Použij výsledek
world.execute(() -> {
applyResult(result);
});
})
.exceptionally(error -> {
// Error handling
getLogger().at(Level.SEVERE).withCause(error).log("Task failed:");
return null;
});
---
ScheduledExecutorService
Vytvoření Scheduleru
public class MyPlugin extends JavaPlugin { private ScheduledExecutorService scheduler;
@Override
protected void setup() {
// Vytvoř scheduler s jedním vláknem
scheduler = Executors.newSingleThreadScheduledExecutor();
}
@Override
protected void shutdown() {
// DŮLEŽITÉ: Ukonči scheduler při shutdown
if (scheduler != null) {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
}
}
}
}
Periodický Task
// Každých 5 minut
scheduler.scheduleAtFixedRate(
this::autoSave,
5, // Initial delay
5, // Period
TimeUnit.MINUTES
);private void autoSave() {
getLogger().atInfo().log("Auto-saving...");
for (Player player : Universe.get().getPlayers()) {
savePlayerAsync(player.getUuid());
}
}
Delayed Task
// Jednou za 30 sekund
scheduler.schedule(
this::delayedTask,
30,
TimeUnit.SECONDS
);private void delayedTask() {
getLogger().atInfo().log("Delayed task executed!");
}
S Fixed Delay (čekání mezi dokončením a startem)
// 10 sekund po dokončení předchozího
scheduler.scheduleWithFixedDelay(
this::cleanupTask,
0, // Initial delay
10, // Delay after completion
TimeUnit.SECONDS
);
---
Vzory pro Běžné Úlohy
Auto-Save
public class AutoSaveManager { private final MyPlugin plugin;
private ScheduledExecutorService scheduler;
private final int saveIntervalMinutes = 5;
public AutoSaveManager(MyPlugin plugin) {
this.plugin = plugin;
}
public void start() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(
this::performAutoSave,
saveIntervalMinutes,
saveIntervalMinutes,
TimeUnit.MINUTES
);
plugin.getLogger().atInfo().log("Auto-save started (every %d minutes)", saveIntervalMinutes);
}
public void stop() {
if (scheduler != null) {
scheduler.shutdown();
}
}
private void performAutoSave() {
plugin.getLogger().atInfo().log("Performing auto-save...");
for (Player player : Universe.get().getPlayers()) {
CompletableFuture.runAsync(() -> {
plugin.getPlayerDataManager().savePlayer(player.getUuid());
});
}
}
}
// Použití v pluginu
@Override
protected void setup() {
autoSaveManager = new AutoSaveManager(this);
}
@Override
protected void start() {
autoSaveManager.start();
}
@Override
protected void shutdown() {
autoSaveManager.stop();
}
Cleanup Task
public class CleanupTask { private final MyPlugin plugin;
private ScheduledExecutorService scheduler;
public void start() {
scheduler = Executors.newSingleThreadScheduledExecutor();
// Cleanup každou hodinu
scheduler.scheduleAtFixedRate(
this::cleanup,
1,
1,
TimeUnit.HOURS
);
}
private void cleanup() {
plugin.getLogger().atInfo().log("Running cleanup...");
// Vyčisti staré záznamy
cleanOldRecords();
// Uvolni paměť
System.gc();
plugin.getLogger().atInfo().log("Cleanup complete");
}
public void stop() {
if (scheduler != null) {
scheduler.shutdown();
}
}
}
Delayed Player Action
public void delayedTeleport(Player player, Vector3d destination, int delaySeconds) {
player.sendMessage(Message.raw("Teleport za " + delaySeconds + " sekund...")); scheduler.schedule(() -> {
// Zkontroluj že hráč je stále online
if (!player.isOnline()) return;
World world = player.getWorld();
world.execute(() -> {
// Teleportuj
teleportPlayer(player, destination);
player.sendMessage(Message.raw("Teleportován!"));
});
}, delaySeconds, TimeUnit.SECONDS);
}
Countdown
public void startCountdown(Player player, int seconds, Runnable onComplete) {
AtomicInteger remaining = new AtomicInteger(seconds); ScheduledFuture> task = scheduler.scheduleAtFixedRate(() -> {
int current = remaining.getAndDecrement();
if (current > 0) {
player.sendMessage(Message.raw("Zbývá: " + current + "s"));
} else {
// Zruší se automaticky při vrácení
}
}, 0, 1, TimeUnit.SECONDS);
// Po uplynutí času
scheduler.schedule(() -> {
task.cancel(false);
World world = player.getWorld();
world.execute(onComplete);
}, seconds, TimeUnit.SECONDS);
}
---
World.execute() Integration
Async → World Thread
scheduler.scheduleAtFixedRate(() -> {
// Na scheduler threadu for (Player player : Universe.get().getPlayers()) {
World world = player.getWorld();
// Přepni na world thread pro komponenty
world.execute(() -> {
Ref ref = player.getRef();
Store store = ref.getStore();
// Bezpečný přístup ke komponentám
CustomComponent comp = store.getComponent(ref, CustomComponent.getComponentType());
if (comp != null) {
comp.incrementPlayTime();
}
});
}
}, 1, 1, TimeUnit.MINUTES);
---
Thread Pool pro Těžké Úlohy
public class HeavyTaskExecutor { // Pool s více vlákny pro paralelní zpracování
private final ExecutorService heavyTaskPool =
Executors.newFixedThreadPool(4);
public void processAllPlayers(Collection players) {
List> futures = new ArrayList<>();
for (Player player : players) {
CompletableFuture future = CompletableFuture.runAsync(() -> {
processPlayer(player);
}, heavyTaskPool);
futures.add(future);
}
// Počkej na všechny
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
getLogger().atInfo().log("All players processed");
});
}
public void shutdown() {
heavyTaskPool.shutdown();
}
}
---
Časté Chyby
Chyba 1: Zapomenutý Shutdown
// ŠPATNĚ - Memory leak, scheduler běží po shutdown
@Override
protected void shutdown() {
// Zapomněl jsem scheduler.shutdown()
}// SPRÁVNĚ
@Override
protected void shutdown() {
if (scheduler != null) {
scheduler.shutdown();
}
}
Chyba 2: Blokování World Threadu
// ŠPATNĚ - Blokuje world thread
world.execute(() -> {
Thread.sleep(5000); // NIKDY!
});// SPRÁVNĚ - Async delay
scheduler.schedule(() -> {
world.execute(() -> {
// Akce po delay
});
}, 5, TimeUnit.SECONDS);
Chyba 3: Přístup ke Komponentám z Async
// ŠPATNĚ
scheduler.scheduleAtFixedRate(() -> {
// Na scheduler threadu!
store.getComponent(ref, Type.getComponentType()); // CRASH!
}, ...);// SPRÁVNĚ
scheduler.scheduleAtFixedRate(() -> {
world.execute(() -> {
store.getComponent(ref, Type.getComponentType()); // OK
});
}, ...);
---
Shrnutí
| Metoda | Použití |
|--------|---------|
| CompletableFuture.runAsync() | Jednorázový async task |
| scheduler.schedule() | Delayed jednorázový task |
| scheduler.scheduleAtFixedRate() | Periodický task (fixní interval) |
| scheduler.scheduleWithFixedDelay() | Periodický task (delay po dokončení) |
| world.execute() | Přepnutí na world thread |
| TimeUnit | Použití |
|----------|---------|
| MILLISECONDS | Krátké delay |
| SECONDS | Většina tasků |
| MINUTES | Auto-save |
| HOURS | Cleanup, maintenance |