Repository navigation
1.3.0.0
Imported from
GrimAnticheat/GrimAPIreleasev1.3.0.0. Original publication:05/14/2026 18:33:54. Original URL:https://github.com/GrimAnticheat/GrimAPI/releases/tag/v1.3.0.0.
Rewrites event dispatch around per-event channels. Zero-alloc hot path,
typed handlers over positional fields, abstract-event subscribe via a
bridge-listener pattern. 1.2.4 source compat maintained.
New
-
bus.get(EventClass.class).onX(grimPlugin, handler)— the typed
subscribe path. Handlers take the event's fields as positional args,
no wrapper object, no registry lookup on fire. Every subscribe binds
to aGrimPluginsobus.unregisterAllListeners(grim)sweeps on
plugin disable, same as Bukkit'sHandlerList. -
Abstract-event subscribe for
GrimEvent,GrimCheckEvent,
GrimVerboseCheckEvent. Bridge-listener pattern installs concrete
bridge entries into every child channel at subscribe time; the hot
path stays untouched.GrimCheckEvent.Channel abs = (GrimCheckEvent.Channel) bus.get(GrimCheckEvent.class); abs.onCheck(grim, (user, check, cancelled) -> { // fires for FlagEvent, CompletePredictionEvent, CommandExecuteEvent return cancelled; }); // Root-level observer for metrics / debug: GrimEvent.Channel any = (GrimEvent.Channel) bus.get(GrimEvent.class); any.onAnyEvent(grim, (eventClass, cancelled) -> counts.get(eventClass).increment());
-
Addon events. Plugins with their own event types call
bus.register(MyEvent.class, new MyEvent.Channel())and, if they want
abstract-level dispatch,abstractChannel.registerSubtype(MyEvent.class, myChannel, MyEvent.Channel::bridgeFromCheck).
Late registration after existing abstract subscribers is handled.
Changed
-
Priority direction flipped. Lower fires first, higher gets final
say — BukkitEventPriorityconvention (LOWEST → … → MONITOR).
Pre-1.3 Grim sorted highest-first. Default priority (0) users are
unaffected; callers with explicit priority numbers on
@GrimEventHandler/ class-keyedsubscribe(...)should re-evaluate. -
GrimEventgained aCHANNELtype parameter sobus.get(Class)can
return the concrete channel without a cast at the call site.
Deprecated (still works)
Every 1.2.4-style entry point is kept with @Deprecated and routes
through the same channels as the typed API:
bus.post(event)— caller supplies the event instance; dispatch
reaches both legacy-registered listeners AND typed handlers via each
channel'sdispatchTypedFromLegacyunpack method.bus.subscribe(ctx, Class, GrimEventListener)— class-keyed legacy
subscribe, routes to the channel's legacy slot.@GrimEventHandlerreflective annotated methods — scanned at
registration, stored as legacy-slot entries.
Each concrete channel also exposes a @Deprecated onX(Object ctx, …)
overload that resolves a platform-specific context (Bukkit
JavaPlugin, Fabric ModContainer, raw Class) through the bus's
extension manager.
Breaking vs 1.2.4
- Priority direction flip (see above). The only semantic break.
Three observational events added in the unreleased 1.2.5 development
version (GrimTransactionSendEvent, GrimTransactionReceivedEvent,
GrimTeleportEvent) have private constructors since they're now
typed-only. Never published, so nothing released breaks.
No reflection on the hot path
OptimizedEventBus constructor explicitly news up each channel. Bridge
factories are plain static method references
(FlagEvent.Channel::bridgeFromCheck). bus.get(Class) is a
ConcurrentHashMap lookup. Obfuscator-safe: rename any event or its
nested Channel class and the bus constructor breaks at compile time,
not at runtime.
The only reflection in the codebase is inside the @Deprecated registerAnnotatedListeners path, which only runs when a plugin opts
into @GrimEventHandler scanning.
Benchmarks
JMH via :benchmarks subproject (task-local, not in the shipped jar):
| Mode | Subs | ns/op | B/op |
|---|---|---|---|
| typed | 0 | 2.17 | ≈10⁻⁵ |
| typed | 1 | 7.19 | ≈10⁻⁵ |
| typed | 3 | 13.51 | ≈10⁻⁴ |
| legacy | 1 | 15.05 | ≈10⁻⁴ |
| legacy | 3 | 21.99 | ≈10⁻⁴ |
| mixed | 3 | 18.39 | ≈10⁻⁴ |
gc.count ≈ 0 and gc.alloc.rate.norm ≈ 10⁻⁵ B/op across every
configuration — the residual is JMH infrastructure noise, not dispatch.
Zero-alloc fire confirmed.
Tests
21 unit tests covering priority ordering, typed/legacy mixed dispatch,
cancellation threading, pooled-event init, plugin-disable sweep,
reflective @GrimEventHandler plumbing, and the three non-negotiable
bridge-pattern cases (abstract-first, subtype-first, unsubscribe-sweep).
End-to-end on paper-1.21.11:
api-event-fire— Join / Quit / Reload / TxSend / TxRecv typed pathsapi-legacy-compat—@GrimEventHandlerstill dispatchesapi-flag-event— Wurst Flight → cancellableFlagEvent.onFlag(grim, ...)api-abstract-subscribe—GrimEvent.onAnyEvent+GrimCheckEvent.onCheckfire via bridges
All four PASS.
Related
Grim-side migration: GrimAnticheat/Grim#2612