Skip to content

1.3.0.0

Choose a tag to compare

@SaltyAlpha SaltyAlpha released this 05 Sep 04:13
· 63 commits to master since this release

Imported from GrimAnticheat/GrimAPI release v1.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 a GrimPlugin so bus.unregisterAllListeners(grim) sweeps on
    plugin disable, same as Bukkit's HandlerList.

  • 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 — Bukkit EventPriority convention (LOWEST → … → MONITOR).
    Pre-1.3 Grim sorted highest-first. Default priority (0) users are
    unaffected; callers with explicit priority numbers on
    @​GrimEventHandler / class-keyed subscribe(...) should re-evaluate.

  • GrimEvent gained a CHANNEL type parameter so bus.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's dispatchTypedFromLegacy unpack method.
  • bus.subscribe(ctx, Class, GrimEventListener) — class-keyed legacy
    subscribe, routes to the channel's legacy slot.
  • @​GrimEventHandler reflective 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 paths
  • api-legacy-compat — @​GrimEventHandler still dispatches
  • api-flag-event — Wurst Flight → cancellable FlagEvent.onFlag(grim, ...)
  • api-abstract-subscribe — GrimEvent.onAnyEvent + GrimCheckEvent.onCheck fire via bridges

All four PASS.

Related

Grim-side migration: GrimAnticheat/Grim#2612