diff --git a/src/main/java/cpw/mods/fml/common/asm/transformers/EventSubscriptionTransformer.java b/src/main/java/cpw/mods/fml/common/asm/transformers/EventSubscriptionTransformer.java new file mode 100644 index 0000000000..6e0fdd0a85 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/asm/transformers/EventSubscriptionTransformer.java @@ -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)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("") && + 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 () + * { + * super(); + * } + */ + MethodNode method = new MethodNode(ASM4, ACC_PUBLIC, "", getMethodDescriptor(VOID_TYPE), null, null); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "", 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(), "", 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; + } +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/ASMEventHandler.java b/src/main/java/cpw/mods/fml/common/eventhandler/ASMEventHandler.java new file mode 100644 index 0000000000..02bb037268 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/ASMEventHandler.java @@ -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> 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, "", "(Ljava/lang/Object;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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); + } + } + +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/Cancelable.java b/src/main/java/cpw/mods/fml/common/eventhandler/Cancelable.java new file mode 100644 index 0000000000..4b7389615e --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/Cancelable.java @@ -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{} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/Event.java b/src/main/java/cpw/mods/fml/common/eventhandler/Event.java new file mode 100644 index 0000000000..d9c31ee7a9 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/Event.java @@ -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, Map, Boolean>> annotationMap = new ConcurrentHashMap, Map, Boolean>>(); + + public Event() + { + setup(); + isCancelable = hasAnnotation(Cancelable.class); + hasResult = hasAnnotation(HasResult.class); + } + + private boolean hasAnnotation(Class annotation) + { + Class me = this.getClass(); + Map, Boolean> list = annotationMap.get(me); + if (list == null) + { + list = new ConcurrentHashMap, 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; + } +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/EventBus.java b/src/main/java/cpw/mods/fml/common/eventhandler/EventBus.java new file mode 100644 index 0000000000..aa6640d585 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/EventBus.java @@ -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> listeners = new ConcurrentHashMap>(); + private final int busID = maxID++; + + public EventBus() + { + ListenerList.resize(busID + 1); + } + + public void register(Object target) + { + if (listeners.containsKey(target)) + { + return; + } + + Set> 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 others = listeners.get(target); + if (others == null) + { + others = new ArrayList(); + listeners.put(target, others); + } + others.add(listener); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void unregister(Object object) + { + ArrayList 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); + } +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/EventPriority.java b/src/main/java/cpw/mods/fml/common/eventhandler/EventPriority.java new file mode 100644 index 0000000000..6594db8eab --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/EventPriority.java @@ -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 +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/IEventListener.java b/src/main/java/cpw/mods/fml/common/eventhandler/IEventListener.java new file mode 100644 index 0000000000..f5eefd41b0 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/IEventListener.java @@ -0,0 +1,8 @@ +package cpw.mods.fml.common.eventhandler; + + + +public interface IEventListener +{ + public void invoke(Event event); +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/ListenerList.java b/src/main/java/cpw/mods/fml/common/eventhandler/ListenerList.java new file mode 100644 index 0000000000..ca9f6665a9 --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/ListenerList.java @@ -0,0 +1,219 @@ +package cpw.mods.fml.common.eventhandler; + +import java.util.*; + + +public class ListenerList +{ + private static ArrayList allLists = new ArrayList(); + 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> priorities; + private ListenerListInst parent; + + private ListenerListInst() + { + int count = EventPriority.values().length; + priorities = new ArrayList>(count); + + for (int x = 0; x < count; x++) + { + priorities.add(new ArrayList()); + } + } + + public void dispose() + { + for (ArrayList 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 getListeners(EventPriority priority) + { + ArrayList ret = new ArrayList(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 ret = new ArrayList(); + 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 list : priorities) + { + if (list.remove(listener)) + { + rebuild = true; + } + } + } + } +} diff --git a/src/main/java/cpw/mods/fml/common/eventhandler/SubscribeEvent.java b/src/main/java/cpw/mods/fml/common/eventhandler/SubscribeEvent.java new file mode 100644 index 0000000000..6effc4240d --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/eventhandler/SubscribeEvent.java @@ -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; +}