--- title: Async Operations type: docs weight: 2 --- Asynchronous operations allow plugins to perform heavy tasks without blocking the world's ticking thread. ## Why Async? {{< callout type="info" >}} Each World has a dedicated ticking thread that handles game logic, player interactions, and tick updates. Blocking it causes lag and poor player experience. Use async for: - Database queries - File I/O - Network requests - Complex calculations {{< /callout >}} ## Basic Async Pattern Use `CompletableFuture.runAsync()` and `world.execute()`: ```java PlayerRef playerRef = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture.runAsync(() -> { // This runs on a background thread // Do heavy work here Data result = computeData(); // Return to world thread for game state changes world.execute(() -> { playerRef.sendMessage(Message.raw("Result: " + result)); }); }); ``` ## Thread Safety ### Using PlayerRef ```java getEventRegistry().register(PlayerConnectEvent.class, event -> { PlayerRef playerRef = event.getPlayerRef(); World world = event.getWorld(); CompletableFuture.runAsync(() -> { // Load data asynchronously PlayerData data = loadFromDatabase(playerRef.getUuid()); // Return to world thread world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null) { applyData(ref, data); } }); }); }); ``` ### Thread-Safe Collections ```java // Use concurrent collections for shared data private final Map playerData = new ConcurrentHashMap<>(); private final Set processing = ConcurrentHashMap.newKeySet(); public void processPlayer(PlayerRef playerRef, World world) { UUID uuid = playerRef.getUuid(); // Prevent duplicate processing if (!processing.add(uuid)) { return; // Already processing } CompletableFuture.runAsync(() -> { try { PlayerData data = compute(uuid); playerData.put(uuid, data); world.execute(() -> { playerRef.sendMessage(Message.raw("Processing complete!")); }); } finally { processing.remove(uuid); } }); } ``` ## Async Events Handle async events with CompletableFuture: ```java getEventRegistry().registerAsync(AsyncEvent.class, event -> { return CompletableFuture.supplyAsync(() -> { // Async processing return processEvent(event); }); }); ``` ## Common Patterns ### Database Operations ```java public void savePlayerData(PlayerRef playerRef, World world) { // Capture data on world thread before going async PlayerData data = captureData(playerRef); CompletableFuture.runAsync(() -> { try { database.save(playerRef.getUuid(), data); getLogger().at(Level.INFO).log("Saved data for " + playerRef.getUsername()); } catch (Exception e) { getLogger().error("Failed to save data", e); } }); } public void loadPlayerData(PlayerRef playerRef, World world) { CompletableFuture.runAsync(() -> { PlayerData data = database.load(playerRef.getUuid()); world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null && data != null) { applyData(ref, data); playerRef.sendMessage(Message.raw("Data loaded!")); } }); }); } ``` ### HTTP Requests ```java public void fetchPlayerStats(PlayerRef playerRef, World world) { CompletableFuture.runAsync(() -> { try { String response = httpClient.get("https://api.example.com/stats/" + playerRef.getUuid()); Stats stats = parseStats(response); world.execute(() -> { Ref ref = playerRef.getReference(); if (ref != null) { displayStats(ref, stats); } }); } catch (Exception e) { world.execute(() -> { playerRef.sendMessage(Message.raw("Failed to fetch stats.")); }); } }); } ``` ### Batch Processing ```java public void processAllPlayers(World world) { // Capture player refs on world thread List players = new ArrayList<>(); world.getEntityStore().forEach((ref, store) -> { PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); if (playerRef != null) { players.add(playerRef); } }); CompletableFuture.runAsync(() -> { Map results = new HashMap<>(); for (PlayerRef playerRef : players) { results.put(playerRef.getUuid(), computeResult(playerRef)); } world.execute(() -> { for (PlayerRef playerRef : players) { Ref ref = playerRef.getReference(); if (ref != null) { Result result = results.get(playerRef.getUuid()); applyResult(ref, result); } } }); }); } ``` ## Error Handling ```java public void safeAsyncOperation(PlayerRef playerRef, World world) { CompletableFuture.runAsync(() -> { try { riskyOperation(playerRef.getUuid()); world.execute(() -> { playerRef.sendMessage(Message.raw("Operation successful!")); }); } catch (Exception e) { getLogger().error("Async operation failed", e); world.execute(() -> { playerRef.sendMessage(Message.raw("Operation failed. Please try again.")); }); } }); } ``` ## Chaining Async Operations Use `CompletableFuture` chaining for sequential async operations: ```java public void chainedOperations(PlayerRef playerRef, World world) { CompletableFuture .supplyAsync(() -> { // First async operation return fetchFromDatabase(playerRef.getUuid()); }) .thenApplyAsync(data -> { // Second async operation return processData(data); }) .thenAccept(result -> { // Return to world thread with final result world.execute(() -> { playerRef.sendMessage(Message.raw("Final result: " + result)); }); }) .exceptionally(e -> { getLogger().error("Chain failed", e); world.execute(() -> { playerRef.sendMessage(Message.raw("Operation failed.")); }); return null; }); } ``` ## Best Practices {{< callout type="warning" >}} **Async Rules:** 1. Never access game state directly from async threads 2. Always use `PlayerRef`, never store `Player` or `Ref` 3. Capture needed data before going async 4. Use `world.execute()` to return to world thread 5. Handle exceptions properly 6. Use concurrent collections for shared data {{< /callout >}} ```java // CORRECT: Capture data, process async, apply on world thread public void correctPattern(PlayerRef playerRef, World world) { String username = playerRef.getUsername(); // Capture on world thread CompletableFuture.runAsync(() -> { String result = process(username); world.execute(() -> { playerRef.sendMessage(Message.raw(result)); }); }); } // WRONG: Using world objects directly in async // public void wrongPattern(Player player, Ref ref) { // CompletableFuture.runAsync(() -> { // // DON'T DO THIS - these may be invalid from async context // String name = player.getDisplayName(); // player.sendMessage("Hello"); // }); // } ``` ## Comparison with Other Platforms {{< callout type="info" >}} **Note for developers from other platforms:** - Unlike Bukkit/Spigot, Hytale does NOT have `runSync()` or `runLater()` methods - Use `CompletableFuture.runAsync()` instead of platform-specific async methods - Use `world.execute()` instead of `runSync()` to return to the world thread - Use `CompletableFuture.delayedExecutor()` for delayed tasks - Use `ScheduledExecutorService` for repeating tasks {{< /callout >}}