Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.minecraftforge.eventbus.testjar.events;

import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.InheritableEvent;

/**
* This event tests the passthrough optimisation.
* <p>An event is considered passthrough when all the following conditions are met:</p>
* <ul>
* <li>The parent is sealed with a single child</li>
* <li>The child is final or a record</li>
* <li>They share the same event characteristics</li>
* </ul>
* <p>When these conditions are met, the {@code EventBus.create(Impl.class)} call returns the EventBus instance of its
* parent, instead of creating a new EventBus for the child. All calls to the child go directly to the parent, saving
* memory.</p>
*/
public sealed interface PassthroughEvent extends InheritableEvent {
EventBus<PassthroughEvent> BUS = EventBus.create(PassthroughEvent.class);

record Impl() implements PassthroughEvent {
public static final EventBus<Impl> BUS = EventBus.create(Impl.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.eventbus.api.event.characteristic.MonitorAware;
import net.minecraftforge.eventbus.testjar.events.PassthroughEvent;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -91,6 +92,17 @@ interface Child extends InheritableTestEvent {}
);
}

/**
* Tests that the references of the {@code BUS} fields of {@link PassthroughEvent} and its child refer to
* the same instance.
* @see PassthroughEvent
*/
@Test
public void testPassthroughEventCreation() {
Assertions.assertSame(PassthroughEvent.BUS, PassthroughEvent.Impl.BUS);
Assertions.assertEquals(PassthroughEvent.BUS.hashCode(), PassthroughEvent.Impl.BUS.hashCode());
}

/**
* Tests that only cancellable events can be created with a {@link CancellableEventBus}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.eventbus.internal.EventBusImpl;
import net.minecraftforge.eventbus.testjar.events.PassthroughEvent;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -231,4 +232,31 @@ final class SubEvent extends SuperEvent implements Cancellable {

SuperEvent.BUS.removeListener(listener);
}

/**
* Tests that listener inheritance works when posting in a passthrough context.
* @see PassthroughEvent
*/
@Test
public void testListenerCallInheritanceWithPassthrough() {
var handled = new AtomicBoolean();
var listener = PassthroughEvent.BUS.addListener(event -> handled.set(true));

Assertions.assertFalse(handled.get(), "PassthroughEvent should not be handled yet");

Assertions.assertDoesNotThrow(() -> PassthroughEvent.Impl.BUS.post(new PassthroughEvent.Impl()));

Assertions.assertTrue(handled.get(), "PassthroughEvent should be handled");

PassthroughEvent.BUS.removeListener(listener);
handled.set(false);

listener = PassthroughEvent.Impl.BUS.addListener(event -> handled.set(true));

Assertions.assertDoesNotThrow(() -> PassthroughEvent.Impl.BUS.post(new PassthroughEvent.Impl()));

Assertions.assertTrue(handled.get(), "PassthroughEvent should be handled");

PassthroughEvent.BUS.removeListener(listener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
public sealed interface AbstractEventBusImpl<T extends Event, I> extends EventBus<T>
permits CancellableEventBusImpl, EventBusImpl {
//region Record component accessors
Class<T> eventType();
ArrayList<EventListener> backingList();
ArrayList<EventListener> monitorBackingList();
List<AbstractEventBusImpl<?, ?>> children();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ private <T extends Event> EventBus<T> createEventBus(Class<T> eventType) {

int characteristics = AbstractEventBusImpl.computeEventCharacteristics(eventType);

boolean isRecord = (Constants.STRICT_BUS_CREATION_CHECKS || Constants.isInheritable(characteristics)) && eventType.isRecord();
if (Constants.STRICT_BUS_CREATION_CHECKS) {
boolean isRecord = eventType.isRecord();
if (!isRecord && RecordEvent.class.isAssignableFrom(eventType))
throw new IllegalArgumentException("Event type " + eventType + " implements RecordEvent but is not a record class");

Expand All @@ -113,13 +113,29 @@ private <T extends Event> EventBus<T> createEventBus(Class<T> eventType) {
}
}

var backingList = new ArrayList<EventListener>();
ArrayList<EventListener> backingList;
List<EventBus<?>> parents = Collections.emptyList();
if (Constants.isInheritable(characteristics)) {
parents = getParentEvents(eventType);

// Direct pass-through of sealed inheritable events that only have one subclass to save memory (EventBus#97)
if ((isRecord || Modifier.isFinal(eventType.getModifiers())) // if this event is effectively final
&& parents.size() == 1 // only has one parent EventBus
&& parents.getFirst() instanceof AbstractEventBusImpl<?, ?> parent) {
var permittedSubclasses = parent.eventType().getPermittedSubclasses(); // that parent is sealed
if (permittedSubclasses != null && permittedSubclasses.length == 1 // only permits this event
&& characteristics == parent.eventCharacteristics()) { // has the same characteristics
assert permittedSubclasses[0] == eventType;
return (EventBus<T>) parents.getFirst(); // then we can reuse the parent directly
}
}

backingList = new ArrayList<>();
for (var parent : parents) {
backingList.addAll(((AbstractEventBusImpl<?, ?>) parent).backingList());
}
} else {
backingList = new ArrayList<>();
}

@SuppressWarnings("rawtypes")
Expand Down