Copy eventhandler from Forge into FML. It is going to replace a lot of the runtime event systems.
This commit is contained in:
parent
a0fcbdccb5
commit
26056619a9
9 changed files with 859 additions and 0 deletions
|
@ -0,0 +1,192 @@
|
||||||
|
package cpw.mods.fml.common.asm.transformers;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||||
|
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.ALOAD;
|
||||||
|
import static org.objectweb.asm.Opcodes.ARETURN;
|
||||||
|
import static org.objectweb.asm.Opcodes.ASM4;
|
||||||
|
import static org.objectweb.asm.Opcodes.DUP;
|
||||||
|
import static org.objectweb.asm.Opcodes.F_SAME;
|
||||||
|
import static org.objectweb.asm.Opcodes.GETSTATIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.IFNULL;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
||||||
|
import static org.objectweb.asm.Opcodes.NEW;
|
||||||
|
import static org.objectweb.asm.Opcodes.PUTSTATIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.RETURN;
|
||||||
|
import static org.objectweb.asm.Type.VOID_TYPE;
|
||||||
|
import static org.objectweb.asm.Type.getMethodDescriptor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.minecraft.launchwrapper.IClassTransformer;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldInsnNode;
|
||||||
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
import org.objectweb.asm.tree.FrameNode;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
|
import org.objectweb.asm.tree.JumpInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.TypeInsnNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
|
import cpw.mods.fml.common.eventhandler.Event;
|
||||||
|
|
||||||
|
public class EventSubscriptionTransformer implements IClassTransformer
|
||||||
|
{
|
||||||
|
public EventSubscriptionTransformer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] transform(String name, String transformedName, byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes == null || name.equals("cpw.mods.fml.common.eventhandler.Event") || name.startsWith("net.minecraft.") || name.indexOf('.') == -1)
|
||||||
|
{
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
ClassReader cr = new ClassReader(bytes);
|
||||||
|
ClassNode classNode = new ClassNode();
|
||||||
|
cr.accept(classNode, 0);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (buildEvents(classNode))
|
||||||
|
{
|
||||||
|
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||||
|
classNode.accept(cw);
|
||||||
|
return cw.toByteArray();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex)
|
||||||
|
{
|
||||||
|
// Discard silently- it's just noise
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean buildEvents(ClassNode classNode) throws Exception
|
||||||
|
{
|
||||||
|
Class<?> parent = this.getClass().getClassLoader().loadClass(classNode.superName.replace('/', '.'));
|
||||||
|
if (!Event.class.isAssignableFrom(parent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasSetup = false;
|
||||||
|
boolean hasGetListenerList = false;
|
||||||
|
boolean hasDefaultCtr = false;
|
||||||
|
|
||||||
|
Class<?> listenerListClazz = Class.forName("cpw.mods.fml.common.eventhandler.ListenerList", false, getClass().getClassLoader());
|
||||||
|
Type tList = Type.getType(listenerListClazz);
|
||||||
|
|
||||||
|
for (MethodNode method : (List<MethodNode>)classNode.methods)
|
||||||
|
{
|
||||||
|
if (method.name.equals("setup") &&
|
||||||
|
method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)) &&
|
||||||
|
(method.access & ACC_PROTECTED) == ACC_PROTECTED)
|
||||||
|
{
|
||||||
|
hasSetup = true;
|
||||||
|
}
|
||||||
|
if (method.name.equals("getListenerList") &&
|
||||||
|
method.desc.equals(Type.getMethodDescriptor(tList)) &&
|
||||||
|
(method.access & ACC_PUBLIC) == ACC_PUBLIC)
|
||||||
|
{
|
||||||
|
hasGetListenerList = true;
|
||||||
|
}
|
||||||
|
if (method.name.equals("<init>") &&
|
||||||
|
method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)))
|
||||||
|
{
|
||||||
|
hasDefaultCtr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSetup)
|
||||||
|
{
|
||||||
|
if (!hasGetListenerList)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Event class defines setup() but does not define getListenerList! " + classNode.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type tSuper = Type.getType(classNode.superName);
|
||||||
|
|
||||||
|
//Add private static ListenerList LISTENER_LIST
|
||||||
|
classNode.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, "LISTENER_LIST", tList.getDescriptor(), null, null));
|
||||||
|
|
||||||
|
/*Add:
|
||||||
|
* public <init>()
|
||||||
|
* {
|
||||||
|
* super();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
MethodNode method = new MethodNode(ASM4, ACC_PUBLIC, "<init>", getMethodDescriptor(VOID_TYPE), null, null);
|
||||||
|
method.instructions.add(new VarInsnNode(ALOAD, 0));
|
||||||
|
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE)));
|
||||||
|
method.instructions.add(new InsnNode(RETURN));
|
||||||
|
if (!hasDefaultCtr)
|
||||||
|
{
|
||||||
|
classNode.methods.add(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Add:
|
||||||
|
* protected void setup()
|
||||||
|
* {
|
||||||
|
* super.setup();
|
||||||
|
* if (LISTENER_LIST != NULL)
|
||||||
|
* {
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
* LISTENER_LIST = new ListenerList(super.getListenerList());
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
method = new MethodNode(ASM4, ACC_PROTECTED, "setup", getMethodDescriptor(VOID_TYPE), null, null);
|
||||||
|
method.instructions.add(new VarInsnNode(ALOAD, 0));
|
||||||
|
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "setup", getMethodDescriptor(VOID_TYPE)));
|
||||||
|
method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
|
||||||
|
LabelNode initLisitener = new LabelNode();
|
||||||
|
method.instructions.add(new JumpInsnNode(IFNULL, initLisitener));
|
||||||
|
method.instructions.add(new InsnNode(RETURN));
|
||||||
|
method.instructions.add(initLisitener);
|
||||||
|
method.instructions.add(new FrameNode(F_SAME, 0, null, 0, null));
|
||||||
|
method.instructions.add(new TypeInsnNode(NEW, tList.getInternalName()));
|
||||||
|
method.instructions.add(new InsnNode(DUP));
|
||||||
|
method.instructions.add(new VarInsnNode(ALOAD, 0));
|
||||||
|
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "getListenerList", getMethodDescriptor(tList)));
|
||||||
|
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tList.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE, tList)));
|
||||||
|
method.instructions.add(new FieldInsnNode(PUTSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
|
||||||
|
method.instructions.add(new InsnNode(RETURN));
|
||||||
|
classNode.methods.add(method);
|
||||||
|
|
||||||
|
/*Add:
|
||||||
|
* public ListenerList getListenerList()
|
||||||
|
* {
|
||||||
|
* return this.LISTENER_LIST;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
method = new MethodNode(ASM4, ACC_PUBLIC, "getListenerList", getMethodDescriptor(tList), null, null);
|
||||||
|
method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
|
||||||
|
method.instructions.add(new InsnNode(ARETURN));
|
||||||
|
classNode.methods.add(method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
|
||||||
|
public class ASMEventHandler implements IEventListener
|
||||||
|
{
|
||||||
|
private static int IDs = 0;
|
||||||
|
private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class);
|
||||||
|
private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(IEventListener.class.getDeclaredMethods()[0]);
|
||||||
|
private static final ASMClassLoader LOADER = new ASMClassLoader();
|
||||||
|
private static final HashMap<Method, Class<?>> cache = Maps.newHashMap();
|
||||||
|
|
||||||
|
private final IEventListener handler;
|
||||||
|
private final SubscribeEvent subInfo;
|
||||||
|
public ASMEventHandler(Object target, Method method) throws Exception
|
||||||
|
{
|
||||||
|
handler = (IEventListener)createWrapper(method).getConstructor(Object.class).newInstance(target);
|
||||||
|
subInfo = method.getAnnotation(SubscribeEvent.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Event event)
|
||||||
|
{
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
if (!event.isCancelable() || !event.isCanceled() || subInfo.receiveCanceled())
|
||||||
|
{
|
||||||
|
handler.invoke(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventPriority getPriority()
|
||||||
|
{
|
||||||
|
return subInfo.priority();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> createWrapper(Method callback)
|
||||||
|
{
|
||||||
|
if (cache.containsKey(callback))
|
||||||
|
{
|
||||||
|
return cache.get(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassWriter cw = new ClassWriter(0);
|
||||||
|
MethodVisitor mv;
|
||||||
|
|
||||||
|
String name = getUniqueName(callback);
|
||||||
|
String desc = name.replace('.', '/');
|
||||||
|
String instType = Type.getInternalName(callback.getDeclaringClass());
|
||||||
|
String eventType = Type.getInternalName(callback.getParameterTypes()[0]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
System.out.println("Name: " + name);
|
||||||
|
System.out.println("Desc: " + desc);
|
||||||
|
System.out.println("InstType: " + instType);
|
||||||
|
System.out.println("Callback: " + callback.getName() + Type.getMethodDescriptor(callback));
|
||||||
|
System.out.println("Event: " + eventType);
|
||||||
|
*/
|
||||||
|
|
||||||
|
cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[]{ HANDLER_DESC });
|
||||||
|
|
||||||
|
cw.visitSource(".dynamic", null);
|
||||||
|
{
|
||||||
|
cw.visitField(ACC_PUBLIC, "instance", "Ljava/lang/Object;", null, null).visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Object;)V", null, null);
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitVarInsn(ALOAD, 1);
|
||||||
|
mv.visitFieldInsn(PUTFIELD, desc, "instance", "Ljava/lang/Object;");
|
||||||
|
mv.visitInsn(RETURN);
|
||||||
|
mv.visitMaxs(2, 2);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null);
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(GETFIELD, desc, "instance", "Ljava/lang/Object;");
|
||||||
|
mv.visitTypeInsn(CHECKCAST, instType);
|
||||||
|
mv.visitVarInsn(ALOAD, 1);
|
||||||
|
mv.visitTypeInsn(CHECKCAST, eventType);
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, instType, callback.getName(), Type.getMethodDescriptor(callback));
|
||||||
|
mv.visitInsn(RETURN);
|
||||||
|
mv.visitMaxs(2, 2);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
cw.visitEnd();
|
||||||
|
Class<?> ret = LOADER.define(name, cw.toByteArray());
|
||||||
|
cache.put(callback, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUniqueName(Method callback)
|
||||||
|
{
|
||||||
|
return String.format("%s_%d_%s_%s_%s", getClass().getName(), IDs++,
|
||||||
|
callback.getDeclaringClass().getSimpleName(),
|
||||||
|
callback.getName(),
|
||||||
|
callback.getParameterTypes()[0].getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ASMClassLoader extends ClassLoader
|
||||||
|
{
|
||||||
|
private ASMClassLoader()
|
||||||
|
{
|
||||||
|
super(ASMClassLoader.class.getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> define(String name, byte[] data)
|
||||||
|
{
|
||||||
|
return defineClass(name, data, 0, data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(value = RUNTIME)
|
||||||
|
@Target(value = TYPE)
|
||||||
|
public @interface Cancelable{}
|
156
fml/src/main/java/cpw/mods/fml/common/eventhandler/Event.java
Normal file
156
fml/src/main/java/cpw/mods/fml/common/eventhandler/Event.java
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Event class that all other events are derived from
|
||||||
|
*/
|
||||||
|
public class Event
|
||||||
|
{
|
||||||
|
@Retention(value = RUNTIME)
|
||||||
|
@Target(value = TYPE)
|
||||||
|
public @interface HasResult{}
|
||||||
|
|
||||||
|
public enum Result
|
||||||
|
{
|
||||||
|
DENY,
|
||||||
|
DEFAULT,
|
||||||
|
ALLOW
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCanceled = false;
|
||||||
|
private final boolean isCancelable;
|
||||||
|
private Result result = Result.DEFAULT;
|
||||||
|
private final boolean hasResult;
|
||||||
|
private static ListenerList listeners = new ListenerList();
|
||||||
|
|
||||||
|
private static final Map<Class<?>, Map<Class<?>, Boolean>> annotationMap = new ConcurrentHashMap<Class<?>, Map<Class<?>, Boolean>>();
|
||||||
|
|
||||||
|
public Event()
|
||||||
|
{
|
||||||
|
setup();
|
||||||
|
isCancelable = hasAnnotation(Cancelable.class);
|
||||||
|
hasResult = hasAnnotation(HasResult.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAnnotation(Class<? extends Annotation> annotation)
|
||||||
|
{
|
||||||
|
Class<?> me = this.getClass();
|
||||||
|
Map<Class<?>, Boolean> list = annotationMap.get(me);
|
||||||
|
if (list == null)
|
||||||
|
{
|
||||||
|
list = new ConcurrentHashMap<Class<?>, Boolean>();
|
||||||
|
annotationMap.put(me, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean cached = list.get(annotation);
|
||||||
|
if (cached != null)
|
||||||
|
{
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> cls = me;
|
||||||
|
while (cls != Event.class)
|
||||||
|
{
|
||||||
|
if (cls.isAnnotationPresent(annotation))
|
||||||
|
{
|
||||||
|
list.put(annotation, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cls = cls.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.put(annotation, false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this function is cancelable at all.
|
||||||
|
* @return If access to setCanceled should be allowed
|
||||||
|
*/
|
||||||
|
public boolean isCancelable()
|
||||||
|
{
|
||||||
|
return isCancelable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this event is canceled and should stop executing.
|
||||||
|
* @return The current canceled state
|
||||||
|
*/
|
||||||
|
public boolean isCanceled()
|
||||||
|
{
|
||||||
|
return isCanceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of this event, not all events are cancelable, and any attempt to
|
||||||
|
* cancel a event that can't be will result in a IllegalArgumentException.
|
||||||
|
*
|
||||||
|
* The functionality of setting the canceled state is defined on a per-event bases.
|
||||||
|
*
|
||||||
|
* @param cancel The new canceled value
|
||||||
|
*/
|
||||||
|
public void setCanceled(boolean cancel)
|
||||||
|
{
|
||||||
|
if (!isCancelable())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Attempted to cancel a uncancelable event");
|
||||||
|
}
|
||||||
|
isCanceled = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this event expects a significant result value.
|
||||||
|
*/
|
||||||
|
public boolean hasResult()
|
||||||
|
{
|
||||||
|
return hasResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value set as the result of this event
|
||||||
|
*/
|
||||||
|
public Result getResult()
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the result value for this event, not all events can have a result set, and any attempt to
|
||||||
|
* set a result for a event that isn't expecting it will result in a IllegalArgumentException.
|
||||||
|
*
|
||||||
|
* The functionality of setting the result is defined on a per-event bases.
|
||||||
|
*
|
||||||
|
* @param value The new result
|
||||||
|
*/
|
||||||
|
public void setResult(Result value)
|
||||||
|
{
|
||||||
|
result = value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called by the base constructor, this is used by ASM generated
|
||||||
|
* event classes to setup various functionality such as the listenerlist.
|
||||||
|
*/
|
||||||
|
protected void setup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ListenerList object that contains all listeners
|
||||||
|
* that are registered to this event.
|
||||||
|
*
|
||||||
|
* @return Listener List
|
||||||
|
*/
|
||||||
|
public ListenerList getListenerList()
|
||||||
|
{
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
}
|
112
fml/src/main/java/cpw/mods/fml/common/eventhandler/EventBus.java
Normal file
112
fml/src/main/java/cpw/mods/fml/common/eventhandler/EventBus.java
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
|
||||||
|
public class EventBus
|
||||||
|
{
|
||||||
|
private static int maxID = 0;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<Object, ArrayList<IEventListener>> listeners = new ConcurrentHashMap<Object, ArrayList<IEventListener>>();
|
||||||
|
private final int busID = maxID++;
|
||||||
|
|
||||||
|
public EventBus()
|
||||||
|
{
|
||||||
|
ListenerList.resize(busID + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(Object target)
|
||||||
|
{
|
||||||
|
if (listeners.containsKey(target))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<? extends Class<?>> supers = TypeToken.of(target.getClass()).getTypes().rawTypes();
|
||||||
|
for (Method method : target.getClass().getMethods())
|
||||||
|
{
|
||||||
|
for (Class<?> cls : supers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Method real = cls.getDeclaredMethod(method.getName(), method.getParameterTypes());
|
||||||
|
if (real.isAnnotationPresent(SubscribeEvent.class))
|
||||||
|
{
|
||||||
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
if (parameterTypes.length != 1)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Method " + method + " has @SubscribeEvent annotation, but requires " + parameterTypes.length +
|
||||||
|
" arguments. Event handler methods must require a single argument."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> eventType = parameterTypes[0];
|
||||||
|
|
||||||
|
if (!Event.class.isAssignableFrom(eventType))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Method " + method + " has @SubscribeEvent annotation, but takes a argument that is not a Event " + eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(eventType, target, method);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(Class<?> eventType, Object target, Method method)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Constructor<?> ctr = eventType.getConstructor();
|
||||||
|
ctr.setAccessible(true);
|
||||||
|
Event event = (Event)ctr.newInstance();
|
||||||
|
ASMEventHandler listener = new ASMEventHandler(target, method);
|
||||||
|
event.getListenerList().register(busID, listener.getPriority(), listener);
|
||||||
|
|
||||||
|
ArrayList<IEventListener> others = listeners.get(target);
|
||||||
|
if (others == null)
|
||||||
|
{
|
||||||
|
others = new ArrayList<IEventListener>();
|
||||||
|
listeners.put(target, others);
|
||||||
|
}
|
||||||
|
others.add(listener);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(Object object)
|
||||||
|
{
|
||||||
|
ArrayList<IEventListener> list = listeners.remove(object);
|
||||||
|
for (IEventListener listener : list)
|
||||||
|
{
|
||||||
|
ListenerList.unregisterAll(busID, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean post(Event event)
|
||||||
|
{
|
||||||
|
IEventListener[] listeners = event.getListenerList().getListeners(busID);
|
||||||
|
for (IEventListener listener : listeners)
|
||||||
|
{
|
||||||
|
listener.invoke(event);
|
||||||
|
}
|
||||||
|
return (event.isCancelable() ? event.isCanceled() : false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
public enum EventPriority
|
||||||
|
{
|
||||||
|
/*Priority of event listeners, listeners will be sorted with respect to this priority level.
|
||||||
|
*
|
||||||
|
* Note:
|
||||||
|
* Due to using a ArrayList in the ListenerList,
|
||||||
|
* these need to stay in a contiguous index starting at 0. {Default ordinal}
|
||||||
|
*/
|
||||||
|
HIGHEST, //First to execute
|
||||||
|
HIGH,
|
||||||
|
NORMAL,
|
||||||
|
LOW,
|
||||||
|
LOWEST //Last to execute
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public interface IEventListener
|
||||||
|
{
|
||||||
|
public void invoke(Event event);
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class ListenerList
|
||||||
|
{
|
||||||
|
private static ArrayList<ListenerList> allLists = new ArrayList<ListenerList>();
|
||||||
|
private static int maxSize = 0;
|
||||||
|
|
||||||
|
private ListenerList parent;
|
||||||
|
private ListenerListInst[] lists = new ListenerListInst[0];
|
||||||
|
|
||||||
|
public ListenerList()
|
||||||
|
{
|
||||||
|
allLists.add(this);
|
||||||
|
resizeLists(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenerList(ListenerList parent)
|
||||||
|
{
|
||||||
|
allLists.add(this);
|
||||||
|
this.parent = parent;
|
||||||
|
resizeLists(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resize(int max)
|
||||||
|
{
|
||||||
|
if (max <= maxSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ListenerList list : allLists)
|
||||||
|
{
|
||||||
|
list.resizeLists(max);
|
||||||
|
}
|
||||||
|
maxSize = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resizeLists(int max)
|
||||||
|
{
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
parent.resizeLists(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lists.length >= max)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenerListInst[] newList = new ListenerListInst[max];
|
||||||
|
int x = 0;
|
||||||
|
for (; x < lists.length; x++)
|
||||||
|
{
|
||||||
|
newList[x] = lists[x];
|
||||||
|
}
|
||||||
|
for(; x < max; x++)
|
||||||
|
{
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
newList[x] = new ListenerListInst(parent.getInstance(x));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newList[x] = new ListenerListInst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lists = newList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearBusID(int id)
|
||||||
|
{
|
||||||
|
for (ListenerList list : allLists)
|
||||||
|
{
|
||||||
|
list.lists[id].dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ListenerListInst getInstance(int id)
|
||||||
|
{
|
||||||
|
return lists[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEventListener[] getListeners(int id)
|
||||||
|
{
|
||||||
|
return lists[id].getListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(int id, EventPriority priority, IEventListener listener)
|
||||||
|
{
|
||||||
|
lists[id].register(priority, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(int id, IEventListener listener)
|
||||||
|
{
|
||||||
|
lists[id].unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unregisterAll(int id, IEventListener listener)
|
||||||
|
{
|
||||||
|
for (ListenerList list : allLists)
|
||||||
|
{
|
||||||
|
list.unregister(id, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListenerListInst
|
||||||
|
{
|
||||||
|
private boolean rebuild = true;
|
||||||
|
private IEventListener[] listeners;
|
||||||
|
private ArrayList<ArrayList<IEventListener>> priorities;
|
||||||
|
private ListenerListInst parent;
|
||||||
|
|
||||||
|
private ListenerListInst()
|
||||||
|
{
|
||||||
|
int count = EventPriority.values().length;
|
||||||
|
priorities = new ArrayList<ArrayList<IEventListener>>(count);
|
||||||
|
|
||||||
|
for (int x = 0; x < count; x++)
|
||||||
|
{
|
||||||
|
priorities.add(new ArrayList<IEventListener>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
for (ArrayList<IEventListener> listeners : priorities)
|
||||||
|
{
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
priorities.clear();
|
||||||
|
parent = null;
|
||||||
|
listeners = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenerListInst(ListenerListInst parent)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ArrayList containing all listeners for this event,
|
||||||
|
* and all parent events for the specified priority.
|
||||||
|
*
|
||||||
|
* The list is returned with the listeners for the children events first.
|
||||||
|
*
|
||||||
|
* @param priority The Priority to get
|
||||||
|
* @return ArrayList containing listeners
|
||||||
|
*/
|
||||||
|
public ArrayList<IEventListener> getListeners(EventPriority priority)
|
||||||
|
{
|
||||||
|
ArrayList<IEventListener> ret = new ArrayList<IEventListener>(priorities.get(priority.ordinal()));
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
ret.addAll(parent.getListeners(priority));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a full list of all listeners for all priority levels.
|
||||||
|
* Including all parent listeners.
|
||||||
|
*
|
||||||
|
* List is returned in proper priority order.
|
||||||
|
*
|
||||||
|
* Automatically rebuilds the internal Array cache if its information is out of date.
|
||||||
|
*
|
||||||
|
* @return Array containing listeners
|
||||||
|
*/
|
||||||
|
public IEventListener[] getListeners()
|
||||||
|
{
|
||||||
|
if (shouldRebuild()) buildCache();
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldRebuild()
|
||||||
|
{
|
||||||
|
return rebuild || (parent != null && parent.shouldRebuild());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild the local Array of listeners, returns early if there is no work to do.
|
||||||
|
*/
|
||||||
|
private void buildCache()
|
||||||
|
{
|
||||||
|
if(parent != null && parent.shouldRebuild())
|
||||||
|
{
|
||||||
|
parent.buildCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<IEventListener> ret = new ArrayList<IEventListener>();
|
||||||
|
for (EventPriority value : EventPriority.values())
|
||||||
|
{
|
||||||
|
ret.addAll(getListeners(value));
|
||||||
|
}
|
||||||
|
listeners = ret.toArray(new IEventListener[ret.size()]);
|
||||||
|
rebuild = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(EventPriority priority, IEventListener listener)
|
||||||
|
{
|
||||||
|
priorities.get(priority.ordinal()).add(listener);
|
||||||
|
rebuild = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(IEventListener listener)
|
||||||
|
{
|
||||||
|
for(ArrayList<IEventListener> list : priorities)
|
||||||
|
{
|
||||||
|
if (list.remove(listener))
|
||||||
|
{
|
||||||
|
rebuild = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package cpw.mods.fml.common.eventhandler;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.*;
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Retention(value = RUNTIME)
|
||||||
|
@Target(value = METHOD)
|
||||||
|
public @interface SubscribeEvent
|
||||||
|
{
|
||||||
|
public EventPriority priority() default EventPriority.NORMAL;
|
||||||
|
public boolean receiveCanceled() default false;
|
||||||
|
}
|
Loading…
Reference in a new issue