A lightweight, type-safe event system for your plugins.
Subscribe with priority and a typed Consumer<T>, get back an EventSubscription you can unsubscribe later.
- Type-safe: subscribe per event class (no raw casting in listeners)
- Priorities (
LOWEST … MONITOR) with deterministic order - OOP API:
EventBus,EventSubscription,EventPriority - Unsubscribe via handle or bus
- Thread-safe dispatch (copy-on-dispatch)
- Small and test-friendly
+------------+ subscribe(handler) +-----------+
| Plugin | --------------------------------> | EventBus |
+------------+ +-----------+
^ |
| post(event) | calls (by priority)
+----------------------------------------------+
The bus stores subscriptions per event class and dispatches them in priority order.
// gg/nextforge/core/util/events/EventPriority.java
public enum EventPriority {
LOWEST(0), LOW(1), NORMAL(2), HIGH(3), HIGHEST(4), MONITOR(5);
private final int value;
EventPriority(int v) { this.value = v; }
public int getValue() { return value; }
}
// gg/nextforge/core/util/events/EventSubscription.java
public interface EventSubscription<T> {
void unsubscribe();
boolean isActive();
}
// gg/nextforge/core/util/events/EventBus.java
public class EventBus {
public <T> EventSubscription<T> subscribe(Class<T> eventType, EventPriority priority, java.util.function.Consumer<T> handler);
public <T> void unsubscribe(EventSubscription<T> subscription);
public <T> void post(T event);
}Implementation: backing store
Map<Class<?>, List<Subscription<?>>>, sorted by priority (desc). Dispatch iterates over a copy to avoid CME.
public record PlayerJoinEvent(String playerName) {}EventBus bus = new EventBus();
var sub = bus.subscribe(
PlayerJoinEvent.class,
EventPriority.NORMAL,
e -> System.out.println("Welcome, " + e.playerName())
);bus.post(new PlayerJoinEvent("Steve"));sub.unsubscribe(); // stops this one
bus.unsubscribe(sub); // or via bus- Subscriptions are stored in a concurrent map; dispatch copies current listeners to a local list to avoid modification during iteration.
- The bus itself is not an async executor — if you want async handlers, schedule them via your Scheduler.
Example:
bus.subscribe(MyEvent.class, EventPriority.HIGH, e -> {
scheduler.runTask(() -> handleOnMain(e), true); // switch to main thread
});- Pure Java: no server dependencies.
- Deterministic ordering via
EventPriorityvalues. - Easy to verify with unit tests by posting events and asserting side effects.
If you need cancelation, add an interface:
public interface Cancelable {
boolean isCancelled();
void setCancelled(boolean cancelled);
}Handlers can set event.setCancelled(true).
Your post can then stop propagation for priorities < MONITOR if cancelled.
public class EventBus {
private final java.util.Map<Class<?>, java.util.List<Subscription<?>>> subscribers = new java.util.concurrent.ConcurrentHashMap<>();
public <T> EventSubscription<T> subscribe(Class<T> eventType, EventPriority priority, java.util.function.Consumer<T> handler) {
var sub = new Subscription<>(eventType, priority, handler);
subscribers.computeIfAbsent(eventType, k -> new java.util.ArrayList<>()).add(sub);
subscribers.get(eventType).sort(java.util.Comparator.comparingInt((Subscription<?> s) -> s.priority.getValue()).reversed());
return sub;
}
public <T> void unsubscribe(EventSubscription<T> subscription) {
if (subscription instanceof Subscription<?> sub) {
var list = subscribers.get(sub.eventType);
if (list != null) list.remove(sub);
}
}
public <T> void post(T event) {
@SuppressWarnings("unchecked")
var list = (java.util.List<Subscription<T>>) (java.util.List<?>) subscribers.getOrDefault(event.getClass(), java.util.List.of());
for (var sub : new java.util.ArrayList<>(list)) {
if (!sub.cancelled) {
try { sub.handler.accept(event); } catch (Throwable t) { t.printStackTrace(); }
}
}
}
private static final class Subscription<T> implements EventSubscription<T> {
private final Class<T> eventType;
private final EventPriority priority;
private final java.util.function.Consumer<T> handler;
private volatile boolean cancelled = false;
Subscription(Class<T> eventType, EventPriority priority, java.util.function.Consumer<T> handler) {
this.eventType = eventType; this.priority = priority; this.handler = handler;
}
public void unsubscribe() { cancelled = true; }
public boolean isActive() { return !cancelled; }
}
}Happy eventing! Combine this with the Scheduler for safe main-thread execution.