Init
This commit is contained in:
361
content/core-concepts/events/async-events.fr.md
Normal file
361
content/core-concepts/events/async-events.fr.md
Normal file
@@ -0,0 +1,361 @@
|
||||
---
|
||||
title: Async Events
|
||||
type: docs
|
||||
weight: 4
|
||||
---
|
||||
|
||||
Async events allow you to perform asynchronous operations during event handling. They use `CompletableFuture` for non-blocking execution.
|
||||
|
||||
## IAsyncEvent Interface
|
||||
|
||||
Events implementing `IAsyncEvent<K>` support asynchronous handling:
|
||||
|
||||
```java
|
||||
public interface IAsyncEvent<K> extends IBaseEvent<K> {
|
||||
// Async events use CompletableFuture in handlers
|
||||
}
|
||||
```
|
||||
|
||||
## Registering Async Handlers
|
||||
|
||||
Use `registerAsync()` for async events with `Void` key, or `registerAsyncGlobal()` for keyed events:
|
||||
|
||||
{{< callout type="warning" >}}
|
||||
`PlayerChatEvent` implements `IAsyncEvent<String>` - it has a `String` key type. Therefore, you must use `registerAsyncGlobal()` instead of `registerAsync()`. The simple `registerAsync(Class, handler)` method only works for events with `Void` key type.
|
||||
{{< /callout >}}
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenApply(event -> {
|
||||
// This runs asynchronously
|
||||
String filtered = filterContent(event.getContent());
|
||||
event.setContent(filtered);
|
||||
return event;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Async Registration Methods
|
||||
|
||||
### Basic Async Registration
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenApply(event -> {
|
||||
// Async processing
|
||||
return event;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### With Priority
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsync(
|
||||
EventPriority.EARLY,
|
||||
PlayerChatEvent.class,
|
||||
future -> future.thenApply(event -> {
|
||||
// Runs early in async chain
|
||||
return event;
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### With Key
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsync(
|
||||
KeyedAsyncEvent.class,
|
||||
myKey,
|
||||
future -> future.thenApply(event -> {
|
||||
// Only for events matching myKey
|
||||
return event;
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Global Async
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(
|
||||
KeyedAsyncEvent.class,
|
||||
future -> future.thenApply(event -> {
|
||||
// All events of this type
|
||||
return event;
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Unhandled Async
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncUnhandled(
|
||||
KeyedAsyncEvent.class,
|
||||
future -> future.thenApply(event -> {
|
||||
// Events not handled by keyed handlers
|
||||
return event;
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Working with CompletableFuture
|
||||
|
||||
### Sequential Operations
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future
|
||||
.thenApply(event -> {
|
||||
// Step 1: Filter content
|
||||
event.setContent(filterProfanity(event.getContent()));
|
||||
return event;
|
||||
})
|
||||
.thenApply(event -> {
|
||||
// Step 2: Add formatting
|
||||
event.setContent(addChatFormatting(event.getContent()));
|
||||
return event;
|
||||
})
|
||||
.thenApply(event -> {
|
||||
// Step 3: Log
|
||||
logChatMessage(event.getSender(), event.getContent());
|
||||
return event;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Parallel Operations
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenCompose(event -> {
|
||||
// Start multiple async operations in parallel
|
||||
CompletableFuture<Boolean> spamCheckFuture =
|
||||
CompletableFuture.supplyAsync(() -> checkForSpam(event.getContent()));
|
||||
|
||||
CompletableFuture<Boolean> linkCheckFuture =
|
||||
CompletableFuture.supplyAsync(() -> checkForLinks(event.getContent()));
|
||||
|
||||
// Combine results
|
||||
return spamCheckFuture.thenCombine(linkCheckFuture, (isSpam, hasLinks) -> {
|
||||
if (isSpam || hasLinks) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return event;
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future
|
||||
.thenApply(event -> {
|
||||
// May throw exception
|
||||
riskyOperation(event);
|
||||
return event;
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
// Handle error
|
||||
getLogger().severe("Async event failed: " + throwable.getMessage());
|
||||
return null; // Event will be skipped
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Switching to Main Thread
|
||||
|
||||
If you need to perform game operations after async work:
|
||||
|
||||
```java
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenApply(event -> {
|
||||
// Async: Check external database
|
||||
boolean isMuted = checkMuteStatus(event.getSender().getUuid());
|
||||
|
||||
if (isMuted) {
|
||||
event.setCancelled(true);
|
||||
|
||||
// Schedule world thread work for player notification
|
||||
World world = Universe.get().getWorld(event.getSender().getWorldUuid());
|
||||
if (world != null) {
|
||||
world.execute(() -> {
|
||||
event.getSender().sendMessage(Message.raw("You are muted!"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## PlayerChatEvent - The Main Async Event
|
||||
|
||||
`PlayerChatEvent` is the primary async event in Hytale. It implements both `IAsyncEvent<String>` and `ICancellable`:
|
||||
|
||||
```java
|
||||
public class PlayerChatEvent implements IAsyncEvent<String>, ICancellable {
|
||||
// sender: PlayerRef
|
||||
// targets: List<PlayerRef>
|
||||
// content: String
|
||||
// formatter: Formatter
|
||||
// cancelled: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Chat Handler Example
|
||||
|
||||
```java
|
||||
public class ChatPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// Early priority: Content filtering
|
||||
getEventRegistry().registerAsync(
|
||||
EventPriority.EARLY,
|
||||
PlayerChatEvent.class,
|
||||
this::filterContent
|
||||
);
|
||||
|
||||
// Normal priority: Standard processing
|
||||
getEventRegistry().registerAsync(
|
||||
PlayerChatEvent.class,
|
||||
this::processChat
|
||||
);
|
||||
|
||||
// Late priority: Logging
|
||||
getEventRegistry().registerAsync(
|
||||
EventPriority.LATE,
|
||||
PlayerChatEvent.class,
|
||||
this::logChat
|
||||
);
|
||||
}
|
||||
|
||||
private CompletableFuture<PlayerChatEvent> filterContent(
|
||||
CompletableFuture<PlayerChatEvent> future) {
|
||||
return future.thenApply(event -> {
|
||||
String content = event.getContent();
|
||||
|
||||
// Filter profanity
|
||||
String filtered = filterProfanity(content);
|
||||
if (!filtered.equals(content)) {
|
||||
event.setContent(filtered);
|
||||
}
|
||||
|
||||
return event;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<PlayerChatEvent> processChat(
|
||||
CompletableFuture<PlayerChatEvent> future) {
|
||||
return future.thenApply(event -> {
|
||||
if (event.isCancelled()) {
|
||||
return event;
|
||||
}
|
||||
|
||||
PlayerRef sender = event.getSender();
|
||||
|
||||
// Custom formatter with rank prefix
|
||||
event.setFormatter((playerRef, msg) -> {
|
||||
String prefix = getRankPrefix(playerRef);
|
||||
return Message.raw(prefix + playerRef.getUsername() + ": " + msg);
|
||||
});
|
||||
|
||||
return event;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<PlayerChatEvent> logChat(
|
||||
CompletableFuture<PlayerChatEvent> future) {
|
||||
return future.thenApply(event -> {
|
||||
if (!event.isCancelled()) {
|
||||
logToDatabase(
|
||||
event.getSender().getUuid(),
|
||||
event.getContent(),
|
||||
System.currentTimeMillis()
|
||||
);
|
||||
}
|
||||
return event;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Integration Example
|
||||
|
||||
```java
|
||||
public class ChatDatabasePlugin extends JavaPlugin {
|
||||
|
||||
private final ExecutorService dbExecutor = Executors.newFixedThreadPool(4);
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenCompose(event -> {
|
||||
// Run database check on dedicated thread pool
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Check if player is muted in database
|
||||
boolean muted = database.isPlayerMuted(event.getSender().getUuid());
|
||||
if (muted) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return event;
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("Database error: " + e.getMessage());
|
||||
return event; // Allow message on DB error
|
||||
}
|
||||
}, dbExecutor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
dbExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
{{< callout type="tip" >}}
|
||||
**Async Event Tips:**
|
||||
- Never block the async chain with `.get()` or `.join()` - use `.thenApply()` or `.thenCompose()`
|
||||
- Use `world.execute()` to return to world thread for game operations
|
||||
- Handle exceptions with `.exceptionally()` or `.handle()`
|
||||
- Keep async handlers lightweight for better performance
|
||||
- Check `isCancelled()` before doing expensive operations
|
||||
{{< /callout >}}
|
||||
|
||||
{{< callout type="warning" >}}
|
||||
**Thread Safety:**
|
||||
- PlayerRef's `getReference()` may return null or invalid reference if player disconnected
|
||||
- Always check `ref != null && ref.isValid()` before accessing ECS data
|
||||
- Avoid modifying game state directly in async handlers
|
||||
- Use `world.execute()` to safely access game state from the world thread
|
||||
{{< /callout >}}
|
||||
|
||||
```java
|
||||
// Safe pattern for async player operations
|
||||
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||
return future.thenApply(event -> {
|
||||
// Async work here...
|
||||
String processed = processMessage(event.getContent());
|
||||
event.setContent(processed);
|
||||
|
||||
// Safe player notification via PlayerRef
|
||||
PlayerRef sender = event.getSender();
|
||||
World world = Universe.get().getWorld(sender.getWorldUuid());
|
||||
if (world != null) {
|
||||
world.execute(() -> {
|
||||
sender.sendMessage(Message.raw("Message processed"));
|
||||
});
|
||||
}
|
||||
|
||||
return event;
|
||||
});
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user