103 lines
4.2 KiB
Markdown
103 lines
4.2 KiB
Markdown
# Event Handling & Routing
|
|
|
|
**Package:** `me.bobert.netneutrality.platform.event`
|
|
**Primary class:** `EventsHandler`
|
|
|
|
`EventsHandler` is the single JDA `EventListener` registered on the `JDA` instance
|
|
(`jda.addEventListener(new EventsHandler())` in `App.startup()`). It is the funnel through
|
|
which **every** gateway event passes. Its job is to translate a raw JDA event into a
|
|
[Context](contexts.md) and route that context to the correct subsystem.
|
|
|
|
## Listener registry
|
|
|
|
Listeners are held in a static, thread-safe map keyed by the JDA event class:
|
|
|
|
```java
|
|
private static final Map<Class<? extends Event>, Function<? extends Event, Context>> listeners
|
|
= new ConcurrentHashMap<>();
|
|
```
|
|
|
|
Each entry is a **factory function**: given an event of that type, it returns a `Context` (or `null`).
|
|
The map is populated once in a `static {}` block.
|
|
|
|
### `addListener` vs `addGenericListener`
|
|
|
|
- `addListener(Class<T>, Function<? super T, Context>)` — registers a factory that produces a
|
|
`Context` to be routed onward (command or task flow).
|
|
- `addGenericListener(Class<T>, Consumer<T>)` — registers a factory that runs a side-effecting
|
|
consumer and returns `null`, so nothing is routed onward. This is the **lambda flow** for simple
|
|
events.
|
|
|
|
## Registered events
|
|
|
|
| JDA event | Produces | Flow |
|
|
|---|---|---|
|
|
| `MessageReceivedEvent` (prefix match) | `MessageCommandContext` | Command |
|
|
| `MessageReceivedEvent` (no prefix) | `MessageTaskContext` | Task |
|
|
| `SlashCommandInteractionEvent` | `SlashCommandContext` | Command |
|
|
| `UserContextInteractionEvent` | `UserMenuContext` | Command |
|
|
| `MessageContextInteractionEvent` | `MessageMenuContext` | Command |
|
|
| `ButtonInteractionEvent` | `ButtonTaskContext` | Task |
|
|
| `ModalInteractionEvent` | `ModalTaskContext` | Task |
|
|
| `MessageReactionAddEvent` | `EmojiTaskContext` | Task |
|
|
| `GuildJoinEvent` | — (consumer) | Lambda |
|
|
| `GuildLeaveEvent` | — (consumer) | Lambda |
|
|
|
|
### Message routing detail
|
|
|
|
For `MessageReceivedEvent` the factory:
|
|
|
|
1. Ignores messages authored by bots and webhook messages (returns `null`).
|
|
2. Reads the configured prefix (`NETNEUTRALITY_BOT_PREFIX`, default `$`).
|
|
3. If the trimmed message starts with the prefix → **`MessageCommandContext`** (command flow).
|
|
4. Otherwise → **`MessageTaskContext`** (task flow). This lets ordinary messages participate in the
|
|
task system, e.g. as a reply that advances a [step task](tasks.md#33-step-tasks).
|
|
|
|
### Guild lifecycle (lambda flow)
|
|
|
|
- `GuildJoinEvent` — resolves `GuildService.get(guild)`; if debug logging is enabled for that guild,
|
|
sends a notice to the debug channel.
|
|
- `GuildLeaveEvent` — `GuildService.get(guild).deleteGuild()` removes the guild's stored data.
|
|
|
|
## Dispatch algorithm
|
|
|
|
```java
|
|
@Override
|
|
public void onEvent(@Nonnull GenericEvent event) {
|
|
handleEvent((Event) event);
|
|
}
|
|
|
|
private void handleEvent(Event event) {
|
|
Class<?> eventClass = event.getClass();
|
|
while (eventClass != null) {
|
|
if (listeners.containsKey(eventClass)) {
|
|
Context context = listeners.get(eventClass).apply(event);
|
|
if (context instanceof CommandContext c) handleCommand(c);
|
|
if (context instanceof TaskContext t) handleTask(t);
|
|
break;
|
|
}
|
|
eventClass = eventClass.getSuperclass(); // walk up the class hierarchy
|
|
}
|
|
}
|
|
```
|
|
|
|
Key points:
|
|
|
|
- **Superclass walk** — if the exact event class is not registered, the handler walks up the
|
|
superclass chain until it finds a match. This means a listener registered for a base event type
|
|
also catches subclasses.
|
|
- **Routing by context type** — the resulting object is routed purely by its runtime type:
|
|
a `CommandContext` goes to `CommandProcessor.dispatch()`, a `TaskContext` goes to
|
|
`TaskManager.getInstance().handle(context)`.
|
|
- **`null` contexts** (bot messages, consumer-only events) are simply not routed anywhere.
|
|
|
|
## Extending the event system
|
|
|
|
To handle a new event type:
|
|
|
|
1. Add an `addListener(SomeEvent.class, SomeContext::new)` (or `addGenericListener(...)` for a
|
|
consumer-only side effect) to the `static {}` block.
|
|
2. If it should run the command or task flow, make the produced context extend
|
|
[`CommandContext`](contexts.md#commandcontext) or [`TaskContext`](contexts.md#taskcontext)
|
|
respectively.
|