---
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<EntityStore> 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<EntityStore> 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}`
