rinde.sim.scenario.ScenarioController Maven / Gradle / Ivy
package rinde.sim.scenario;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rinde.sim.core.Simulator;
import rinde.sim.core.Simulator.SimulatorEventType;
import rinde.sim.core.TickListener;
import rinde.sim.core.TimeLapse;
import rinde.sim.event.Event;
import rinde.sim.event.EventAPI;
import rinde.sim.event.EventDispatcher;
import rinde.sim.event.Listener;
/**
* A scenario controller represents a single simulation run using a
* {@link Scenario}. The scenario controller makes sure that all events in the
* scenario are dispatched at their respective time and it checks whether they
* are handled.
*
* @author Rinde van Lon
* @author Bartosz Michalik
* @since 2.0
*/
public class ScenarioController implements TickListener {
/**
* Logger for this class.
*/
protected static final Logger LOGGER = LoggerFactory
.getLogger(ScenarioController.class);
/**
* The {@link Event} types which can be dispatched by this class.
* @author Rinde van Lon
*/
public enum EventType {
/**
* Dispatched when the scenario starts playing.
*/
SCENARIO_STARTED,
/**
* Dispatched when the scenario has finished playing.
*/
SCENARIO_FINISHED;
}
/**
* The scenario that is played.
*/
protected final Scenario scenario;
/**
* The {@link Event} queue.
*/
protected final Queue scenarioQueue;
/**
* The {@link EventDispatcher} that is used for dispatching all events.
*/
protected final EventDispatcher disp;
/**
* A reference to the simulator.
*/
protected final Simulator simulator;
/**
* A reference to the {@link UICreator} that is responsible for creating the
* UI.
*/
@Nullable
protected UICreator uiCreator;
/**
* A handler for the TimedEvents.
*/
protected TimedEventHandler timedEventHandler;
private int ticks;
@Nullable
private EventType status;
private boolean uiMode;
// TODO ScenarioController should be added to Simulator not other way
// around.
/**
* Create an instance of ScenarioController with defined {@link Scenario} and
* number of ticks till end. If the number of ticks is negative the simulator
* will run until the {@link Simulator#stop()} method is called. TODO refine
* documentation
*
* @param scen Scenario which is controlled.
* @param sim Simulator which is controlled.
* @param eventHandler Is used to handle scenario events.
* @param numberOfTicks The number of ticks play, when negative the number of
* tick is infinite.
*/
public ScenarioController(final Scenario scen, Simulator sim,
TimedEventHandler eventHandler, int numberOfTicks) {
scenario = scen;
simulator = sim;
timedEventHandler = eventHandler;
ticks = numberOfTicks;
scenarioQueue = scenario.asQueue();
final Set> typeSet = newHashSet(scenario.getPossibleEventTypes());
typeSet.addAll(asList(EventType.values()));
disp = new EventDispatcher(typeSet);
disp.addListener(new InternalTimedEventHandler(),
scenario.getPossibleEventTypes());
simulator.getEventAPI().addListener(new Listener() {
@Override
public void handleEvent(Event e) {
if (simulator.getCurrentTime() == 0) {
dispatchSetupEvents();
}
}
}, SimulatorEventType.STARTED);
simulator.addTickListener(this);
simulator.configure();
}
// TODO add UICreator directly to Simulator?
/**
* Enables the UI for this scenario controller. This means that when
* {@link #start()} is called the UI is fired up. Using {@link UICreator} any
* kind of UI can be hooked to the simulation.
* @param creator The creator of the UI.
*/
public void enableUI(UICreator creator) {
uiMode = true;
uiCreator = creator;
}
/**
* Provides access to the {@link Event} API, allows adding and removing
* {@link Listener}s that are notified when {@link ScenarioController}
* dispatches {@link Event}s.
* @return The event API of the scenario controller.
*/
public EventAPI getEventAPI() {
return disp.getPublicEventAPI();
}
/**
* Stop the simulation.
*/
public void stop() {
if (!uiMode) {
simulator.stop();
}
}
/**
* Starts the simulation, if UI is enabled it will start the UI instead.
* @see #enableUI(UICreator)
* @see #stop()
*/
public void start() {
if (ticks != 0) {
if (!uiMode) {
simulator.start();
} else {
uiCreator.createUI(simulator);
}
}
}
/**
* Dispatch all setup events (the ones that define initial settings). For
* example, a vehicle that is added during setup (at time < 0) will receive
* its first tick at time 0. If the vehicle is added at the beginning of the
* simulation (time 0) the first tick it will receive will be the second
* (globally) tick.
*/
protected void dispatchSetupEvents() {
TimedEvent e = null;
while ((e = scenarioQueue.peek()) != null && e.time < 0) {
scenarioQueue.poll();
disp.dispatchEvent(e);
}
}
/**
* @return true
if all events of this scenario have been
* dispatched, false
otherwise.
*/
public boolean isScenarioFinished() {
return scenarioQueue.isEmpty();
}
@Override
public final void tick(TimeLapse timeLapse) {
if (!uiMode && ticks == 0) {
LOGGER.info("scenario finished at virtual time:" + timeLapse.getTime()
+ "[stopping simulation]");
simulator.stop();
}
if (LOGGER.isDebugEnabled() && ticks >= 0) {
LOGGER.debug("ticks to end: " + ticks);
}
if (ticks > 0) {
ticks--;
}
TimedEvent e = null;
while ((e = scenarioQueue.peek()) != null && e.time <= timeLapse.getTime()) {
scenarioQueue.poll();
if (status == null) {
LOGGER.info("scenario started at virtual time:" + timeLapse.getTime());
status = EventType.SCENARIO_STARTED;
disp.dispatchEvent(new Event(status, this));
}
disp.dispatchEvent(e);
}
if (e == null && status != EventType.SCENARIO_FINISHED) {
status = EventType.SCENARIO_FINISHED;
disp.dispatchEvent(new Event(status, this));
}
if (ticks == 0 && status == EventType.SCENARIO_FINISHED) {
LOGGER.info("scenario finished at virtual time:" + timeLapse.getTime()
+ "[stopping simulation]");
simulator.stop();
simulator.removeTickListener(this);
}
}
@Override
public void afterTick(TimeLapse timeLapse) {}
/**
* A UICreator can be used to dynamically create a UI for the simulation run.
* It can be used with any kind of GUI imaginable.
* @author Rinde van Lon
*/
public interface UICreator {
// TODO convert to use View.Builder ?
/**
* Should instantiate the UI.
* @param sim The {@link Simulator} instance for which the UI should be
* created.
*/
void createUI(Simulator sim);
}
class InternalTimedEventHandler implements Listener {
public InternalTimedEventHandler() {}
@Override
public final void handleEvent(Event e) {
checkState(timedEventHandler.handleTimedEvent((TimedEvent) e),
"The event %s is not handled.", e.getEventType());
}
}
}