Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>The uniqueness of this name is enforced when the bus group is {@linkplain #create(String) created}.</p>
*/
String name();

Expand All @@ -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.
* <p>Warning: This is a destructive operation - this BusGroup should not be used again after calling this method.</p>
* {@linkplain #shutdown() Shuts down} all EventBus instances associated with this BusGroup,
* {@linkplain #unregister(Collection) unregisters} all listeners and frees no longer needed resources.
*
* <p>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.</p>
*
* @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.
* <p>Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources.</p>
* Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources.
*
* <p>Warning: This is only intended to be called <b>once</b> after all listeners are registered - calling this
* repeatedly may hurt performance.</p>
*
* @apiNote This is an experimental feature that may be removed, renamed or otherwise changed without notice.
*/
void trim();

Expand All @@ -68,6 +99,11 @@ static BusGroup create(String name, Class<?> baseType) {
* @apiNote This method only registers static listeners.
* <p>If you want to register both instance and static methods, use
* {@link BusGroup#register(MethodHandles.Lookup, Object)} instead.</p>
* @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<EventListener> register(MethodHandles.Lookup callerLookup, Class<?> utilityClassWithStaticListeners);

Expand All @@ -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<EventListener> register(MethodHandles.Lookup callerLookup, Object listener);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

import java.util.function.Consumer;

/**
* @see CancellableEventBus if your event type implements {@link Cancellable}
* @param <T> The type of event this EventBus handles
*/
public sealed interface EventBus<T extends Event> permits CancellableEventBus, AbstractEventBusImpl, EventBusImpl {
/**
* Adds a listener to this EventBus with the default priority of {@link Priority#NORMAL}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>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.</p>
*
* <p>Consider {@link RecordEvent} for better performance and conciseness if field mutability and direct extendability
* aren't needed.</p>
*
* @see RecordEvent
*/
public non-sealed abstract class MutableEvent extends MutableEventInternals implements Event {}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>Provides a means for declaring events in a concise and performant manner.</p>
*
* <h2>Examples</h2>
* <p>Here are some conceptual examples of record events:</p>
* <ul>
* <li>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.</li>
* <li>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.</li>
* <li>Stateless events which do not carry any data inside but are still useful for notifying listeners <i>when</i>
* a specific action occurs, such as some types of lifecycle events.</li>
* </ul>
*
* <p>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.</p>
*
* @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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>When an event is cancelled, it will not be passed to any further non-monitor listeners.</p>
* 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.
*
* <p>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}.</p>
*
* @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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>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.</p>
* <p>Only supported for {@link MutableEvent} at this time.</p>
* 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>This is useful for single-use lifecycle events.</p>
*
* @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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import net.minecraftforge.eventbus.internal.EventCharacteristic;

/**
* Experimental feature - may be removed, renamed or otherwise changed without notice.
* <p>{@link SelfPosting} events are associated with a default {@link EventBus} in order to offer some convenience
* instance methods.</p>
* <u>Example</u>
* Self-posting events are associated with a default {@link EventBus}, allowing for some convenience methods on the
* instance.
*
* <h2>Example</h2>
* {@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<ExampleEvent>, RecordEvent {
Expand All @@ -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<T extends Event> 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<T> getDefaultBus();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,32 @@

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;

import java.util.function.Consumer;

/**
* 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)}.
* <p>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.</p>
*
* @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<? extends Event> 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() {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/net/minecraftforge/eventbus/internal/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* <ul>
* <li>To avoid ambiguity in the system where different events in the inheritance chain may technically be events
* but not declare their type</li>
* <li>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.</li>
* <li>To minimise the public API surface - the more we have to support, the more restricted and complicated
* internals become</li>
* </ul>
*
* <p>Library users should use one of the three base types instead:</p>
* <ul>
* <li>{@link MutableEvent} for classes</li>
* <li>{@link RecordEvent} for records</li>
* <li>{@link InheritableEvent} for interfaces</li>
* </ul>
*/
public sealed interface Event permits InheritableEvent, MutableEvent, RecordEvent {}