/** * Copyright (C) 2008 Hal Hildebrand. All rights reserved. * * This file is part of the Prime Mover Event Driven Simulation Framework. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.hellblazer.primeMover.runtime; import static com.hellblazer.primeMover.runtime.ContinuationFrame.BASE; import java.util.logging.Logger; import com.hellblazer.primeMover.Controller; import com.hellblazer.primeMover.Event; import com.hellblazer.primeMover.SimulationException; /** * The processor of events, the continuation of time. This is the central * control interface of PrimeMover. * * @author <a href="mailto:hal.hildebrand@gmail.com">Hal Hildebrand</a> */ abstract public class Devi implements Controller { private static final String $ENTITY$GEN = "$entity$gen"; private EventImpl blockingEvent; private EventImpl continuingEvent; private ContinuationFrame returnFrame; private EventImpl caller; private ContinuationFrame currentFrame; private EventImpl currentEvent; private long currentTime = 0; private boolean debugEvents = false; private Logger eventLog; private boolean trackEventSources = false; /** * Advance the current time of the controller * * @param duration */ @Override public void advance(long duration) { currentTime = currentTime + duration; } /** * Reinitialize the state of the controller */ @Override public void clear() { currentTime = 0; blockingEvent = null; continuingEvent = null; currentFrame = null; caller = null; currentEvent = null; } protected EventImpl createEvent(long time, EntityReference entity, int event, Object... arguments) { Event sourceEvent = trackEventSources ? currentEvent : null; if (debugEvents) { StackTraceElement[] stackTrace = new Throwable().getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { if (stackTrace[i].getClassName().endsWith($ENTITY$GEN)) { return new EventImpl(stackTrace[i + 1].toString(), time, sourceEvent, entity, event, arguments); } } } return new EventImpl(time, sourceEvent, entity, event, arguments); } /** * The heart of the event processing loop. This is where the events are * actually evaluated. * * @param next * - the event to evaluate. * @throws SimulationException * - if an exception occurs during the evaluation of the event. */ protected void evaluate(EventImpl next) throws SimulationException { currentEvent = next; currentTime = currentEvent.getTime(); Continuation continuation = currentEvent.getContinuation(); if (continuation != null) { returnFrame = continuation.getFrame(); caller = continuation.getCaller(); } Throwable exception = null; Object result = null; try { if (eventLog != null) { eventLog.info(currentEvent.toString()); } result = currentEvent.invoke(); } catch (SimulationEnd e) { throw e; } catch (Throwable e) { if (caller == null || blockingEvent != null) { throw new SimulationException(e); } exception = e; } finally { currentEvent = null; } if (blockingEvent != null) { continuingEvent.setContinuation(new Continuation(caller, currentFrame)); blockingEvent.setContinuation(new Continuation(continuingEvent)); post(blockingEvent); blockingEvent = null; continuingEvent = null; currentFrame = null; } else if (caller != null) { post(caller.resume(currentTime, result, exception)); } caller = null; returnFrame = null; } /** * Answer the current event of the controller * * @return */ @Override public Event getCurrentEvent() { return currentEvent; } /** * Answer the current instant in time of the controller * * @return */ @Override public long getCurrentTime() { return currentTime; } /** * @return true if the controller is collecting debug information as to the * source of where the event was raised */ @Override public boolean isDebugEvents() { return debugEvents; } /** * @return true if the controller is tracking event sources */ @Override public boolean isTrackEventSources() { return trackEventSources; } /** * Pop the frame off the continuation stack * * @return */ protected ContinuationFrame popFrame() { ContinuationFrame frame = returnFrame; returnFrame = frame.next; return frame; } /** * Post the event to be evaluated * * @param event */ abstract protected void post(EventImpl event); /** * Post the event to be evaluated. The event is blocking, meaning that it * will cause the caller to continue execution until the event is processed. * * @param entity * - the target of the event * @param event * - the event event * @param arguments * - the arguments to the event * @return * @throws Throwable */ @Override public Object postContinuingEvent(EntityReference entity, int event, Object... arguments) throws Throwable { assert blockingEvent == null; assert currentEvent != null; if (restoreFrame()) { returnFrame = null; return currentEvent.getContinuation().returnFrom(); } blockingEvent = createEvent(currentTime, entity, event, arguments); continuingEvent = currentEvent.clone(currentTime); currentFrame = BASE; return null; } /** * Post the event to be evaluated * * @param entity * - the target of the event * @param event * - the event event * @param arguments * - the arguments to the event */ @Override public void postEvent(EntityReference entity, int event, Object... arguments) { post(createEvent(currentTime, entity, event, arguments)); } /** * Post the event to be evaluated at the specified instant in time * * @param time * - the instant in time the event is to be processed * @param entity * - the target of the event * @param event * - the event event * @param arguments * - the arguments to the event */ @Override public void postEvent(long time, EntityReference entity, int event, Object... arguments) { post(createEvent(time, entity, event, arguments)); } /** * Answer true if the caller is to restore the continuation frame * * @return */ protected void pushFrame(ContinuationFrame frame) { frame.next = currentFrame; currentFrame = frame; } /** * Answer true if the caller is to restore the continuation frame * * @return */ protected boolean restoreFrame() { return returnFrame != null; } /** * Answer true if the caller is to save the continuation frame * * @return */ protected boolean saveFrame() { return blockingEvent != null; } /** * Set the current time of the controller * * @param time */ @Override public void setCurrentTime(long time) { currentTime = time; } /** * Configure the collecting of debug information for raised events. When * debug is enabled, the controller will record the source location where an * event was raised. The collection of debug information for events is * expensive and significantly impacts the performance of the simulation * event processing. * * @param debug * - true to trigger the collecting of event debug information */ @Override public void setDebugEvents(boolean debug) { debugEvents = debug; } /** * Configure the logger for tracing all event processing * * @param eventLog */ @Override public void setEventLogger(Logger eventLog) { this.eventLog = eventLog; } /** * Configure whether the controller will track the source event of a raised * event. Tracking event sources has garbage collection implications, as * event chains prevent the elimantion of previous events which have already * been processed. * * @param track * - true to track event sources */ @Override public void setTrackEventSources(boolean track) { trackEventSources = track; } /** * Swap the calling event for the current caller * * @param caller * @return */ protected EventImpl swapCaller(EventImpl caller) { EventImpl tmp = this.caller; this.caller = caller; return tmp; } }