Skip to content

Commit

Permalink
Added @ModuleInstance annotation. Annotate a static field inside your…
Browse files Browse the repository at this point in the history
… module class to have it populated with its instance, allowing for easy singleton patterns. This is opt in
  • Loading branch information
MehVahdJukaar committed Jul 30, 2024
1 parent 832410e commit 1b5a03c
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ private Listeners getListenersFor(Method method) {
* Pausefrogeline
*/
private class Listeners {

private final Map<Subscriber, MethodHandle> handles = new LinkedHashMap<>();

private record Subscriber(@Nullable Object receiver, Class<?> owningClazz, Method method) {
@Override
public boolean equals(Object object) {
Expand Down Expand Up @@ -200,7 +203,6 @@ MethodHandle unreflect() {
}
}

private final Map<Subscriber, MethodHandle> handles = new LinkedHashMap<>();

void subscribe(@Nullable Object receiver, Class<?> owningClazz, Method method) {
try {
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/org/violetmoon/zeta/event/bus/wip/ForgeZetaBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.violetmoon.zeta.event.bus.wip;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.IEventBus;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.violetmoon.zeta.Zeta;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

public class ForgeZetaBus<E> extends ZetaBus<E> {

private final Map<Class<? extends E>, Function<? extends Event, ? extends E>> forgeToZetaMap = new HashMap<>();
private final Map<Class<? extends Event>, Function<? extends E, ? extends Event>> zetaToForgeMap = new HashMap<>();
//ForgeZAddReloadListener.class, (Function<AddReloadListenerEvent, IZetaLoadEvent>) ForgeZAddReloadListener::new

private final IEventBus forgeBus;

/**
* @param subscriberAnnotation The annotation that subscribe()/unsubscribe() will pay attention to.
* @param eventRoot The superinterface of all events fired on this bus.
*/
public ForgeZetaBus(Zeta z, Class<? extends Annotation> subscriberAnnotation, Class<E> eventRoot, @Nullable Logger logSpam) {
super(z, subscriberAnnotation, eventRoot, logSpam);
this.forgeBus = MinecraftForge.EVENT_BUS;
}

public void registerEventMappings(Class<? extends E> zetaEvent,
Function<? extends Event, ? extends E> forgeToZeta,
Class<? extends Event> forgeEvent,
Function<? extends E, ? extends Event> zetaToForge) {
forgeToZetaMap.put(zetaEvent, forgeToZeta);
zetaToForgeMap.put(forgeEvent, zetaToForge);
}

// takes a method that takes a zeta event and turns into one that takes a forge event
private Consumer<? extends Event> remapMethod(MethodHandle zetaEventConsumer, Class<? extends E> zetaEventClass) {
Function<? extends Event, ? extends E> forgeToZetaFunc = forgeToZetaMap.get(zetaEventClass);
return createForgeConsumer(zetaEventConsumer, forgeToZetaFunc);
}

private <Z extends E, F extends Event> Consumer<F> createForgeConsumer(MethodHandle zetaEventConsumer, Function<F, Z> forgeToZetaFunc) {
return event -> {
try {
zetaEventConsumer.invoke(forgeToZetaFunc.apply(event));
} catch (Throwable e) {
throw new RuntimeException(e);
}
};
}

@Override
protected void addListener(Method method, Object receiver, Class<?> owningClazz) {
if (method.getParameterCount() != 1)
throw arityERR(method);

Class<?> eventType = method.getParameterTypes()[0];
if (!eventRoot.isAssignableFrom(eventType))
throw typeERR(method);

MethodHandle handle;
try {
handle = MethodHandles.publicLookup().unreflect(method);
} catch (Exception e) {
throw new RuntimeException(e);
}

//fill in the "this" parameter
if (receiver != null)
handle = handle.bindTo(receiver);

forgeBus.addListener(remapMethod(handle, (Class<? extends E>) eventType));
}

@Override
protected void removeListener(Method m, Object receiver, Class<?> owningClazz) {

}


@Override
public <T extends E> T fire(@NotNull T event) {
forgeBus.post(remapEvent(event));
return event;
}

private <T extends E> Event remapEvent(@NotNull T event) {
Function<? extends E, ? extends Event> zetaToForgeFunc = zetaToForgeMap.get(event.getClass());
return createForgeEvent(event, zetaToForgeFunc);
}

private <T extends E> Event createForgeEvent(@NotNull E event, Function<T, ? extends Event> function) {
return function.apply((T) event);
}

@Override
public <T extends E> T fire(@NotNull T event, Class<? extends T> firedAs) {
return null;
}
}
125 changes: 125 additions & 0 deletions src/main/java/org/violetmoon/zeta/event/bus/wip/ZetaBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.violetmoon.zeta.event.bus.wip;

import com.google.common.base.Preconditions;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.violetmoon.zeta.Zeta;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.stream.Stream;

public abstract class ZetaBus<E> {

protected final Class<? extends Annotation> subscriberAnnotation;
protected final Class<E> eventRoot;
protected final Zeta z;
protected final @Nullable Logger logSpam;

/**
* @param subscriberAnnotation The annotation that subscribe()/unsubscribe() will pay attention to.
* @param eventRoot The superinterface of all events fired on this bus.
*/
public ZetaBus(Zeta z, Class<? extends Annotation> subscriberAnnotation, Class<E> eventRoot, @Nullable Logger logSpam) {
Preconditions.checkArgument(eventRoot.isInterface(), "Event roots should be an interface");

this.z = z;
this.subscriberAnnotation = subscriberAnnotation;
this.eventRoot = eventRoot;
this.logSpam = logSpam;
}

/**
* If the parameter is a Class: subscribes all static methods from it (and its superclasses) to the event bus.
* Otherwise, subscribes all non-static methods on that object (and its superclasses) to the event bus.
* (Note that the event bus will hold a reference to this object.)
*/
public ZetaBus<E> subscribe(@NotNull Object target) {
Preconditions.checkNotNull(target, "null passed to subscribe");

Object receiver;
Class<?> owningClazz;
if (target instanceof Class<?> clazz) {
receiver = null;
owningClazz = clazz;
} else {
receiver = target;
owningClazz = target.getClass();
}

streamAnnotatedMethods(owningClazz, receiver == null)
.forEach(m -> addListener(m, receiver, owningClazz));
return this;
}

protected abstract void addListener(Method m, Object receiver, Class<?> owningClazz);

/**
* If the parameter is a Class: unsubscribes all static methods from it (and its superclasses) from the event bus.
* Otherwise, unsubscribes all non-static methods on that object (and its superclasses) from the event bus.
*/
public ZetaBus<E> unsubscribe(@NotNull Object target) {
Preconditions.checkNotNull(target, "null passed to unsubscribe");

Object receiver;
Class<?> owningClazz;
if (target instanceof Class<?> clazz) {
receiver = null;
owningClazz = clazz;
} else {
receiver = target;
owningClazz = target.getClass();
}

streamAnnotatedMethods(owningClazz, receiver == null)
.forEach(m -> removeListener(m, receiver, owningClazz));
return this;
}

protected abstract void removeListener(Method m, Object receiver, Class<?> owningClazz);

/**
* Fires an event on the event bus. Each subscriber will be visited in order.
*/
public abstract <T extends E> T fire(@NotNull T event);

/**
* Fires an event on the event bus. Each subscriber will be visited in order.
* Listeners for "firedAs" will be invoked, instead of listeners for the event's own class.
* <p>
* (The generic should be Class&lt;? super T & ? extends E&gt;, but unfortunately, javac.)
*/
public abstract <T extends E> T fire(@NotNull T event, Class<? extends T> firedAs);

/**
* Grabs methods from this class (and its superclasses, recursively) that are annotated with this bus's
* annotation; and of the requested staticness.
*/
private Stream<Method> streamAnnotatedMethods(Class<?> owningClazz, boolean wantStatic) {
return Arrays.stream(owningClazz.getMethods())
.filter(m -> m.isAnnotationPresent(subscriberAnnotation) && ((m.getModifiers() & Modifier.STATIC) != 0) == wantStatic);
}

protected RuntimeException arityERR(Method method) {
return methodProblem("Method annotated with @" + subscriberAnnotation.getSimpleName() +
" should take 1 parameter.", method, null);
}

protected RuntimeException typeERR(Method method) {
return methodProblem("Method annotated with @" + subscriberAnnotation.getSimpleName() +
" should take an implementor of " + eventRoot.getSimpleName() + ".", method, null);
}

protected RuntimeException unreflectERR(Method method, Throwable cause) {
return methodProblem("Exception unreflecting a @" + subscriberAnnotation.getSimpleName() +
" method, is it public?", method, cause);
}

protected static RuntimeException methodProblem(String problem, Method method, @Nullable Throwable cause) {
return new RuntimeException("%s%nMethod class: %s%nMethod name: %s".formatted(
problem, method.getDeclaringClass().getName(), method.getName()), cause);
}
}
42 changes: 0 additions & 42 deletions src/main/java/org/violetmoon/zetaimplforge/event/load/Test.java

This file was deleted.

0 comments on commit 1b5a03c

Please sign in to comment.