Package rinde.sim.core

Source Code of rinde.sim.core.Simulator

/**
*
*/
package rinde.sim.core;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.measure.Measure;
import javax.measure.quantity.Duration;
import javax.measure.unit.Unit;

import org.apache.commons.math3.random.RandomGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rinde.sim.core.model.Model;
import rinde.sim.core.model.ModelManager;
import rinde.sim.core.model.ModelProvider;
import rinde.sim.event.Event;
import rinde.sim.event.EventAPI;
import rinde.sim.event.EventDispatcher;

/**
* Simulator is the core class of a simulation. It is responsible for managing
* time which it does by periodically providing {@link TimeLapse} instances to
* registered {@link TickListener}s. Further it provides methods to start and
* stop simulations. The simulator also acts as a facade through which
* {@link Model}s and objects can be added to the simulator, more info about
* models can be found in {@link ModelManager}.
*
* The configuration phase of the simulator looks as follows:
* <ol>
* <li>register models using {@link #register(Model)}</li>
* <li>call {@link #configure()}
* <li>register objects using {@link #register(Object)}</li>
* <li>start simulation by calling {@link #start()}</li>
* </ol>
* Note that objects can not be registered <b>before</b> calling
* {@link #configure()} and {@link Model}s can not be registered <b>after</b>
* configuring.
*
* @author Rinde van Lon (rinde.vanlon@cs.kuleuven.be)
* @author Bartosz Michalik <bartosz.michalik@cs.kuleuven.be> - simulator API
*         changes
*/
public class Simulator implements SimulatorAPI {

  /**
   * The logger of the simulator.
   */
  protected static final Logger LOGGER = LoggerFactory
      .getLogger(Simulator.class);

  /**
   * Enum that describes the possible types of events that the simulator can
   * dispatch.
   */
  public enum SimulatorEventType {
    /**
     * Indicates that the simulator has stopped.
     */
    STOPPED,

    /**
     * Indicates that the simulator has started.
     */
    STARTED,

    /**
     * Indicates that the simulator has been configured.
     */
    CONFIGURED
  }

  /**
   * Contains the set of registered {@link TickListener}s.
   */
  protected volatile Set<TickListener> tickListeners;

  /**
   * Reference to dispatcher of simulator events, can be used by subclasses to
   * issue additional events.
   */
  protected final EventDispatcher dispatcher;

  /**
   * @see #isPlaying
   */
  protected volatile boolean isPlaying;

  /**
   * @see #getCurrentTime()
   */
  protected long time;

  /**
   * Model manager instance.
   */
  protected final ModelManager modelManager;
  private boolean configured;

  private Set<Object> toUnregister;
  private final RandomGenerator rand;
  private final long timeStep;
  private final TimeLapse timeLapse;

  // TODO RandomGenerator should be moved into an own model. This way, objects
  // that need a reference to a random generator can get one by implementing
  // this model's interface. The model could have several policies for
  // distributing RNGs: ALL_SAME, CLASS_SAME, ALL_DIFFERENT. This would
  // indicate: every subscribing object uses same RNG, objects of the same
  // class share same RNG, all objects get a different RNG instance
  // respectively.

  // TODO investigate if a TimeModel should be created, this would move all
  // time/tick related stuff into its own class. Making it easier to extend
  // this part.
  /**
   * Create a new simulator instance.
   * @param r The random number generator that is used in this simulator.
   * @param step The time that passes each tick. This can be in any unit the
   *          programmer prefers.
   */
  public Simulator(RandomGenerator r, Measure<Long, Duration> step) {
    LOGGER.info("Simulator constructor, time step {}", step);
    checkArgument(step.getValue() > 0L, "Step must be a positive number.");
    timeStep = step.getValue();
    tickListeners = Collections
        .synchronizedSet(new LinkedHashSet<TickListener>());

    toUnregister = new LinkedHashSet<Object>();

    rand = r;
    time = 0L;
    // time lapse is reused in a Flyweight kind of style
    timeLapse = new TimeLapse(step.getUnit());

    modelManager = new ModelManager();

    dispatcher = new EventDispatcher(SimulatorEventType.values());
  }

  /**
   * This configures the {@link Model}s in the simulator. After calling this
   * method models can no longer be added, objects can only be registered after
   * this method is called.
   * @see ModelManager#configure()
   */
  public void configure() {
    for (final Model<?> m : modelManager.getModels()) {
      if (m instanceof TickListener) {
        LOGGER.info("adding {} as a tick listener", m.getClass().getName());
        addTickListener((TickListener) m);
      }
    }
    modelManager.configure();
    configured = true;
    dispatcher.dispatchEvent(new Event(SimulatorEventType.CONFIGURED, this));
  }

  // TODO create a SimulatorBuilder for configuration of Simulator?
  // TODO should fail on error instead of returning a boolean
  /**
   * Register a model to the simulator.
   * @param model The {@link Model} instance to register.
   * @return true if successful, false otherwise
   */
  public boolean register(Model<?> model) {
    if (configured) {
      throw new IllegalStateException(
          "cannot add model after calling configure()");
    }
    injectDependencies(model);
    final boolean result = modelManager.add(model);
    if (result) {
      LOGGER.info("registering model {} for type {}.", model.getClass()
          .getName(), model.getSupportedType().getName());
    }
    return result;
  }

  @Override
  public boolean register(Object obj) {
    if (obj instanceof Model<?>) {
      return register((Model<?>) obj);
    }
    if (!configured) {
      throw new IllegalStateException(
          "can not add object before calling configure()");
    }
    LOGGER.info("{} - register({})", time, obj);
    injectDependencies(obj);
    if (obj instanceof TickListener) {
      addTickListener((TickListener) obj);
    }
    return modelManager.register(obj);
  }

  /**
   * {@inheritDoc} Unregistration from the models is delayed until all ticks are
   * processed.
   */
  @Override
  public boolean unregister(Object o) {
    if (o instanceof Model<?>) {
      throw new IllegalArgumentException("can not unregister a model");
    }
    if (!configured) {
      throw new IllegalStateException(
          "can not unregister object before calling configure()");
    }
    if (o instanceof TickListener) {
      removeTickListener((TickListener) o);
    }
    toUnregister.add(o);
    return true;
  }

  /**
   * Inject all required dependecies basing on the declared types of the object.
   * @param o object that need to have dependecies injected
   */
  protected void injectDependencies(Object o) {
    if (o instanceof SimulatorUser) {
      ((SimulatorUser) o).setSimulator(this);
    }
  }

  /**
   * Returns a safe to modify list of all models registered in the simulator.
   * @return list of models
   */
  public List<Model<?>> getModels() {
    return modelManager.getModels();
  }

  /**
   * Returns the {@link ModelProvider} that has all registered models.
   * @return The model provider
   */
  public ModelProvider getModelProvider() {
    return modelManager;
  }

  @Override
  public long getCurrentTime() {
    return time;
  }

  @Override
  public long getTimeStep() {
    return timeStep;
  }

  /**
   * Adds a tick listener to the simulator.
   * @param listener The listener to add.
   */
  public void addTickListener(TickListener listener) {
    tickListeners.add(listener);
  }

  /**
   * Removes the listener specified. Implemented in O(1).
   * @param listener The listener to remove
   */
  public void removeTickListener(TickListener listener) {
    tickListeners.remove(listener);
  }

  /**
   * Start the simulation.
   */
  public void start() {
    if (!configured) {
      throw new IllegalStateException(
          "Simulator can not be started when it is not configured.");
    }
    if (!isPlaying) {
      dispatcher.dispatchEvent(new Event(SimulatorEventType.STARTED, this));
    }
    isPlaying = true;
    while (isPlaying) {
      tick();
    }
    dispatcher.dispatchEvent(new Event(SimulatorEventType.STOPPED, this));
  }

  /**
   * Advances the simulator with one step (the size is determined by the time
   * step).
   */
  public void tick() {
    // unregister all pending objects
    Set<Object> copy;
    copy = toUnregister;
    toUnregister = new LinkedHashSet<Object>();

    for (final Object c : copy) {
      modelManager.unregister(c);
    }

    // using a copy to avoid concurrent modifications of this set
    // this also means that adding or removing a TickListener is
    // effectively executed after a 'tick'

    final List<TickListener> localCopy = new ArrayList<TickListener>();
    localCopy.addAll(tickListeners);

    final long end = time + timeStep;
    LOGGER.trace("{} ->----> tick ->----> {}", time, end);
    for (final TickListener t : localCopy) {
      timeLapse.initialize(time, end);
      t.tick(timeLapse);
    }
    timeLapse.initialize(time, end);
    // in the after tick the TimeLapse can no longer be consumed
    timeLapse.consumeAll();
    for (final TickListener t : localCopy) {
      t.afterTick(timeLapse);
    }
    time += timeStep;

  }

  /**
   * Either starts or stops the simulation depending on the current state.
   */
  public void togglePlayPause() {
    if (!isPlaying) {
      start();
    } else {
      isPlaying = false;
    }
  }

  /**
   * Resets the time to 0.
   */
  public void resetTime() {
    time = 0L;
  }

  /**
   * Stops the simulation.
   */
  public void stop() {
    isPlaying = false;
  }

  /**
   * @return true if simulator is playing, false otherwise.
   */
  public boolean isPlaying() {
    return isPlaying;
  }

  /**
   * @return <code>true</code> if the simulator is configured, see
   *         {@link Simulator} for more information.
   */
  public boolean isConfigured() {
    return configured;
  }

  /**
   * @return An unmodifiable view on the set of tick listeners.
   */
  public Set<TickListener> getTickListeners() {
    return Collections.unmodifiableSet(tickListeners);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public RandomGenerator getRandomGenerator() {
    return rand;
  }

  @Override
  public Unit<Duration> getTimeUnit() {
    return timeLapse.getTimeUnit();
  }

  @Override
  public EventAPI getEventAPI() {
    return dispatcher.getPublicEventAPI();
  }
}
TOP

Related Classes of rinde.sim.core.Simulator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.