mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-08 21:14:41 -04:00
Change event behavior for one listener, introduce AutoInvokingEvent
, update ArrayBackedEvent
implementation (#1369)
* Cleanup Events * Add the AutoInvokingEvent annotation. Co-authored-by: Player <sfPlayer1@users.noreply.github.com> Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
This commit is contained in:
parent
1f1ad061d4
commit
4b24d382c1
4 changed files with 85 additions and 37 deletions
fabric-api-base/src/main/java/net/fabricmc/fabric
api/event
impl/base/event
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.event;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that this {@link Event} is auto-invoking:
|
||||
* it calls the event callback implemented by a context parameter type automatically and without registration.
|
||||
*
|
||||
* <p>This means that this event can be listened to in two ways:
|
||||
* <ul>
|
||||
* <li>If the consumer is the context parameter and it implements the callback, it will be automatically invoked, don't register manually.
|
||||
* <li>Otherwise, there is no invocation and the listener needs manual registration as usual.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Do note that there may be more than one context parameter.
|
||||
*
|
||||
* <p>A typical use case is feature augmentation, for example to expose raw clicks to slots.
|
||||
* The event callback has a slot parameter - the context parameter - and the event itself is carrying this annotation.
|
||||
* All the slot needs to receive slot clicks is to implement {@code SlotClickCallback} on itself.
|
||||
* It shouldn't do any explicit event registration like {@code SLOT_CLICK_EVENT.register(this::onSlotClick)},
|
||||
* otherwise it will see extraneous callback invocations.
|
||||
*
|
||||
* <p>In general, an auto-invoking event bridges the gap between the flexibility of an event with global reach,
|
||||
* and the convenience of implementing an interface that gets detected automatically.
|
||||
*
|
||||
* <p>This is a documentation-only annotation, the event factory has to implement the functionality explicitly by checking the parameter type and invoking it.
|
||||
* On top of adding this annotation, the event field or method should document which parameters are context parameters,
|
||||
* and under which circumstances they are invoked.
|
||||
*/
|
||||
// TODO: explore enforcing that auto-invoked listeners don't register themselves.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
public @interface AutoInvokingEvent {
|
||||
}
|
|
@ -48,6 +48,10 @@ public final class EventFactory {
|
|||
/**
|
||||
* Create an "array-backed" Event instance.
|
||||
*
|
||||
* <p>If your factory simply delegates to the listeners without adding custom behavior,
|
||||
* consider using {@linkplain #createArrayBacked(Class, Object, Function) the other overload}
|
||||
* if performance of this event is critical.
|
||||
*
|
||||
* @param type The listener class type.
|
||||
* @param invokerFactory The invoker factory, combining multiple listeners into one instance.
|
||||
* @param <T> The listener type.
|
||||
|
@ -58,7 +62,14 @@ public final class EventFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create an "array-backed" Event instance with a custom empty invoker.
|
||||
* Create an "array-backed" Event instance with a custom empty invoker,
|
||||
* for an event whose {@code invokerFactory} only delegates to the listeners.
|
||||
* <ul>
|
||||
* <li>If there is no listener, the custom empty invoker will be used.</li>
|
||||
* <li><b>If there is only one listener, that one will be used as the invoker
|
||||
* and the factory will not be called.</b></li>
|
||||
* <li>Only when there are at least two listeners will the factory be used.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Having a custom empty invoker (of type (...) -> {}) increases performance
|
||||
* relative to iterating over an empty array; however, it only really matters
|
||||
|
@ -70,9 +81,16 @@ public final class EventFactory {
|
|||
* @param <T> The listener type.
|
||||
* @return The Event instance.
|
||||
*/
|
||||
// TODO: Deprecate this once we have working codegen
|
||||
public static <T> Event<T> createArrayBacked(Class<T> type, T emptyInvoker, Function<T[], T> invokerFactory) {
|
||||
return EventFactoryImpl.createArrayBacked(type, emptyInvoker, invokerFactory);
|
||||
return createArrayBacked(type, listeners -> {
|
||||
if (listeners.length == 0) {
|
||||
return emptyInvoker;
|
||||
} else if (listeners.length == 1) {
|
||||
return listeners[0];
|
||||
} else {
|
||||
return invokerFactory.apply(listeners);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,6 +18,7 @@ package net.fabricmc.fabric.impl.base.event;
|
|||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
@ -25,52 +26,30 @@ import java.util.function.Function;
|
|||
import net.fabricmc.fabric.api.event.Event;
|
||||
|
||||
class ArrayBackedEvent<T> extends Event<T> {
|
||||
private final Class<? super T> type;
|
||||
private final Function<T[], T> invokerFactory;
|
||||
private final T dummyInvoker;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private T[] handlers;
|
||||
|
||||
ArrayBackedEvent(Class<? super T> type, T dummyInvoker, Function<T[], T> invokerFactory) {
|
||||
this.type = type;
|
||||
this.dummyInvoker = dummyInvoker;
|
||||
@SuppressWarnings("unchecked")
|
||||
ArrayBackedEvent(Class<? super T> type, Function<T[], T> invokerFactory) {
|
||||
this.invokerFactory = invokerFactory;
|
||||
this.handlers = (T[]) Array.newInstance(type, 0);
|
||||
update();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void update() {
|
||||
if (handlers == null) {
|
||||
if (dummyInvoker != null) {
|
||||
invoker = dummyInvoker;
|
||||
} else {
|
||||
invoker = invokerFactory.apply((T[]) Array.newInstance(type, 0));
|
||||
}
|
||||
} else if (handlers.length == 1) {
|
||||
invoker = handlers[0];
|
||||
} else {
|
||||
invoker = invokerFactory.apply(handlers);
|
||||
}
|
||||
this.invoker = invokerFactory.apply(handlers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void register(T listener) {
|
||||
if (listener == null) {
|
||||
throw new NullPointerException("Tried to register a null listener!");
|
||||
}
|
||||
Objects.requireNonNull(listener, "Tried to register a null listener!");
|
||||
|
||||
lock.lock();
|
||||
|
||||
try {
|
||||
if (handlers == null) {
|
||||
handlers = (T[]) Array.newInstance(type, 1);
|
||||
handlers[0] = listener;
|
||||
} else {
|
||||
handlers = Arrays.copyOf(handlers, handlers.length + 1);
|
||||
handlers[handlers.length - 1] = listener;
|
||||
}
|
||||
|
||||
handlers = Arrays.copyOf(handlers, handlers.length + 1);
|
||||
handlers[handlers.length - 1] = listener;
|
||||
update();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
|
|
@ -39,11 +39,7 @@ public final class EventFactoryImpl {
|
|||
}
|
||||
|
||||
public static <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
|
||||
return createArrayBacked(type, null /* buildEmptyInvoker(type, invokerFactory) */, invokerFactory);
|
||||
}
|
||||
|
||||
public static <T> Event<T> createArrayBacked(Class<? super T> type, T emptyInvoker, Function<T[], T> invokerFactory) {
|
||||
ArrayBackedEvent<T> event = new ArrayBackedEvent<>(type, emptyInvoker, invokerFactory);
|
||||
ArrayBackedEvent<T> event = new ArrayBackedEvent<>(type, invokerFactory);
|
||||
ARRAY_BACKED_EVENTS.add(event);
|
||||
return event;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue