diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java index 3bb9559..f2548bc 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java @@ -9,25 +9,47 @@ import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.eventbus.internal.BusGroupImpl; +import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandles; import java.util.Collection; /** - * A collection of {@link EventBus} instances that are grouped together for easier management. + * A collection of {@link EventBus} instances that are grouped together for easier management, allowing for bulk + * operations. */ public sealed interface BusGroup permits BusGroupImpl { + /** + * The default BusGroup, which is used when an {@link EventBus} is created without specifying a BusGroup. + */ BusGroup DEFAULT = create("default"); + /** + * Creates a new BusGroup with the given name. + * + * @param name The unique name of the BusGroup + * @return A new BusGroup with the given name + * @throws IllegalArgumentException if the name is already in use by another BusGroup + * @apiNote To enforce a base type for all events in this BusGroup, use {@link #create(String, Class)}. + */ static BusGroup create(String name) { return new BusGroupImpl(name, Event.class); } + /** + * Creates a new BusGroup with the given name and base type. + * + * @param name The unique name of the BusGroup + * @param baseType The base type that all events in this BusGroup must extend or implement + * @return A new BusGroup with the given name and base type + * @throws IllegalArgumentException if the name is already in use by another BusGroup + */ static BusGroup create(String name, Class baseType) { return new BusGroupImpl(name, baseType); } /** * The unique name of this BusGroup. + *

The uniqueness of this name is enforced when the bus group is {@linkplain #create(String) created}.

*/ String name(); @@ -40,21 +62,30 @@ static BusGroup create(String name, Class baseType) { /** * Shuts down all EventBus instances associated with this BusGroup, preventing any further events from being posted * until {@link #startup()} is called. + * + * @apiNote If you don't intend on using this BusGroup again, prefer {@link #dispose()} instead as that will also + * free up resources. */ void shutdown(); /** - * Shuts down all EventBus instances associated with this BusGroup, unregisters all listeners and frees resources - * no longer needed. - *

Warning: This is a destructive operation - this BusGroup should not be used again after calling this method.

+ * {@linkplain #shutdown() Shuts down} all EventBus instances associated with this BusGroup, + * {@linkplain #unregister(Collection) unregisters} all listeners and frees no longer needed resources. + * + *

Warning: This is a destructive operation - this BusGroup should not be used again after calling this method - + * attempting to do so may throw exceptions or act as a no-op.

+ * + * @apiNote If you plan on using this BusGroup again, prefer {@link #shutdown()} instead. */ void dispose(); /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources.

+ * Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources. + * *

Warning: This is only intended to be called once after all listeners are registered - calling this * repeatedly may hurt performance.

+ * + * @apiNote This is an experimental feature that may be removed, renamed or otherwise changed without notice. */ void trim(); @@ -68,6 +99,11 @@ static BusGroup create(String name, Class baseType) { * @apiNote This method only registers static listeners. *

If you want to register both instance and static methods, use * {@link BusGroup#register(MethodHandles.Lookup, Object)} instead.

+ * @implNote Internally, bulk registration uses {@link LambdaMetafactory} to create method references to the + * annotated methods using the provided {@code callerLookup} - said lookup must have + * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() full privilege access} as + * {@linkplain LambdaMetafactory LMF} may need to spin an inner class for implementing the lambda, which + * inherently allows access to private fields and methods. */ Collection register(MethodHandles.Lookup callerLookup, Class utilityClassWithStaticListeners); @@ -80,6 +116,11 @@ static BusGroup create(String name, Class baseType) { * * @apiNote If you know all the listeners are static methods, use * {@link BusGroup#register(MethodHandles.Lookup, Class)} instead for better registration performance. + * @implNote Internally, bulk registration uses {@link LambdaMetafactory} to create method references to the + * annotated methods using the provided {@code callerLookup} - said lookup must have + * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() full privilege access} as + * {@linkplain LambdaMetafactory LMF} may need to spin an inner class for implementing the lambda, which + * inherently allows access to private fields and methods. */ Collection register(MethodHandles.Lookup callerLookup, Object listener); diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java index 8daaf0b..bb0f703 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java @@ -14,6 +14,10 @@ import java.util.function.Consumer; +/** + * @see CancellableEventBus if your event type implements {@link Cancellable} + * @param The type of event this EventBus handles + */ public sealed interface EventBus permits CancellableEventBus, AbstractEventBusImpl, EventBusImpl { /** * Adds a listener to this EventBus with the default priority of {@link Priority#NORMAL}. diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java b/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java index 1eb8184..ad6166b 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java @@ -2,6 +2,11 @@ * Copyright (c) Forge Development LLC * SPDX-License-Identifier: LGPL-2.1-only */ +/** + * EventBus is built around the idea of {@linkplain net.minecraftforge.eventbus.api.bus.EventBus event buses} that are + * grouped together by {@linkplain net.minecraftforge.eventbus.api.bus.BusGroup bus groups}. This package contains this + * core part of the API. + */ @NullMarked package net.minecraftforge.eventbus.api.bus; diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/InheritableEvent.java b/src/main/java/net/minecraftforge/eventbus/api/event/InheritableEvent.java index f3e7251..ec2b755 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/InheritableEvent.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/InheritableEvent.java @@ -6,4 +6,9 @@ import net.minecraftforge.eventbus.internal.Event; +/** + * A hybrid of an event base type and characteristic - implementing this interface on your event type allows for event + * posting on subclasses to propagate to your event type, essentially opting into event inheritance. This allows for + * flexible event hierarchies with mixed levels of encapsulation and mutability. + */ public non-sealed interface InheritableEvent extends Event {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/MutableEvent.java b/src/main/java/net/minecraftforge/eventbus/api/event/MutableEvent.java index bf6f915..53dbf0b 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/MutableEvent.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/MutableEvent.java @@ -7,4 +7,14 @@ import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.MutableEventInternals; +/** + * For mutable event classes that may also extend other classes and/or support being extended. + *

More advanced techniques like protected fields and methods are also possible, where the supertype may be a + * protected abstract class with some internals handled for you, but only the concrete types are actual events.

+ * + *

Consider {@link RecordEvent} for better performance and conciseness if field mutability and direct extendability + * aren't needed.

+ * + * @see RecordEvent + */ public non-sealed abstract class MutableEvent extends MutableEventInternals implements Event {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/RecordEvent.java b/src/main/java/net/minecraftforge/eventbus/api/event/RecordEvent.java index 7ed4ecd..5cdfe34 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/RecordEvent.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/RecordEvent.java @@ -4,9 +4,34 @@ */ package net.minecraftforge.eventbus.api.event; +import net.minecraftforge.eventbus.api.bus.EventBus; +import net.minecraftforge.eventbus.api.event.characteristic.Cancellable; import net.minecraftforge.eventbus.internal.Event; /** * For read-only or shallowly-immutable records. + *

Provides a means for declaring events in a concise and performant manner.

+ * + *

Examples

+ *

Here are some conceptual examples of record events:

+ *
    + *
  • Consider a leaderboard event that provides a list of players that listeners can mutate but doesn't allow + * the list to be set to null or an immutable list that would result in unexpected behaviour for the other + * listeners.
  • + *
  • An event where listeners are notified of a player joining the server and can optionally cancel it to kick them + * (when combined with the {@link Cancellable} characteristic), but can't set the player to someone else.
  • + *
  • Stateless events which do not carry any data inside but are still useful for notifying listeners when + * a specific action occurs, such as some types of lifecycle events.
  • + *
+ * + *

Note that while records are final and cannot extend other classes, inheritance is still possible through other + * means, such as by implementing a sealed interface and using the Java module system.

+ * + * @apiNote Cancellation is supported for record events as long as they also implement the {@link Cancellable} + * characteristic - this is possible thanks to the cancellation state being kept on the stack separately from + * the record instance itself. + * @implSpec This event base type can only be applied to {@linkplain Record Java record classes}. + * If you implement this interface on an ordinary class, an exception will be thrown when attempting to + * create an associated {@link EventBus}. */ public non-sealed interface RecordEvent extends Event {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java index a01cebd..1d126c9 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java @@ -5,11 +5,25 @@ package net.minecraftforge.eventbus.api.event.characteristic; import net.minecraftforge.eventbus.api.bus.CancellableEventBus; +import net.minecraftforge.eventbus.api.event.RecordEvent; +import net.minecraftforge.eventbus.api.listener.Priority; import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.EventCharacteristic; +import java.util.function.Consumer; +import java.util.function.Predicate; + /** - * A cancellable event returns {@code true} from {@link CancellableEventBus#post(Event)} if it was cancelled. - *

When an event is cancelled, it will not be passed to any further non-monitor listeners.

+ * A cancellable event returns {@code true} from {@link CancellableEventBus#post(Event)} if it was cancelled by a + * {@linkplain CancellableEventBus#addListener(Predicate) 'maybe cancelling'} or + * {@linkplain CancellableEventBus#addListener(boolean, Consumer) 'always cancelling'} listener. + * + *

When an event is cancelled, it will not be passed to any further non-{@linkplain Priority#MONITOR monitor} + * listeners. + * For further details on a cancellable event's interactions with an EventBus, see {@link CancellableEventBus}.

+ * + * @implNote Internally, the cancellation state is kept on the stack separately from the event instance itself, allowing + * for this characteristic to be applied to any event type - even {@link RecordEvent}s. + * @see CancellableEventBus */ public non-sealed interface Cancellable extends EventCharacteristic {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java index 266d505..815d6d4 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java @@ -4,15 +4,21 @@ */ package net.minecraftforge.eventbus.api.event.characteristic; +import net.minecraftforge.eventbus.api.bus.EventBus; import net.minecraftforge.eventbus.api.event.MutableEvent; +import net.minecraftforge.eventbus.api.event.RecordEvent; +import net.minecraftforge.eventbus.api.listener.Priority; import net.minecraftforge.eventbus.internal.EventCharacteristic; import net.minecraftforge.eventbus.internal.MutableEventInternals; /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

Events that are {@link MonitorAware} can provide stronger immutability guarantees to monitor listeners by - * returning unmodifiable views or throwing exceptions on mutation attempts when monitoring.

- *

Only supported for {@link MutableEvent} at this time.

+ * Events that are {@linkplain Priority#MONITOR monitor}-aware are able to provide stronger immutability guarantees to + * monitoring listeners by returning unmodifiable views or throwing exceptions on mutation attempts when monitoring. + * + * @apiNote This is an experimental feature that may be removed, renamed or otherwise changed without notice. + * @implNote This characteristic is only supported for the {@link MutableEvent} base type at this time. + * If combined with a different base type (such as {@link RecordEvent}), an exception will be thrown when + * attempting to create an associated {@link EventBus}. */ public non-sealed interface MonitorAware extends EventCharacteristic { default boolean isMonitoring() { diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java index 04fc80d..c70856e 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java @@ -4,13 +4,16 @@ */ package net.minecraftforge.eventbus.api.event.characteristic; +import net.minecraftforge.eventbus.api.bus.BusGroup; import net.minecraftforge.eventbus.api.bus.EventBus; -import net.minecraftforge.eventbus.internal.AbstractEventBusImpl; import net.minecraftforge.eventbus.internal.EventCharacteristic; /** - * A self-destructing event will {@link AbstractEventBusImpl#dispose() dispose} of its associated {@link EventBus} - * after it has been posted to free up resources, after which it cannot be posted to again. + * A self-destructing event will {@link BusGroup#dispose() dispose} of its associated {@link EventBus} after it has + * been posted to free up resources, after which it cannot be posted to again. *

This is useful for single-use lifecycle events.

+ * + * @apiNote The dispose action is similar to {@link BusGroup#dispose()}, but applies to only this event rather than to + * all events in the group. */ public non-sealed interface SelfDestructing extends EventCharacteristic {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java index fdf21f1..d3fefea 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java @@ -9,12 +9,14 @@ import net.minecraftforge.eventbus.internal.EventCharacteristic; /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

{@link SelfPosting} events are associated with a default {@link EventBus} in order to offer some convenience - * instance methods.

- * Example + * Self-posting events are associated with a default {@link EventBus}, allowing for some convenience methods on the + * instance. + * + *

Example

* {@snippet : + * import net.minecraftforge.eventbus.api.bus.EventBus; * import net.minecraftforge.eventbus.api.event.RecordEvent; + * import net.minecraftforge.eventbus.api.event.characteristic.SelfPosting; * * // Event declaration * public record ExampleEvent() implements SelfPosting, RecordEvent { @@ -32,10 +34,13 @@ * // instead of this * ExampleEvent.BUS.post(new ExampleEvent()); *} + * + * @apiNote This is an experimental feature that may be removed, renamed or otherwise changed without notice. */ public non-sealed interface SelfPosting extends EventCharacteristic { /** - * @implSpec This should directly return a {@code static final} field without additional logic or processing. + * @implSpec This should directly return a {@code static final} field without additional logic or processing - + * failure to do so may hurt performance. */ EventBus getDefaultBus(); diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java index 37c81f0..ef542db 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java @@ -2,6 +2,9 @@ * Copyright (c) Forge Development LLC * SPDX-License-Identifier: LGPL-2.1-only */ +/** + * Events can have characteristics that define how they behave, composed by implementing interfaces. + */ @NullMarked package net.minecraftforge.eventbus.api.event.characteristic; diff --git a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java index 018f47c..5db6e58 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java +++ b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java @@ -6,6 +6,7 @@ import net.minecraftforge.eventbus.api.bus.CancellableEventBus; import net.minecraftforge.eventbus.api.bus.EventBus; +import net.minecraftforge.eventbus.api.event.characteristic.Cancellable; import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.EventListenerImpl; @@ -13,21 +14,24 @@ /** * Users can retain instances of this interface to remove listeners that were previously added to the same - * {@link EventBus}.You can obtain instances of this interface by calling any of the {@code addListener} methods + * {@link EventBus}. You can obtain instances of this interface by calling any of the {@code addListener} methods * on an EventBus, such as {@link EventBus#addListener(Consumer)}. - *

Internally, this acts as a wrapper over lambdas to give them identity, enrich debug info and to allow - * various conversion operations to different lambda types.

+ * + * @implNote Internally, this acts as a wrapper over lambdas to give them identity, enrich debug info and to allow + * various conversion operations to different lambda types. */ public sealed interface EventListener permits EventListenerImpl { @SuppressWarnings("ClassEscapesDefinedScope") // ? can be a subtype of Event which is publicly accessible Class eventType(); /** + * The priority of this listener. Higher numbers are called first. * @see Priority */ byte priority(); /** + * Whether this listener is known to always cancel the {@link Cancellable} event when called. * @see CancellableEventBus#addListener(boolean, Consumer) */ default boolean alwaysCancelling() { diff --git a/src/main/java/net/minecraftforge/eventbus/internal/Event.java b/src/main/java/net/minecraftforge/eventbus/internal/Event.java index b76cc27..0e79d5a 100644 --- a/src/main/java/net/minecraftforge/eventbus/internal/Event.java +++ b/src/main/java/net/minecraftforge/eventbus/internal/Event.java @@ -8,4 +8,23 @@ import net.minecraftforge.eventbus.api.event.MutableEvent; import net.minecraftforge.eventbus.api.event.RecordEvent; +/** + * The internal marker interface for all event base types. This is internal and sealed for many reasons: + *
    + *
  • To avoid ambiguity in the system where different events in the inheritance chain may technically be events + * but not declare their type
  • + *
  • To discourage bad usages by library users that could hurt performance. For example, having all listeners + * bounce to a generic void handleEvent(Event e) method and doing chains of instanceof checks would be very + * slow.
  • + *
  • To minimise the public API surface - the more we have to support, the more restricted and complicated + * internals become
  • + *
+ * + *

Library users should use one of the three base types instead:

+ *
    + *
  • {@link MutableEvent} for classes
  • + *
  • {@link RecordEvent} for records
  • + *
  • {@link InheritableEvent} for interfaces
  • + *
+ */ public sealed interface Event permits InheritableEvent, MutableEvent, RecordEvent {}