Skip to content

Commit

Permalink
Added custom EventBus (not implemented yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
fr1kin committed Dec 14, 2020
1 parent b945580 commit 6eeb6a2
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 32 deletions.
13 changes: 10 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ minecraft {
}

mapper {
include sourceSets.main
include sourceSets.main, sourceSets.test
}

dependencies {
Expand All @@ -106,15 +106,22 @@ dependencies {

minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}"

testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.15.0'
testCompileOnly group: 'org.projectlombok', name: 'lombok', version: lombokVersion
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombokVersion
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0'
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0'
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
}

compileJava {
// this will allow use to read a constructors parameter names at runtime
options.compilerArgs << '-parameters'
}

test {
useJUnitPlatform()
}

jar {
manifest {
attributes([
Expand Down
5 changes: 2 additions & 3 deletions buildSrc/Annotations/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
plugins {
id 'java'
id 'java'
}

group 'dev.fiki.forgehax.api.asm'

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package dev.fiki.forgehax.api.event;

public interface EventListener extends Comparable<EventListener> {
public interface EventListener {
void run(Event event);

default int getPriority() {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,68 @@
package dev.fiki.forgehax.api.event;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class ListenerList {
public final class ListenerList implements Iterable<EventListener> {
private static final Comparator<EventListener> EVENT_LISTENER_COMPARATOR =
Comparator.comparing(EventListener::getPriority);

private final Class<?> eventType;
private volatile List<EventListener> listeners = Collections.emptyList();

public ListenerList(Class<?> classType) {
public ListenerList(Class<?> eventType) {
this.eventType = Objects.requireNonNull(eventType, "event type cannot be null");
}

public Class<?> getEventType() {
return eventType;
}

public synchronized void registerAll(Collection<EventListener> listeners) {
List<EventListener> mutable = new ArrayList<>(this.listeners);
mutable.addAll(listeners);
mutable.sort(EVENT_LISTENER_COMPARATOR);

this.listeners = Collections.unmodifiableList(mutable);
}

public void register(EventListener listener) {
registerAll(Collections.singleton(listener));
}

public synchronized void unregisterAll(Collection<EventListener> listeners) {
List<EventListener> mutable = new ArrayList<>(this.listeners);
mutable.removeAll(listeners);

this.listeners = Collections.unmodifiableList(mutable);
}

public void registerAll(Collection<EventListener> listeners) {
public void unregister(EventListener listener) {
unregisterAll(Collections.singleton(listener));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

ListenerList that = (ListenerList) o;

return eventType.equals(that.eventType);
}

@Override
public int hashCode() {
return eventType.hashCode();
}

@Override
public Iterator<EventListener> iterator() {
return listeners.listIterator();
}

public Stream<EventListener> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ class MapperExtension {
project.dependencies.add(sourceSet.annotationProcessorConfigurationName, pluginJar)
}

void include(SourceSet sourceSet) {
// add the api dependency to the project
dependencyOnly(sourceSet)

project.tasks.find { it.getName() == sourceSet.getCompileJavaTaskName() }.with {
it.dependsOn importSourcesTask
void include(SourceSet... sourceSets) {
sourceSets.each { SourceSet sourceSet ->
// add the api dependency to the project
dependencyOnly(sourceSet)

project.tasks.find { it.getName() == sourceSet.getCompileJavaTaskName() }.with {
it.dependsOn importSourcesTask
}
}
}
}
95 changes: 95 additions & 0 deletions src/main/java/dev/fiki/forgehax/api/event/EventBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dev.fiki.forgehax.api.event;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import dev.fiki.forgehax.api.Tuple;
import lombok.SneakyThrows;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class EventBus {
private static final Map<Class<?>, Event> EVENT_FACTORY_CACHE = Maps.newConcurrentMap();

private final Map<Integer, List<Tuple<EventListener, Event>>> trackedListeners = Maps.newHashMap();

public void register(Object obj) {
final Class<?> objClass = obj.getClass();

// list of active listeners
List<Tuple<EventListener, Event>> tracks = Lists.newArrayList();

// only get visible methods
for (Method method : objClass.getMethods()) {
if (method.isAnnotationPresent(SubscribeListener.class)) {
Event event = getEventFactory(getMethodEventType(method));
EventListener listener = new EventListenerWrapper(obj, method);
event.getListenerList().register(listener);
tracks.add(new Tuple<>(listener, event));
}
}

synchronized (trackedListeners) {
trackedListeners.put(System.identityHashCode(obj), tracks);
}
}

public void unregister(Object obj) {
final int id = System.identityHashCode(obj);

synchronized (trackedListeners) {
List<Tuple<EventListener, Event>> tracked = trackedListeners.get(id);

if (tracked != null) {
for (Tuple<EventListener, Event> tuple : tracked) {
tuple.getSecond().getListenerList().unregister(tuple.getFirst());
}

trackedListeners.remove(id);
}
}
}

public <T extends Event> void post(T event) {
for (EventListener listener : event.getListenerList()) {
listener.run(event);
}
}

List<EventListener> getObjectListeners(Object obj) {
synchronized (trackedListeners) {
List<Tuple<EventListener, Event>> list = trackedListeners.get(System.identityHashCode(obj));
if (list != null) {
return list.stream()
.map(Tuple::getFirst)
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
}

private static Event getEventFactory(Class<?> eventClass) {
return EVENT_FACTORY_CACHE.computeIfAbsent(eventClass, EventBus::createNewEventFactory);
}

@SneakyThrows
private static Event createNewEventFactory(Class<?> clazz) {
return (Event) clazz.newInstance();
}

private static Class<?> getMethodEventType(Method method) {
if (method.getParameterCount() != 1) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" must have exactly 1 argument!");
}

Class<?> type = method.getParameterTypes()[0];
if (!Event.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" argument must be assignable form of Event!");
}
return type;
}
}
142 changes: 142 additions & 0 deletions src/main/java/dev/fiki/forgehax/api/event/EventListenerWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package dev.fiki.forgehax.api.event;

import com.google.common.collect.Maps;
import dev.fiki.forgehax.api.common.PriorityEnum;
import lombok.Getter;
import lombok.SneakyThrows;
import org.objectweb.asm.*;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import static org.objectweb.asm.Opcodes.*;

@Getter
public final class EventListenerWrapper implements EventListener {
private static final Map<Method, Class<?>> METHOD_WRAPPERS = Maps.newConcurrentMap();

private final Object declaringInstance;
private final Method method;
private final EventListener instance;
private final int priority;

@SneakyThrows
EventListenerWrapper(Object declaringInstance, Method method) {
if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" must be public!");
} else if (method.getReturnType() != void.class) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" must have a void return type!");
} else if (method.getParameterCount() != 1) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" must have exactly 1 argument!");
} else if (!Event.class.isAssignableFrom(method.getParameterTypes()[0])) {
throw new IllegalArgumentException("Method \"" + method.getName() + "\" argument must be assignable form of Event!");
}

this.declaringInstance = declaringInstance;
this.method = method;
this.instance = (EventListener) getOrCreateWrapperClass(method)
.getConstructor(declaringInstance.getClass())
.newInstance(declaringInstance);

this.priority = method.isAnnotationPresent(SubscribeListener.class)
? method.getAnnotation(SubscribeListener.class).priority().ordinal()
: PriorityEnum.DEFAULT.ordinal();
}

@Override
public void run(Event event) {
instance.run(event);
}

private static Class<?> getOrCreateWrapperClass(Method method) {
return METHOD_WRAPPERS.computeIfAbsent(method, EventListenerWrapper::genWrapperClass);
}

@SneakyThrows
private static Class<?> genWrapperClass(Method method) {
final String methodName = method.getName();
final Type declaring = Type.getType(method.getDeclaringClass());
final Type wrapper = Type.getObjectType(declaring.getInternalName() + "$" + method.getName() + "Event");
final Type eventType = Type.getType(method.getParameterTypes()[0]);

ClassLoader cl = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = createWrapperClassBytes(wrapper, declaring, methodName, eventType);
return defineClass(name, bytes, 0, bytes.length);
}
};

return cl.loadClass(wrapper.getClassName());
}

private static byte[] createWrapperClassBytes(Type classType, Type targetType, String targetMethodName, Type eventType) {
final ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;

cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, classType.getInternalName(), null, "java/lang/Object", new String[]{Type.getType(EventListener.class).getInternalName()});

cw.visitSource(".dynamic", null);

{
fv = cw.visitField(ACC_PRIVATE | ACC_FINAL, "instance", targetType.getDescriptor(), null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, targetType), null, null);
mv.visitCode();

Label label0 = new Label();
mv.visitLabel(label0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);

Label label1 = new Label();
mv.visitLabel(label1);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, classType.getInternalName(), "instance", targetType.getDescriptor());

Label label2 = new Label();
mv.visitLabel(label2);
mv.visitInsn(RETURN);

Label label3 = new Label();
mv.visitLabel(label3);
mv.visitLocalVariable("this", classType.getDescriptor(), null, label0, label3, 0);
mv.visitLocalVariable("instance", targetType.getDescriptor(), null, label0, label3, 1);
mv.visitMaxs(2, 2);

mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "run", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Event.class)), null, null);
mv.visitCode();

Label label0 = new Label();
mv.visitLabel(label0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, classType.getInternalName(), "instance", targetType.getDescriptor());
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, eventType.getInternalName());
mv.visitMethodInsn(INVOKEVIRTUAL, targetType.getInternalName(), targetMethodName, Type.getMethodDescriptor(Type.VOID_TYPE, eventType), false);

Label label1 = new Label();
mv.visitLabel(label1);
mv.visitInsn(RETURN);

Label label2 = new Label();
mv.visitLabel(label2);
mv.visitLocalVariable("this", classType.getDescriptor(), null, label0, label2, 0);
mv.visitLocalVariable("event", Type.getType(Event.class).getDescriptor(), null, label0, label2, 1);
mv.visitMaxs(2, 2);

mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}
Loading

0 comments on commit 6eeb6a2

Please sign in to comment.