/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Mathew A. Nelson
* - Initial API and implementation
* Matthew Reeder
* - Fix for HyperThreading hang issue with the getTime() method that was
* synchronized before, which sometimes caused a deadlock to occur in the
* code processing the hitWall event.
* Flemming N. Larsen
* - Ported to Java 5.0
* - Bugfix: Added setting and getting the priority of BulletHitBulletEvent
* - Added missing getMessageEvents()
* - Code cleanup
* - Added features to support the new JuniorRobot class
* - Bugfix: Fixed ConcurrentModificationExceptions due to lack of
* synchronization with the event queue. Now all getXXXEvents() methods
* are synchronized against the event queue, and the list of customEvents
* is a CopyOnWriteArrayList which is fully thread-safe
* - Changed the priority of the DeathEvent from 100 to -1 in order to let
* robots process events before they die
* - Added handling of the new StatusEvent, which is used for calling the new
* Robot.onStatus(StatusEvent e) event handler, and added the
* getStatusEvents() method
* - Added PaintEvent with the onPaint() handler and also getPaintEvents()
* Robert D. Maupin
* - Replaced old collection types like Vector and Hashtable with
* synchronized List and HashMap
* Nathaniel Troutman
* - Added cleanup() method for cleaning up references to internal classes
* to prevent circular references causing memory leaks
* Pavel Savara
* - Re-work of robot interfaces
* - dispatch and priorities redesign
*******************************************************************************/
package net.sf.robocode.host.events;
import net.sf.robocode.host.proxies.BasicRobotProxy;
import net.sf.robocode.security.HiddenAccess;
import robocode.*;
import robocode.exception.EventInterruptedException;
import robocode.robotinterfaces.IBasicRobot;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Mathew A. Nelson (original)
* @author Flemming N. Larsen (contributor)
* @author Matthew Reeder (contributor)
* @author Robert D. Maupin (contributor)
* @author Nathaniel Troutman (contributor)
* @author Pavel Savara (contributor)
*/
public class EventManager implements IEventManager {
private BasicRobotProxy robotProxy;
private final static int MAX_PRIORITY = 100;
public final static int MAX_EVENT_STACK = 2;
private int currentTopEventPriority;
private Event currentTopEvent;
private final List<Condition> customEvents = new CopyOnWriteArrayList<Condition>();
private final EventQueue eventQueue;
private final boolean[] interruptible = new boolean[MAX_PRIORITY + 1];
private Dictionary<String, Event> namedEvents;
private ScannedRobotEvent dummyScannedRobotEvent;
public final static int MAX_QUEUE_SIZE = 256;
private IBasicRobot robot;
/**
* EventManager constructor comment.
*
* @param robotProxy robotProxy
*/
public EventManager(BasicRobotProxy robotProxy) {
super();
registerNamedEvents();
this.robotProxy = robotProxy;
eventQueue = new EventQueue();
reset();
}
public void add(Event e) {
if (!HiddenAccess.isCriticalEvent(e)) {
HiddenAccess.setEventPriority(e, getEventPriority(e.getClass().getName()));
}
HiddenAccess.setEventTime(e, getTime());
addImpl(e);
}
private void addImpl(Event e) {
if (eventQueue != null) {
if (eventQueue.size() > MAX_QUEUE_SIZE) {
robotProxy.println(
"Not adding to " + robotProxy.getStatics().getName() + "'s queue, exceeded " + MAX_QUEUE_SIZE
+ " events in queue.");
} else {
eventQueue.add(e);
}
}
}
public void addCustomEvent(Condition condition) {
customEvents.add(condition);
}
public void clearAllEvents(boolean includingSystemEvents) {
eventQueue.clear(includingSystemEvents);
// customEvents.clear(); // Custom event should not be cleared here
}
public void cleanup() {
// Remove all events
reset();
// Remove all references to robots
robot = null;
robotProxy = null;
}
/**
* Returns a list containing all events currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (Event e : getAllEvents()) {
* if (e instanceof HitByRobotEvent)
* <i> (do something with e) </i>
* else if (e instanceof HitByBulletEvent)
* <i> (so something else with e) </i>
* }
* </pre>
*
* @see BulletHitEvent
* @see BulletMissedEvent
* @see HitByBulletEvent
* @see HitRobotEvent
* @see HitWallEvent
* @see SkippedTurnEvent
* @see Event
* @see List
*/
public List<Event> getAllEvents() {
List<Event> events = Collections.synchronizedList(new ArrayList<Event>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
events.add(e);
}
}
return events;
}
/**
* Returns a list containing all BulletHitBulletEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (BulletHitBulletEvent e : getBulletHitBulletEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see BulletHitBulletEvent
* @see List
*/
public List<BulletHitBulletEvent> getBulletHitBulletEvents() {
List<BulletHitBulletEvent> events = Collections.synchronizedList(new ArrayList<BulletHitBulletEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof BulletHitBulletEvent) {
events.add((BulletHitBulletEvent) e);
}
}
}
return events;
}
/**
* Returns a list containing all BulletHitEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (BulletHitEvent e : getBulletHitEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see BulletHitEvent
* @see List
*/
public List<BulletHitEvent> getBulletHitEvents() {
List<BulletHitEvent> events = Collections.synchronizedList(new ArrayList<BulletHitEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof BulletHitEvent) {
events.add((BulletHitEvent) e);
}
}
}
return events;
}
/**
* Returns a list containing all BulletMissedEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (BulletMissedEvent e : getBulletMissedEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see BulletMissedEvent
* @see List
*/
public List<BulletMissedEvent> getBulletMissedEvents() {
List<BulletMissedEvent> events = Collections.synchronizedList(new ArrayList<BulletMissedEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof BulletMissedEvent) {
events.add((BulletMissedEvent) e);
}
}
}
return events;
}
public int getCurrentTopEventPriority() {
return currentTopEventPriority;
}
public Event getCurrentTopEvent() {
return currentTopEvent;
}
/**
* Returns a list containing all HitByBulletEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (HitByBulletEvent e : getHitByBulletEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see HitByBulletEvent
* @see List
*/
public List<HitByBulletEvent> getHitByBulletEvents() {
List<HitByBulletEvent> events = Collections.synchronizedList(new ArrayList<HitByBulletEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof HitByBulletEvent) {
events.add((HitByBulletEvent) e);
}
}
}
return events;
}
/**
* Returns a list containing all HitRobotEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (HitRobotEvent e : getHitRobotEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see HitRobotEvent
* @see List
*/
public List<HitRobotEvent> getHitRobotEvents() {
List<HitRobotEvent> events = Collections.synchronizedList(new ArrayList<HitRobotEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof HitRobotEvent) {
events.add((HitRobotEvent) e);
}
}
}
return events;
}
/**
* Returns a list containing all HitWallEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (HitWallEvent e : getHitWallEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see HitWallEvent
* @see List
*/
public List<HitWallEvent> getHitWallEvents() {
List<HitWallEvent> events = Collections.synchronizedList(new ArrayList<HitWallEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof HitWallEvent) {
events.add((HitWallEvent) e);
}
}
}
return events;
}
public boolean getInterruptible(int priority) {
return this.interruptible[priority];
}
private IBasicRobot getRobot() {
return robot;
}
public void setRobot(IBasicRobot r) {
this.robot = r;
}
/**
* Returns a list containing all RobotDeathEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (RobotDeathEvent e : getRobotDeathEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see RobotDeathEvent
* @see List
*/
public List<RobotDeathEvent> getRobotDeathEvents() {
List<RobotDeathEvent> events = Collections.synchronizedList(new ArrayList<RobotDeathEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof RobotDeathEvent) {
events.add((RobotDeathEvent) e);
}
}
}
return events;
}
public int getScannedRobotEventPriority() {
return dummyScannedRobotEvent.getPriority();
}
/**
* Returns a list containing all ScannedRobotEvents currently in the robot's queue.
* You might, for example, call this while processing another event.
* <p/>
* <P>Example:
* <pre>
* for (ScannedRobotEvent e : getScannedRobotEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @see ScannedRobotEvent
* @see List
*/
public List<ScannedRobotEvent> getScannedRobotEvents() {
List<ScannedRobotEvent> events = Collections.synchronizedList(new ArrayList<ScannedRobotEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof ScannedRobotEvent) {
events.add((ScannedRobotEvent) e);
}
}
}
return events;
}
public long getTime() {
return robotProxy.getTimeImpl();
}
public void processEvents() {
// remove too old stuff
eventQueue.clear(getTime() - MAX_EVENT_STACK);
// Process custom events
for (Condition customEvent : customEvents) {
boolean conditionSatisfied = false;
robotProxy.setTestingCondition(true);
try {
conditionSatisfied = customEvent.test();
} finally {
robotProxy.setTestingCondition(false);
}
if (conditionSatisfied) {
CustomEvent event = new CustomEvent(customEvent);
HiddenAccess.setEventTime(event, getTime());
addImpl(event);
}
}
// Process event queue here
eventQueue.sort();
Event currentEvent = null;
if (eventQueue.size() > 0) {
currentEvent = eventQueue.get(0);
}
while (currentEvent != null && currentEvent.getPriority() >= currentTopEventPriority) {
if (currentEvent.getPriority() == currentTopEventPriority) {
if (currentTopEventPriority > Integer.MIN_VALUE && getInterruptible(currentTopEventPriority)) {
setInterruptible(currentTopEventPriority, false); // we're going to restart it, so reset.
// We are already in an event handler, took action, and a new event was generated.
// So we want to break out of the old handler to process it here.
throw new EventInterruptedException(currentEvent.getPriority());
}
break;
}
int oldTopEventPriority = currentTopEventPriority;
currentTopEventPriority = currentEvent.getPriority();
currentTopEvent = currentEvent;
eventQueue.remove(currentEvent);
try {
dispatch(currentEvent);
setInterruptible(currentTopEventPriority, false);
} catch (EventInterruptedException e) {
currentTopEvent = null;
} catch (RuntimeException e) {
currentTopEventPriority = oldTopEventPriority;
currentTopEvent = null;
throw e;
} catch (Error e) {
currentTopEventPriority = oldTopEventPriority;
currentTopEvent = null;
throw e;
}
currentTopEventPriority = oldTopEventPriority;
currentEvent = (eventQueue.size() > 0) ? eventQueue.get(0) : null;
}
}
private void dispatch(Event currentEvent) {
final IBasicRobot robot = getRobot();
if (robot != null && currentEvent != null) {
try {
// skip too old events
if ((currentEvent.getTime() > getTime() - MAX_EVENT_STACK) || HiddenAccess.isCriticalEvent(currentEvent)) {
HiddenAccess.dispatch(currentEvent, robot, robotProxy.getStatics(), robotProxy.getGraphicsImpl());
}
} catch (Exception ex) {
robotProxy.println("SYSTEM: Exception occurred on " + currentEvent.getClass().getName());
ex.printStackTrace(robotProxy.getOut());
}
}
}
public void removeCustomEvent(Condition condition) {
customEvents.remove(condition);
}
public void resetCustomEvents() {
customEvents.clear();
}
public synchronized void reset() {
currentTopEventPriority = Integer.MIN_VALUE;
clearAllEvents(true);
customEvents.clear();
}
public void setInterruptible(int priority, boolean interruptable) {
if (priority >= 0 && priority < MAX_PRIORITY) {
this.interruptible[priority] = interruptable;
}
}
/**
* Returns a vector containing all MessageEvents currently in the robot's
* queue. You might, for example, call this while processing another event.
* <p/>
* Example:
* <pre>
* for (MessageEvent e : getMessageEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @return a vector containing all MessageEvents currently in the robot's
* queue
* @see MessageEvent
* @since 1.2.6
*/
public List<MessageEvent> getMessageEvents() {
List<MessageEvent> events = Collections.synchronizedList(new ArrayList<MessageEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof MessageEvent) {
events.add((MessageEvent) e);
}
}
}
return events;
}
/**
* Returns a vector containing all StatusEvents currently in the robot's
* queue. You might, for example, call this while processing another event.
* <p/>
* Example:
* <pre>
* for (StatusEvent e : getStatusEvents()) {
* <i> (do something with e) </i>
* }
* </pre>
*
* @return a vector containing all StatusEvents currently in the robot's
* queue.
* @see StatusEvent
* @since 1.5
*/
public List<StatusEvent> getStatusEvents() {
List<StatusEvent> events = Collections.synchronizedList(new ArrayList<StatusEvent>());
synchronized (eventQueue) {
for (Event e : eventQueue) {
if (e instanceof StatusEvent) {
events.add((StatusEvent) e);
}
}
}
return events;
}
public int getEventPriority(String eventClass) {
if (eventClass == null) {
return -1;
}
final Event event = namedEvents.get(eventClass);
if (event == null) {
return -1;
}
return event.getPriority();
}
public void setEventPriority(String eventClass, int priority) {
if (eventClass == null) {
return;
}
final Event event = namedEvents.get(eventClass);
if (event == null) {
robotProxy.println("SYSTEM: Unknown event class: " + eventClass);
return;
}
if (HiddenAccess.isCriticalEvent(event)) {
robotProxy.println("SYSTEM: You may not change the priority of system event. setPriority ignored.");
}
HiddenAccess.setEventPriority(event, priority);
}
private void registerNamedEvents() {
namedEvents = new Hashtable<String, Event>();
dummyScannedRobotEvent = new ScannedRobotEvent(null, 0, 0, 0, 0, 0);
registerNamedEvent(new BattleEndedEvent(false, null));
registerNamedEvent(new BulletHitBulletEvent(null, null));
registerNamedEvent(new BulletHitEvent(null, 0, null));
registerNamedEvent(new BulletMissedEvent(null));
registerNamedEvent(new DeathEvent());
registerNamedEvent(new HitByBulletEvent(0, null));
registerNamedEvent(new HitRobotEvent(null, 0, 0, false));
registerNamedEvent(new HitWallEvent(0));
registerNamedEvent(new KeyPressedEvent(null));
registerNamedEvent(new KeyReleasedEvent(null));
registerNamedEvent(new KeyTypedEvent(null));
registerNamedEvent(new MessageEvent(null, null));
registerNamedEvent(new MouseClickedEvent(null));
registerNamedEvent(new MouseDraggedEvent(null));
registerNamedEvent(new MouseEnteredEvent(null));
registerNamedEvent(new MouseExitedEvent(null));
registerNamedEvent(new MouseMovedEvent(null));
registerNamedEvent(new MousePressedEvent(null));
registerNamedEvent(new MouseReleasedEvent(null));
registerNamedEvent(new MouseWheelMovedEvent(null));
registerNamedEvent(new PaintEvent());
registerNamedEvent(new RobotDeathEvent(null));
registerNamedEvent(new RoundEndedEvent(0, 0, 0));
registerNamedEvent(dummyScannedRobotEvent);
registerNamedEvent(new SkippedTurnEvent(0));
registerNamedEvent(new StatusEvent(null));
registerNamedEvent(new WinEvent());
// same as any line above but for custom event
final DummyCustomEvent custom = new DummyCustomEvent();
namedEvents.put("robocode.CustomEvent", custom);
namedEvents.put("CustomEvent", custom);
}
private void registerNamedEvent(Event e) {
final String name = e.getClass().getName();
if (!HiddenAccess.isCriticalEvent(e)) {
HiddenAccess.setDefaultPriority(e);
}
namedEvents.put(name, e);
namedEvents.put(name.substring(9), e);
}
private static final class DummyCustomEvent extends CustomEvent {
private static final long serialVersionUID = 1L;
public DummyCustomEvent() {
super(null);
}
}
}