package de.tud.kom.p2psim.impl.topology.movement.modularosm;
import de.tud.kom.p2psim.api.topology.Topology;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.movement.local.RouteImpl;
import de.tud.kom.p2psim.impl.topology.movement.local.RealWorldStreetsMovement;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.transition.ITransitionStrategy;
import de.tud.kom.p2psim.impl.util.Either;
import de.tudarmstadt.maki.simonstrator.api.Binder;
import de.tudarmstadt.maki.simonstrator.api.Monitor;
import de.tudarmstadt.maki.simonstrator.api.NodeDebugMonitor;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* This class is meant to be used with the RealWorldStreetsMovement
* and allows changes of the movement type and the used {@link ITransitionStrategy} mid-simulation
* on a per-host basis. It acts like the {@link ModularMovementModel}, but each of the {@link SimLocationActuator}s
* can have a different movement type and {@link ITransitionStrategy}. All routes and targets will be
* calculated accordingly.
*
* Originally the whole movement system within the simonstrator platform was not intended to be manipulable
* by an application since only overlays were implemented which in a real world would reside on handheld devices
* for example and should therefore not be able to manipulate the movement of the node/user.
* But demand changed and for some systems it is inevitable to be able to control/influence the movement. Therefore
* this class was created to fill the gap and provide access to the movement from outside the internal system.
*
* USAGE:
* So, since the movement of a person in real life could not be controlled by anyone but the person itself,
* the access to this class is only provided from the simrunner project since it is responsible for the simulation
* of the "real-life" parts of the simonstrator platform. From within this project you have access to the
* {@link de.tud.kom.p2psim.impl.topology.DefaultTopologyComponent} from where you can access this movement model.
* If you want to use different movement types, all you have to do is
* (besides selecting this model in your config) call the {@link #setMovementType(SimLocationActuator, String)}
* for each of your components.
*
* The used {@link ITransitionStrategy} can be changed on runtime, too. However, the first
* TransitionStrategy specified in the config will be used as default, and will be applied if there is
* no further strategy specified for a specific host. To use multiple strategies, add them to your
* config just as the first one. To set a specific strategy for a specific host, call the {@link #setTransitionForComponent(SimLocationActuator, Class)}
* with the second parameter being the class of the transition strategy you want to use.
*
* NOTE: All the movement types you are using need to be specified in you config for the
* {@link RealWorldStreetsMovement}. E.g if you are using 'car' and 'foot' movement types,
* in you config the MovementType for the {@link RealWorldStreetsMovement} should be specified as 'car,foot'.
* If not done properly there won't be an error, but the movement behaviour will be strange.
*
* @author Clemens Krug
*/
public class ModularMultiTypeMovementModel extends ModularMovementModel
{
private HashMap movementTypes;
private HashMap transitions;
private HashMap supportedTransitions;
private LinkedList movementListeners = new LinkedList<>();
/**
* Suppresses notifications to {@link de.tud.kom.p2psim.impl.topology.movement.modularosm.transition.ITransitionStrategy.AttractionAssignmentListener}s.
*/
private boolean suppressListenerNotify = false;
public ModularMultiTypeMovementModel()
{
super();
movementTypes = new HashMap<>();
transitions = new HashMap<>();
supportedTransitions = new HashMap<>();
}
@Override
public void initialize()
{
super.initialize();
suppressListenerNotify = true;
for(ITransitionStrategy strategy : supportedTransitions.values())
{
strategy.setAttractionPoints(transition.getAllAttractionPoints());
strategy.addAttractionAssignmentListener(this);
for (SimLocationActuator ms : moveableHosts) {
strategy.addComponent(ms);
}
}
suppressListenerNotify = false;
}
@Override
protected void doLocalMovement(SimLocationActuator ms, PositionVector destination)
{
assert localMovementStrategy instanceof RealWorldStreetsMovement: "ModularMultiTypeMovementModel can only be used with RealWorldStreetsMovement!";
Either either;
if(movementTypes.containsKey(ms)) either = ((RealWorldStreetsMovement) localMovementStrategy).nextPosition(ms, destination, movementTypes.get(ms));
else either = localMovementStrategy.nextPosition(ms, destination);
if (either.hasLeft()) {
ms.updateCurrentLocation(either.getLeft());
/*
* Check for negative or out of bound coordinates!
*/
assert ms.getRealPosition().getX() >= 0.0
&& ms.getRealPosition().getX() <= Binder
.getComponentOrNull(Topology.class)
.getWorldDimensions().getX();
assert ms.getRealPosition().getY() >= 0.0
&& ms.getRealPosition().getY() <= Binder
.getComponentOrNull(Topology.class)
.getWorldDimensions().getY();
} else {
if(transitions.containsKey(ms)) {
transitions.get(ms).reachedAttractionPoint(ms);
}
else {
transition.reachedAttractionPoint(ms);
}
movementListeners.forEach(l -> l.onTransition(ms));
}
}
/**
* Sets the movement type for the specified {@link SimLocationActuator}. Movement types can be for example
* 'car' or 'foot'. Used types need to be specified in the config at the {@link RealWorldStreetsMovement}.
* @param ms The SimLocationActuator
* @param movementType the movement type
*/
public void setMovementType(SimLocationActuator ms, String movementType)
{
movementTypes.put(ms, movementType);
}
/**
* Returns the current movement type for this {@link SimLocationActuator} as String.
* @param ms The SimLocationActuator
* @return the current movement type
*/
public String getMovementType(SimLocationActuator ms)
{
return movementTypes.get(ms);
}
/**
* Return the currently used transitions strategy of the specified component
* @param ms the component
* @return the current transition strategy
*/
public ITransitionStrategy getTransitionForComponent(SimLocationActuator ms)
{
return transitions.get(ms);
}
/**
* Gets one of the supported transition strategies.
* @param strategy The class of the strategy which should be returned.
* @return The specified strategy
*/
public ITransitionStrategy getTransitionStrategy(Class strategy)
{
ITransitionStrategy selectedStrategy = supportedTransitions.get(strategy);
if(selectedStrategy == null)
{
throw new UnsupportedOperationException(
String.format("ModularMultiTypeMovementModel: TransitionStrategy %s ist not supported!", strategy.toString()));
}
return selectedStrategy;
}
/**
* Sets the {@link ITransitionStrategy} for the specified {@link SimLocationActuator}. Used strategies
* need to be registered in the config.
* @param ms The SimLocationActuator
* @param strategy the strategy to use
*/
public void setTransitionForComponent(SimLocationActuator ms, Class strategy)
{
changeTransitionStrategy(ms, getTransitionStrategy(strategy));
}
/**
* Changes the transition strategy of the specified {@link SimLocationActuator}.
* @param ms The SimLocationActuator
* @param newStrategy the new strategy to use
*/
private void changeTransitionStrategy(SimLocationActuator ms, ITransitionStrategy newStrategy)
{
ITransitionStrategy usedStrategy = transitions.containsKey(ms) ? transitions.get(ms) : transition;
newStrategy.updateTargetAttractionPoint(ms, usedStrategy.getAssignment(ms));
transitions.put(ms, newStrategy);
Monitor.log(ModularMultiTypeMovementModel.class, Monitor.Level.DEBUG, String.format("Client %s changed his transition strategy from %s to %s", ms.getHost().getId().toString(), usedStrategy.getClass(), newStrategy.getClass()));
}
/**
* Returns a list of points representing the current route of the component. Points are
* in x / y values of the own world.
* @param ms the component
* @return list of movement points.
*/
public List getMovementPoints(SimLocationActuator ms)
{
return ((RealWorldStreetsMovement) localMovementStrategy).getMovementPoints(ms);
}
public RealWorldStreetsMovement getMovementStrategy()
{
return (RealWorldStreetsMovement) localMovementStrategy;
}
/**
* Sets the default {@link ITransitionStrategy} for the specified {@link SimLocationActuator}.
* @param ms The SimLocationActuator
*/
public void returnToDefaultTransition(SimLocationActuator ms)
{
transitions.remove(ms);
}
@Override
public void changeTargetLocation(SimLocationActuator actuator, AttractionPoint ap) {
if(transitions.containsKey(actuator)) transitions.get(actuator).updateTargetAttractionPoint(actuator, ap);
else transition.updateTargetAttractionPoint(actuator, ap);
}
@Override
public void setITransitionStrategy(ITransitionStrategy transition) {
if(supportedTransitions.size() == 0) this.transition = transition;
supportedTransitions.put(transition.getClass(), transition);
}
@Override
public AttractionPoint getTargetLocation(SimLocationActuator actuator) {
if(transitions.containsKey(actuator)) return transitions.get(actuator).getAssignment(actuator);
else return transition.getAssignment(actuator);
}
@Override
public void updatedAttractionAssignment(SimLocationActuator component, AttractionPoint newAssignment) {
//Notifications of listeners get suppressed in setup phase to prevent multiple assignments of destinations.
if(suppressListenerNotify) return;
super.updatedAttractionAssignment(component, newAssignment);
}
public void addMovementListener(MultiTypeMovementListener listener)
{
movementListeners.add(listener);
}
public void removeMovementListener(MultiTypeMovementListener listener)
{
movementListeners.remove(listener);
}
}