---
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<EntityStore> ref = playerRef.getReference();
            if (ref != null) {
                applyData(ref, data);
            }
        });
    });
});
```

### Thread-Safe Collections

```java
// Use concurrent collections for shared data
private final Map<UUID, PlayerData> playerData = new ConcurrentHashMap<>();
private final Set<UUID> 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<EntityStore> 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<EntityStore> 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<PlayerRef> players = new ArrayList<>();
    world.getEntityStore().forEach((ref, store) -> {
        PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
        if (playerRef != null) {
            players.add(playerRef);
        }
    });

    CompletableFuture.runAsync(() -> {
        Map<UUID, Result> results = new HashMap<>();

        for (PlayerRef playerRef : players) {
            results.put(playerRef.getUuid(), computeResult(playerRef));
        }

        world.execute(() -> {
            for (PlayerRef playerRef : players) {
                Ref<EntityStore> 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<EntityStore>`
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<EntityStore> 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 >}}
