--- title: Threading type: docs weight: 4 --- Understanding threading in Hytale is crucial for writing safe and performant plugins. Unlike many game servers, Hytale uses a **per-world threading model** where each world runs on its own dedicated thread. ## Per-World Threading Each `World` in Hytale extends `TickingThread` and runs on its own dedicated thread at 30 TPS (ticks per second). This means: - Each world ticks independently - World operations must be called from that world's thread - Players in different worlds run on different threads ```java World world = player.getWorld(); // Check if we're on this world's thread if (world.isInThread()) { // Safe to modify world state world.setBlock(position, blockType); } ``` {{< callout type="warning" >}} Never perform blocking operations (I/O, network, database) on a world thread. This will cause that world to lag. {{< /callout >}} ## Async Operations For long-running operations, use `CompletableFuture`: ```java PlayerRef ref = player.getPlayerRef(); World world = player.getWorld(); CompletableFuture.supplyAsync(() -> { // This runs on a worker thread return loadDataFromDatabase(ref.getUuid()); }).thenAccept(data -> { // PlayerRef.sendMessage() is thread-safe ref.sendMessage(Message.raw("Data loaded: " + data)); }); ``` ## Thread-Safe Classes Some Hytale classes are designed for thread safety: | Class | Thread Safety | |-------|---------------| | `PlayerRef` | Safe to use across threads | | `World` | Must be accessed on its own thread (`world.isInThread()`) | | `Entity` | Must be accessed on its world's thread | | `ItemStack` | Immutable, thread-safe | ### PlayerRef `PlayerRef` is a persistent, thread-safe reference to a player: ```java import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.Message; // Store reference (thread-safe) PlayerRef playerRef = player.getPlayerRef(); // Safe to access on any thread: UUID uuid = playerRef.getUuid(); String username = playerRef.getUsername(); String language = playerRef.getLanguage(); // Get current world UUID (may change if player switches worlds) UUID worldUuid = playerRef.getWorldUuid(); // Send message directly (thread-safe) playerRef.sendMessage(Message.raw("Hello!")); // For ECS operations, get the entity reference Ref entityRef = playerRef.getReference(); // null if not in world ``` ### Checking Thread Context ```java World world = player.getWorld(); // Check if on world's thread if (!world.isInThread()) { throw new IllegalStateException("Must be called from world thread!"); } // Debug: print current thread System.out.println("Current thread: " + Thread.currentThread().getName()); ``` ## Common Patterns ### Database Operations ```java public void savePlayer(Player player) { PlayerRef ref = player.getPlayerRef(); PlayerData data = collectPlayerData(player); CompletableFuture.runAsync(() -> { // Runs on worker thread database.save(ref.getUuid(), data); }).thenRun(() -> { // Notify player (PlayerRef.sendMessage is thread-safe) ref.sendMessage(Message.raw("Saved!")); }); } ``` ### Loading Data on Join ```java getEventRegistry().register(PlayerConnectEvent.class, event -> { PlayerRef ref = event.getPlayerRef(); World world = event.getWorld(); CompletableFuture.supplyAsync(() -> { // Load data on worker thread return database.load(ref.getUuid()); }).thenAccept(data -> { if (world != null && data != null) { world.execute(() -> { // Back on world thread - safe to access ECS Ref entityRef = ref.getReference(); if (entityRef != null) { applyData(entityRef, data); } }); } }); }); ``` ## Async Events Some events support asynchronous handling. Events with a keyed type (like `PlayerChatEvent` which has a `String` key) must use `registerAsyncGlobal`: ```java getEventRegistry().registerAsyncGlobal( PlayerChatEvent.class, future -> future.thenApply(event -> { // This runs asynchronously // Can perform slow operations here String filtered = filterMessage(event.getContent()); event.setContent(filtered); return event; }) ); ``` ## Cross-World Operations When working with entities across worlds or transferring players: ```java // Player transfer returns CompletableFuture PlayerRef ref = player.getPlayerRef(); World targetWorld = universe.getWorld("target_world"); // Note: player operations that change worlds use async patterns // Always use PlayerRef to track player across world changes ``` ## Best Practices {{< callout type="tip" >}} - Always use `PlayerRef` when passing player references across async boundaries - Check `world.isInThread()` before modifying world state - Use `CompletableFuture` for async database/network operations - Keep world thread operations fast (< 33ms per tick) - Use connection pools for database access - Remember: each world has its own thread - no single "main thread" {{< /callout >}} ## Debugging Thread Issues If you encounter threading issues: 1. Check `world.isInThread()` before world modifications 2. Use `PlayerRef` instead of direct `Player` references for async work 3. Log the current thread: `Thread.currentThread().getName()` 4. World thread names follow pattern: `World-{worldName}`