Init
This commit is contained in:
190
content/core-concepts/threading.en.md
Normal file
190
content/core-concepts/threading.en.md
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
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}`
|
||||
Reference in New Issue
Block a user