forked from fr1kin/ForgeHax
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added custom EventBus (not implemented yet)
- Loading branch information
Showing
13 changed files
with
424 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} |
6 changes: 5 additions & 1 deletion
6
buildSrc/Annotations/src/main/java/dev/fiki/forgehax/api/event/EventListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
63 changes: 57 additions & 6 deletions
63
buildSrc/Annotations/src/main/java/dev/fiki/forgehax/api/event/ListenerList.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
142
src/main/java/dev/fiki/forgehax/api/event/EventListenerWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.