Add IEventExceptionHandler for EventBus to allow special handeling exceptions that are fired while running an event.

Events now track what 'phase' they are in during the execution process. Each EventPriority is a 'phase'.
An exception is thrown if the event attempts to set its phase to a previous one.
This commit is contained in:
Lex Manos 2014-09-08 17:54:41 -07:00
parent 6192119682
commit 5f65fb754e
6 changed files with 98 additions and 10 deletions

View File

@ -5,7 +5,6 @@ import static org.objectweb.asm.Opcodes.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import org.apache.logging.log4j.ThreadContext;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
@ -27,11 +26,14 @@ public class ASMEventHandler implements IEventListener
private final IEventListener handler;
private final SubscribeEvent subInfo;
private ModContainer owner;
private String readable;
public ASMEventHandler(Object target, Method method, ModContainer owner) throws Exception
{
this.owner = owner;
handler = (IEventListener)createWrapper(method).getConstructor(Object.class).newInstance(target);
subInfo = method.getAnnotation(SubscribeEvent.class);
readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method);
}
@Override
@ -142,4 +144,8 @@ public class ASMEventHandler implements IEventListener
}
}
public String toString()
{
return readable;
}
}

View File

@ -9,6 +9,11 @@ import java.lang.annotation.Target;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
* Base Event class that all other events are derived from
@ -31,6 +36,7 @@ public class Event
private Result result = Result.DEFAULT;
private final boolean hasResult;
private static ListenerList listeners = new ListenerList();
private EventPriority phase = null;
private static final Map<Class<?>, Map<Class<?>, Boolean>> annotationMap = new ConcurrentHashMap<Class<?>, Map<Class<?>, Boolean>>();
@ -153,4 +159,18 @@ public class Event
{
return listeners;
}
@Nullable
public EventPriority getPhase()
{
return this.phase;
}
public void setPhase(@Nonnull EventPriority value)
{
Preconditions.checkArgument(value != null, "setPhase argument must not be null");
int prev = phase == null ? -1 : phase.ordinal();
Preconditions.checkArgument(prev < value.ordinal(), "Attempted to set event phase to %s when already %s", value, phase);
phase = value;
}
}

View File

@ -6,27 +6,41 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import org.apache.logging.log4j.Level;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.MapMaker;
import com.google.common.reflect.TypeToken;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
public class EventBus
public class EventBus implements IEventExceptionHandler
{
private static int maxID = 0;
private ConcurrentHashMap<Object, ArrayList<IEventListener>> listeners = new ConcurrentHashMap<Object, ArrayList<IEventListener>>();
private Map<Object,ModContainer> listenerOwners = new MapMaker().weakKeys().weakValues().makeMap();
private final int busID = maxID++;
private IEventExceptionHandler exceptionHandler;
public EventBus()
{
ListenerList.resize(busID + 1);
exceptionHandler = this;
register(this);
}
public EventBus(@Nonnull IEventExceptionHandler handler)
{
this();
Preconditions.checkArgument(handler != null, "EventBus exception handler can not be null");
exceptionHandler = handler;
}
public void register(Object target)
@ -117,10 +131,30 @@ public class EventBus
public boolean post(Event event)
{
IEventListener[] listeners = event.getListenerList().getListeners(busID);
for (IEventListener listener : listeners)
int index = 0;
try
{
listener.invoke(event);
for (; index < listeners.length; index++)
{
listeners[index].invoke(event);
}
}
catch (Throwable throwable)
{
exceptionHandler.handleException(this, event, listeners, index, throwable);
Throwables.propagate(throwable);
}
return (event.isCancelable() ? event.isCanceled() : false);
}
@Override
public void handleException(EventBus bus, Event event, IEventListener[] listeners, int index, Throwable throwable)
{
FMLLog.log(Level.ERROR, throwable, "Exception caught during firing event %s:", event);
FMLLog.log(Level.ERROR, "Index: %d Listeners:", index);
for (int x = 0; x < listeners.length; x++)
{
FMLLog.log(Level.ERROR, "%d: %s", x, listeners[x]);
}
}
}

View File

@ -1,16 +1,23 @@
package cpw.mods.fml.common.eventhandler;
public enum EventPriority
public enum EventPriority implements IEventListener
{
/*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}
* these need to stay in a contiguous index starting at 0. {Default ordinal}
*/
HIGHEST, //First to execute
HIGH,
NORMAL,
LOW,
LOWEST //Last to execute
;
@Override
public void invoke(Event event)
{
event.setPhase(this);
}
}

View File

@ -0,0 +1,16 @@
package cpw.mods.fml.common.eventhandler;
public interface IEventExceptionHandler
{
/**
* Fired when a EventListener throws an exception for the specified event on the event bus.
* After this function returns, the original Throwable will be propogated upwards.
*
* @param bus The bus the event is being fired on
* @param event The event that is being fired
* @param listeners All listeners that are listening for this event, in order
* @param index Index for the current listener being fired.
* @param throwable The throwable being thrown
*/
void handleException(EventBus bus, Event event, IEventListener[] listeners, int index, Throwable throwable);
}

View File

@ -202,7 +202,12 @@ public class ListenerList
ArrayList<IEventListener> ret = new ArrayList<IEventListener>();
for (EventPriority value : EventPriority.values())
{
ret.addAll(getListeners(value));
List<IEventListener> listeners = getListeners(value);
if (listeners.size() > 0)
{
ret.add(value); //Add the priority to notify the event of it's current phase.
ret.addAll(listeners);
}
}
listeners = ret.toArray(new IEventListener[ret.size()]);
rebuild = false;