/*
* Copyright (c) 2005-2011 KOM - Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.topology.movement;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import de.tud.kom.p2psim.api.topology.TopologyComponent;
import de.tud.kom.p2psim.api.topology.movement.MovementModel;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.api.topology.movement.local.LocalMovementStrategy;
import de.tud.kom.p2psim.api.topology.placement.PlacementModel;
import de.tud.kom.p2psim.api.topology.waypoints.WaypointModel;
import de.tud.kom.p2psim.impl.scenario.simcfg2.annotations.After;
import de.tud.kom.p2psim.impl.scenario.simcfg2.annotations.Configure;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.TopologyFactory;
import de.tud.kom.p2psim.impl.util.Either;
import de.tud.kom.p2psim.impl.util.geo.maps.MapLoader;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.EventHandler;
import de.tudarmstadt.maki.simonstrator.api.Monitor;
import de.tudarmstadt.maki.simonstrator.api.Monitor.Level;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.Time;
/**
* The AbstractWaypointMovementModel can be used to implement movement models
* based on way point data. It uses an implementation of LocalMovementStrategy
* for the movement between selected way points.
*
* @author Fabio Zöllner
* @version 1.0, 27.03.2012
*/
public abstract class AbstractWaypointMovementModel implements MovementModel {
private Set components = new LinkedHashSet();
protected PositionVector worldDimensions;
protected WeakHashMap destinations;
protected WeakHashMap pauseTimes;
protected WeakHashMap pauseInProgressTimes;
protected WaypointModel waypointModel;
protected LocalMovementStrategy localMovementStrategy;
private int configurationCounter = 100;
private long timeBetweenMovement = 1 * Time.SECOND;
private double speedLimit = 1;
private double unscaledSpeedLimit = speedLimit;
private Random rnd = Randoms.getRandom(AbstractWaypointMovementModel.class);
public AbstractWaypointMovementModel(double worldX, double worldY) {
worldDimensions = new PositionVector(worldX, worldY);
destinations = new WeakHashMap();
pauseTimes = new WeakHashMap();
pauseInProgressTimes = new WeakHashMap();
// Simulator.registerAtEventBus(this);
}
@Configure()
@After(required = { WaypointModel.class, MapLoader.class })
public boolean configure() {
if (this.waypointModel == null && configurationCounter > 0) {
configurationCounter--;
return false;
}
if (this.waypointModel == null) {
Monitor.log(AbstractWaypointMovementModel.class, Level.INFO,
"No waypoint model has been configured. Thus the movement speed won't be adjusted for the scale of the waypoint model.");
}
//
// if (waypointModel.getScaleFactor() != 1.0) {
// Simulator.postEvent(new ScaleWorldEvent(waypointModel
// .getScaleFactor()));
// }
return true;
}
/**
* This default implementation relies on {@link PlacementModel}s to be
* configured in the {@link TopologyFactory}
*/
@Override
public void placeComponent(SimLocationActuator actuator) {
// not supported
}
/**
* Gets called periodically (after timeBetweenMoveOperations) or by an
* application and should be used to recalculate positions
*/
public void move() {
long nrOfSteps = timeBetweenMovement / Time.SECOND;
for (int i = 0; i < nrOfSteps; i++) {
step();
}
}
private void step() {
Set comps = getComponents();
for (SimLocationActuator mcomp : comps) {
Long currentPause = pauseInProgressTimes.get(mcomp);
if (currentPause != null) {
if (Time.getCurrentTime() >= currentPause) {
Monitor.log(AbstractWaypointMovementModel.class,
Level.DEBUG, "Pause time ended...");
pauseInProgressTimes.remove(mcomp);
} else
continue;
}
PositionVector dst = getDestination(mcomp);
// If the movement model gave null as a destination no move is
// executed
if (dst == null) {
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"No destination before reachedPosition check... continuing");
continue;
}
// If the position has been reached last round
// set pause active and get a new destination
if (reachedPosition(mcomp, dst)) {
pauseAndGetNextPosition(mcomp);
continue;
}
// Ask the local movement strategy for the next position.
// It may return the next position or a boolean with true to notify
// the
// movement model that it can't get any closer to the current way
// point.
Either either = localMovementStrategy
.nextPosition(mcomp, dst);
if (either.hasLeft()) {
updatePosition(mcomp, either.getLeft());
} else {
if (either.getRight().booleanValue()) {
pauseAndGetNextPosition(mcomp);
continue;
} else {
// Pause this round
continue;
}
}
}
}
/**
* Call this method to finally update the location of the given component.
*
* @param actuator
* @param newPosition
*/
protected void updatePosition(SimLocationActuator actuator,
PositionVector newPosition) {
actuator.updateCurrentLocation(newPosition);
}
/**
* Sets the pause time for the given component and calls nextPosition on it.
*
* @param comp
*/
private void pauseAndGetNextPosition(SimLocationActuator comp) {
Long pt = pauseTimes.get(comp) * Time.SECOND;
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Position reached... pause time is " + pt);
if (pt != null) {
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Simulator time: " + Time.getCurrentTime());
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Pause time: " + pt);
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Added up: " + (Time.getCurrentTime() + pt));
pauseInProgressTimes.put(comp, Time.getCurrentTime() + pt);
}
nextPosition(comp);
}
/**
* Checks if the given component has reached its destination
*
* @param comp
* @param dst
* @return Returns true if the destination was reached
*/
private boolean reachedPosition(SimLocationActuator comp,
PositionVector dst) {
PositionVector pos = comp.getRealPosition();
double distance = pos.distanceTo(dst);
// FIXME: Better detection?
return (distance < getSpeedLimit() * 2);
}
/**
* Returns the current destination of the given component and calls
* nextPosition if it hasn't been set yet.
*
* @param comp
* @return
*/
private PositionVector getDestination(SimLocationActuator comp) {
PositionVector dst = destinations.get(comp);
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Pos: " + comp.getRealPosition());
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"Dst: " + dst);
if (dst == null) {
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"No destination, calling nextPosition()");
nextPosition(comp);
dst = destinations.get(comp);
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"New destination is: " + dst);
}
return dst;
}
/**
* This method can be called by the concrete implementation to let the
* AbstractWaypointMovementModel know the next destination and pause time.
*
* @param comp
* @param destination
* @param pauseTime
*/
protected void nextDestination(SimLocationActuator comp,
PositionVector destination, long pauseTime) {
destinations.put(comp, destination);
pauseTimes.put(comp, pauseTime);
}
/**
* Is to be implemented by the concrete movement model and will be called by
* the AbstractWaypointMovementModel to ask the concrete implementation for
* a new destination.
*
* @param component
*/
public abstract void nextPosition(SimLocationActuator component);
/**
* Get all participating Components
*
* @return
*/
protected Set getComponents() {
return components;
}
/**
* Add a component to this movement-Model (used to keep global information
* about all participants in a movement model). Each Component acts as a
* callback upon movement of this component
*
* @param component
*/
@Override
public void addComponent(SimLocationActuator component) {
Monitor.log(AbstractWaypointMovementModel.class, Level.DEBUG,
"AbstractMovementModel: Adding component to the movement model");
components.add(component);
}
/**
* Get a valid delta-Vector (does not cross world-boundaries and does not
* exceed moveSpeedLimit)
*
* @param oldPosition
* @return
*/
protected PositionVector getRandomDelta(PositionVector oldPosition) {
return getRandomDelta(oldPosition, getSpeedLimit());
}
/**
* Get a valid delta-Vector, where each abs(entry) must not exceed maxLength
*
* @param oldPosition
* @param maxLength
* @return
*/
protected PositionVector getRandomDelta(PositionVector oldPosition,
double maxLength) {
double[] delta = new double[oldPosition.getDimensions()];
for (int i = 0; i < oldPosition.getDimensions(); i++) {
int tries = 0;
do {
delta[i] = getRandomDouble(-maxLength, maxLength);
if (++tries > 50) {
delta[i] = 0;
break;
}
// delta[i] = (r.nextInt(2 * getMoveSpeedLimit() + 1) -
// getMoveSpeedLimit());
} while (oldPosition.getEntry(i) + delta[i] > getWorldDimension(i)
|| oldPosition.getEntry(i) + delta[i] < 0);
}
PositionVector deltaVector = new PositionVector(delta);
return deltaVector;
}
/**
* Returns if this is a valid position within the boundaries
*
* @return
*/
protected boolean isValidPosition(PositionVector vector) {
for (int i = 0; i < vector.getDimensions(); i++) {
if (vector.getEntry(i) > getWorldDimension(i)
|| vector.getEntry(i) < 0) {
return false;
}
}
return true;
}
/**
* Returns a random int between from and to, including both interval ends!
*
* @param from
* @param to
* @return
*/
protected int getRandomInt(int from, int to) {
int intervalSize = Math.abs(to - from);
return (rnd.nextInt(intervalSize + 1) + from);
}
protected double getRandomDouble(double from, double to) {
double intervalSize = Math.abs(to - from);
return (rnd.nextDouble() * intervalSize + from);
}
/**
* Move models can periodically calculate new positions and notify
* listeners, if a position changed. Here you can specify the interval
* between these notifications/calculations. If this is set to zero, there
* is no periodical execution, which may be useful if you want to call
* move() from within an application.
*
* @param timeBetweenMoveOperations
*/
public void setTimeBetweenMoveOperations(long timeBetweenMoveOperations) {
this.timeBetweenMovement = timeBetweenMoveOperations;
if (timeBetweenMoveOperations > 0) {
reschedule();
}
}
protected void reschedule() {
Event.scheduleWithDelay(timeBetweenMovement, new PeriodicMove(), this,
0);
}
@Deprecated
public void setSpeedLimit(double speedLimit) {
this.speedLimit = speedLimit;
this.unscaledSpeedLimit = speedLimit;
}
@Deprecated
public double getSpeedLimit() {
return speedLimit;
}
public void setWorldX(double dimension) {
this.worldDimensions.setEntry(0, dimension);
}
public void setWorldY(double dimension) {
this.worldDimensions.setEntry(1, dimension);
}
public double getWorldDimension(int dim) {
return worldDimensions.getEntry(dim);
}
public void setWorldZ(double dimension) {
this.worldDimensions.setEntry(2, dimension);
}
public void setWaypointModel(WaypointModel model) {
this.waypointModel = model;
if (localMovementStrategy != null)
localMovementStrategy.setWaypointModel(getWaypointModel());
}
public WaypointModel getWaypointModel() {
return this.waypointModel;
}
public void setLocalMovementStrategy(LocalMovementStrategy strategy) {
strategy.setWaypointModel(getWaypointModel());
this.localMovementStrategy = strategy;
}
/**
* Triggers the periodic move
*
* @author Bjoern Richerzhagen
* @version 1.0, 20.03.2012
*/
protected class PeriodicMove implements EventHandler {
@Override
public void eventOccurred(Object content, int type) {
move();
reschedule();
}
}
}