Skip to content

Commit

Permalink
impl
Browse files Browse the repository at this point in the history
  • Loading branch information
MehVahdJukaar committed Jul 30, 2024
1 parent 037faad commit b5bd334
Show file tree
Hide file tree
Showing 6 changed files with 834 additions and 990 deletions.
13 changes: 5 additions & 8 deletions src/main/java/org/violetmoon/zeta/Zeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
import org.violetmoon.zeta.config.ConfigManager;
import org.violetmoon.zeta.config.IZetaConfigInternals;
import org.violetmoon.zeta.config.SectionDefinition;
import org.violetmoon.zeta.event.bus.IZetaLoadEvent;
import org.violetmoon.zeta.event.bus.IZetaPlayEvent;
import org.violetmoon.zeta.event.bus.LoadEvent;
import org.violetmoon.zeta.event.bus.PlayEvent;
import org.violetmoon.zeta.event.bus.ZetaEventBus;
import org.violetmoon.zeta.event.bus.*;
import org.violetmoon.zeta.item.ext.ItemExtensionFactory;
import org.violetmoon.zeta.module.ModuleFinder;
import org.violetmoon.zeta.module.ZetaCategory;
Expand Down Expand Up @@ -51,13 +47,14 @@ public abstract class Zeta implements IZeta {
public static final String ZETA_ID = "zeta";
public static final Logger GLOBAL_LOG = LogManager.getLogger(ZETA_ID);

public Zeta(String modid, Logger log, ZetaSide side) {
public Zeta(String modid, Logger log, ZetaSide side, ZetaEventBus<IZetaLoadEvent> loadBus, ZetaEventBus<IZetaPlayEvent> playBus) {
this.log = log;

this.modid = modid;
this.side = side;
this.loadBus = new ZetaEventBus<>(this, LoadEvent.class, IZetaLoadEvent.class, log);
this.playBus = new ZetaEventBus<>(this, PlayEvent.class, IZetaPlayEvent.class, null);

this.loadBus = loadBus;
this.playBus = playBus;

this.modules = createModuleManager();
this.registry = createRegistry();
Expand Down
197 changes: 197 additions & 0 deletions src/main/java/org/violetmoon/zeta/event/bus/FabricZetaEventBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package org.violetmoon.zeta.event.bus;

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.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.violetmoon.zeta.Zeta;

/**
* A polymorphic event bus. Events can be fired under one of their supertypes, allowing a sort of API/impl split of events.
*
* Due to implementation complexity, there is unfortunately no support for:
* - generic events (like Forge's RegistryEvent<T>)
* - registering an anonymous `Consumer` (like Forge's "addListener" method)
* Supported Java reflection APIs don't expose this information. Forge can only get at it with a library internally using sun.misc.Unsafe.
*/
public class FabricZetaEventBus<E> extends ZetaEventBus<E> {

private final Map<Class<? extends E>, Listeners> listenerMap = new HashMap<>();

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

@Override
protected void subscribeMethod(Method m, Object receiver, Class owningClazz) {
getListenersFor(m).subscribe(receiver, owningClazz, m);
}

@Override
protected void unsubscribeMethod(Method m, Object receiver, Class owningClazz) {
getListenersFor(m).unsubscribe(receiver, owningClazz, m);
}

/**
* Fires an event on the event bus. Each subscriber will be visited in order.
*/
public <T extends E> T fire(@NotNull T event) {
Listeners subs = listenerMap.get(event.getClass());
if(subs != null) {
if(logSpam != null)
logSpam.info("Dispatching {} to {} listener{}", logSpamSimpleName(event.getClass()), subs.size(), subs.size() > 1 ? "s" : "");

subs.doFire(event);
}

return 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 <T extends E> T fire(@NotNull T event, Class<? super T> firedAs) {
Listeners subs = listenerMap.get(firedAs);
if(subs != null) {
if(logSpam != null)
logSpam.info("Dispatching {} (as {}) to {} listener{}", logSpamSimpleName(event.getClass()), logSpamSimpleName(firedAs), subs.size(), subs.size() > 1 ? "s" : "");

subs.doFire(event);
}

return event;
}

//this is really silly
private String logSpamSimpleName(Class<?> clazz) {
String[] split = clazz.getName().split("\\.");
return split[split.length - 1];
}

public <T extends E> T fireExternal(@NotNull T event, Class<? super T> firedAs) {
event = fire(event, firedAs);

if(event instanceof Cancellable cancellable && cancellable.isCanceled())
return event;
else{
throw new RuntimeException();
//TODO: re add. this shuld be put in loader specific bus code
//return z.fireExternalEvent(event); // Interfaces with the platform-specific event bus utility
}

}

/**
* Picks out the "Foo" in "void handleFoo(Foo event)", and gets/creates the Listeners corresponding to that type.
*/
@SuppressWarnings("unchecked")
private Listeners getListenersFor(Method method) {
if(method.getParameterCount() != 1)
throw arityERR(method);

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

return listenerMap.computeIfAbsent((Class<? extends E>) eventType, __ -> new Listeners());
}

/**
* Mildly overengineered since I want method dispatching to hopefully be low-overhead... don't mind me
* MethodHandle is magic free performance right
* 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) {
if(this == object) return true;
if(object == null || getClass() != object.getClass()) return false;
Subscriber that = (Subscriber) object;
return receiver == that.receiver && //<-- object identity compare
Objects.equals(owningClazz, that.owningClazz) &&
Objects.equals(method, that.method);
}

@Override
public int hashCode() {
return System.identityHashCode(receiver) + owningClazz.hashCode() + method.hashCode();
}

MethodHandle unreflect() {
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);
return handle;
}
}


void subscribe(@Nullable Object receiver, Class<?> owningClazz, Method method) {
try {
handles.computeIfAbsent(new Subscriber(receiver, owningClazz, method), Subscriber::unreflect);
} catch (Exception e) {
throw unreflectERR(method, e);
}
}

void unsubscribe(@Nullable Object receiver, Class<?> owningClazz, Method method) {
handles.remove(new Subscriber(receiver, owningClazz, method));
}

int size() {
return handles.size();
}

//just hoisting the instanceof out of the loop.. No profiling just vibes <3
void doFire(E event) {
try {
if(event instanceof Cancellable cancellable)
doFireCancellable(cancellable);
else
doFireNonCancellable(event);
} catch (Throwable e) {
throw new RuntimeException("Exception while firing event " + event + ": ", e);
}
}

void doFireCancellable(Cancellable event) throws Throwable {
for(MethodHandle handle : handles.values()) {
handle.invoke(event);
if(event.isCanceled()) break;
}
}

void doFireNonCancellable(E event) throws Throwable {
for(MethodHandle handle : handles.values())
handle.invoke(event);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.violetmoon.zeta.event.bus.wip;
package org.violetmoon.zeta.event.bus;

import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.IEventBus;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.units.qual.A;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.violetmoon.zeta.Zeta;
Expand All @@ -20,7 +19,7 @@
import java.util.function.Function;

// this is quite jank. Basically converts all zeta events to forge ones, then delegates to the forge bus directly
public class ForgeZetaBus<E> extends ZetaBus<E> {
public class ForgeZetaEventBus<E> extends ZetaEventBus<E> {

private final Map<Class<? extends E>, Function<? extends Event, ? extends E>> forgeToZetaMap = new HashMap<>();
private final Map<Class<? extends E>, Function<? extends E, ? extends Event>> zetaToForgeMap = new HashMap<>();
Expand All @@ -32,14 +31,14 @@ public class ForgeZetaBus<E> extends ZetaBus<E> {
* @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, IEventBus forgeBus) {
super(z, subscriberAnnotation, eventRoot, logSpam);
public ForgeZetaEventBus(Class<? extends Annotation> subscriberAnnotation, Class<E> eventRoot,
@Nullable Logger logSpam, IEventBus forgeBus) {
super(subscriberAnnotation, eventRoot, logSpam);
this.forgeBus = forgeBus;
}

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

Expand All @@ -65,7 +64,7 @@ protected void addListener(Method method, Object receiver, Class<?> owningClazz)
}

@Override
protected void unregisterMethod(Method m, Object receiver, Class<?> owningClazz) {
protected void unsubscribeMethod(Method m, Object receiver, Class<?> owningClazz) {
var handler = convertedHandlers.remove(new Key(m, receiver, owningClazz));
if (handler != null) {
forgeBus.unregister(handler);
Expand All @@ -82,7 +81,7 @@ public <T extends E> T fire(@NotNull T event) {
}

@Override
public <T extends E> T fire(@NotNull T event, Class<? extends T> firedAs) {
public <T extends E> T fire(@NotNull T event, Class<? super T> firedAs) {
forgeBus.post(remapEvent(event, firedAs));
return event;
}
Expand Down
Loading

0 comments on commit b5bd334

Please sign in to comment.