/* * This file is part of architectury. * Copyright (C) 2020, 2021 shedaniel * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package me.shedaniel.architectury.event; import com.google.common.reflect.AbstractInvocationHandler; import me.shedaniel.architectury.ForgeEvent; import me.shedaniel.architectury.ForgeEventCancellable; import me.shedaniel.architectury.annotations.ExpectPlatform; import net.jodah.typetools.TypeResolver; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; public final class EventFactory { private EventFactory() {} @Deprecated @ApiStatus.ScheduledForRemoval public static Event create(Function function) { Class[] arguments = TypeResolver.resolveRawArguments(Function.class, function.getClass()); T[] array; try { array = (T[]) Array.newInstance(arguments[1], 0); } catch (Exception e) { throw new RuntimeException(e); } return of(list -> function.apply(list.toArray(array))); } public static Event of(Function, T> function) { return new EventImpl<>(function); } @SafeVarargs public static Event createLoop(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); return createLoop((Class) typeGetter.getClass().getComponentType()); } @SuppressWarnings("UnstableApiUsage") public static Event createLoop(Class clazz) { return of(listeners -> (T) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{clazz}, new AbstractInvocationHandler() { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (T listener : listeners) { method.invoke(listener, args); } return null; } })); } @SafeVarargs public static Event createInteractionResult(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); return createInteractionResult((Class) typeGetter.getClass().getComponentType()); } @SuppressWarnings("UnstableApiUsage") public static Event createInteractionResult(Class clazz) { return of(listeners -> (T) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{clazz}, new AbstractInvocationHandler() { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (T listener : listeners) { InteractionResult result = (InteractionResult) method.invoke(listener, args); if (result != InteractionResult.PASS) { return result; } } return InteractionResult.PASS; } })); } @SafeVarargs public static Event createInteractionResultHolder(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); return createInteractionResultHolder((Class) typeGetter.getClass().getComponentType()); } @SuppressWarnings("UnstableApiUsage") public static Event createInteractionResultHolder(Class clazz) { return of(listeners -> (T) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{clazz}, new AbstractInvocationHandler() { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (T listener : listeners) { InteractionResultHolder result = (InteractionResultHolder) Objects.requireNonNull(method.invoke(listener, args)); if (result.getResult() != InteractionResult.PASS) { return result; } } return InteractionResultHolder.pass(null); } })); } @SafeVarargs public static Event> createConsumerLoop(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); return createConsumerLoop((Class) typeGetter.getClass().getComponentType()); } @SuppressWarnings("UnstableApiUsage") public static Event> createConsumerLoop(Class clazz) { Event> event = of(listeners -> (Consumer) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{Consumer.class}, new AbstractInvocationHandler() { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (Consumer listener : listeners) { method.invoke(listener, args); } return null; } })); Class superClass = clazz; do { if (superClass.isAnnotationPresent(ForgeEvent.class)) { return attachToForge(event); } superClass = superClass.getSuperclass(); } while (superClass != null); return event; } @SafeVarargs public static Event> createActorLoop(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); return createActorLoop((Class) typeGetter.getClass().getComponentType()); } @SuppressWarnings("UnstableApiUsage") public static Event> createActorLoop(Class clazz) { Event> event = of(listeners -> (Actor) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{Actor.class}, new AbstractInvocationHandler() { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (Actor listener : listeners) { InteractionResult result = (InteractionResult) method.invoke(listener, args); if (result != InteractionResult.PASS) { return result; } } return InteractionResult.PASS; } })); Class superClass = clazz; do { if (superClass.isAnnotationPresent(ForgeEventCancellable.class)) { return attachToForgeActorCancellable(event); } superClass = superClass.getSuperclass(); } while (superClass != null); superClass = clazz; do { if (superClass.isAnnotationPresent(ForgeEvent.class)) { return attachToForgeActor(event); } superClass = superClass.getSuperclass(); } while (superClass != null); return event; } @ExpectPlatform public static Event> attachToForge(Event> event) { throw new AssertionError(); } @ExpectPlatform public static Event> attachToForgeActor(Event> event) { throw new AssertionError(); } @ExpectPlatform public static Event> attachToForgeActorCancellable(Event> event) { throw new AssertionError(); } private static class EventImpl implements Event { private final Function, T> function; private T invoker = null; private ArrayList listeners; public EventImpl(Function, T> function) { this.function = function; this.listeners = new ArrayList<>(); } @Override public T invoker() { if (invoker == null) { update(); } return invoker; } @Override public void register(T listener) { listeners.add(listener); invoker = null; } @Override public void unregister(T listener) { listeners.remove(listener); listeners.trimToSize(); invoker = null; } @Override public boolean isRegistered(T listener) { return listeners.contains(listener); } @Override public void clearListeners() { listeners.clear(); listeners.trimToSize(); invoker = null; } public void update() { if (listeners.size() == 1) { invoker = listeners.get(0); } else { invoker = function.apply(listeners); } } } }