
io.jbotsim.core.DelayMessageEngine Maven / Gradle / Ivy
/*
* Copyright 2008 - 2020, Arnaud Casteigts and the JBotSim contributors
*
*
* This file is part of JBotSim.
*
* JBotSim is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JBotSim 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JBotSim. If not, see .
*
*/
package io.jbotsim.core;
import java.util.*;
/**
* The {@link DelayMessageEngine} is responsible for the regular transmission of the available
* {@link Message Messages} from any sender {@link Node} of the {@link Topology} to their destination.
*
* By default, {@link Message Messages} sent during round n are delivered at the beginning of round
* n+1; provided that the corresponding arc (a {@link Link} going, at least, from the
* {@link Message}'s source {@link Node} to its destination) actually exists at the beginning of round n+1.
*
* Delay feature
* The previously explained default delivery duration ({@link #DEFAULT_DELAY}) is said instantaneous
* ({@link #DELAY_INSTANT}).
* The {@link DelayMessageEngine} allows you to modify this duration by specifying the
* amount of rounds {@link Message Messages} should take to be delivered, using {@link #setDelay(int)}.
*
* Link
checks
* By default, each round, the {@link DelayMessageEngine} checks for each {@link Message}
* that the corresponding {@link Link} is still present. If not, the {@link Message} is dropped.
* The time spent doing this rises with the delay and the number of {@link Node Nodes} and {@link Message Messages}.
* Depending on your case, you might want to disable this using {@link #disableLinksContinuityChecks()}.
*/
public class DelayMessageEngine extends DefaultMessageEngine {
/**
* The delay value to use for the shortest delivery time possible; value: {@value #DELAY_INSTANT}.
* @see #setDelay(int)
* @see #getDelay()
*/
public static final int DELAY_INSTANT = 1;
/**
* The default number of round before message delivery; value: {@value #DELAY_INSTANT}.
* @see #setDelay(int)
* @see #getDelay()
*/
public static final int DEFAULT_DELAY = DELAY_INSTANT;
private int delay;
protected Map> delayedMessages = new HashMap<>();
protected int currentTime;
private boolean shouldCheckLinksContinuity = true;
/**
* Creates a {@link DelayMessageEngine}.
* By default, the messages sent during one round are actually during the next.
*
* @param topology the {@link Topology} to use.
*/
public DelayMessageEngine(Topology topology) {
this(topology, DEFAULT_DELAY);
}
/**
* Creates a {@link DelayMessageEngine}.
*
* @param topology the {@link Topology} to use.
* @param delay the number of round a message should be delayed, as an integer.
*/
public DelayMessageEngine(Topology topology, int delay) {
super(topology);
assert(delay >= 0);
setDelay(delay);
this.shouldCheckLinksContinuity = true;
}
/**
* Sets the number of round a message should be delayed.
* @param speed the number of round a message should be delayed, as an integer.
* @deprecated Please use {@link #setDelay(int)} instead.
*/
@Deprecated
public void setSpeed(int speed) {
setDelay(speed);
}
/**
* Sets the number of round a message should be delayed.
* Any value below {@link #DELAY_INSTANT} (i.e. {@value #DELAY_INSTANT}) will be replaced by
* {@link #DELAY_INSTANT}.
*
* @param delay the number of round a message should be delayed, as an integer.
* @see #getDelay()
*/
public void setDelay(int delay) {
if(delay < DELAY_INSTANT)
this.delay = DELAY_INSTANT;
else
this.delay = delay;
}
/**
* Gets the number of round a message should be delayed.
*
* @return the number of round a message should be delayed, as an integer.
* @see #setDelay(int)
*/
public int getDelay() {
return delay;
}
/**
* Specifies whether a {@link Message} should be removed if the corresponding {@link Link} disappears at some
* point during its waiting delay.
*
* Note: Whatever the status, the link presence will be checked at sending time and delivery time.
*
* @return true
if intermediate checks and removal should be performed; false
otherwise.
* @see #disableLinksContinuityChecks()
*/
public boolean shouldCheckLinksContinuity() {
return shouldCheckLinksContinuity;
}
/**
* Disables the intermediate checks on {@link Link} existence for each {@link Message}.
* @see #shouldCheckLinksContinuity()
*/
public void disableLinksContinuityChecks() {
this.shouldCheckLinksContinuity = false;
}
@Override
public void onClock() {
currentTime = topology.getTime();
List nodes = topology.getNodes();
clearMailboxes(nodes);
List newMessages = collectMessages(nodes);
removeIrrelevantMessages(newMessages.listIterator(), nodes);
List messagesToSend = getMessagesToSend(newMessages, nodes);
deliverMessages(messagesToSend);
delayedMessages.remove(currentTime);
}
/**
* Constructs the {@link List} of {@link Message Messages} that must be sent during this round.
* @param newMessages the {@link List} of new {@link Message Messages} which has been collected during this round.
* @param existingNodes the {@link Collection} of existing {@link Node Nodes}.
* @return the {@link List} of {@link Message Messages} that must be sent during this round.
*/
protected List getMessagesToSend(List newMessages, Collection existingNodes) {
List currentDateMessages;
if (noCachingNeeded(newMessages))
currentDateMessages = newMessages;
else {
if(shouldCheckLinksContinuity())
removeIrrelevantMessages(existingNodes);
cacheNewMessages(newMessages);
currentDateMessages = getMessagesForCurrentDate();
if(!shouldCheckLinksContinuity())
removeIrrelevantMessages(currentDateMessages.listIterator(), existingNodes);
}
return currentDateMessages;
}
/**
* Tests whether the provided list of {@link Message Messages} should be cached or not.
* @param newMessages a {@link List} containing new {@link Message Messages}.
* @return true
if the provided messages should not be cached.
*/
protected boolean noCachingNeeded(List newMessages) {
return getDelay() == DELAY_INSTANT && delayedMessages.isEmpty();
}
/**
* Removes any irrelevant messages from the cached delayed messages, according to the {@link Collection} of
* existing {@link Node Nodes}.
* @param existingNodes the {@link Collection} of existing {@link Node Nodes}.
* @see #removeIrrelevantMessages(ListIterator, Collection)
*/
protected void removeIrrelevantMessages(Collection existingNodes) {
for (List messageList : delayedMessages.values())
removeIrrelevantMessages(messageList.listIterator(), existingNodes);
}
/**
* Caches the provided {@link List} of new {@link Message Messages}.
* @param messages a {@link List} containing new {@link Message Messages}.
* @see #prepareNewMessagesForCaching(List)
* @see #cacheMessagesAtTime(List, int)
*/
protected void cacheNewMessages(List messages) {
Map> newMessages = prepareNewMessagesForCaching(messages);
for (Map.Entry> entry : newMessages.entrySet())
cacheMessagesAtTime(entry.getValue(), entry.getKey());
}
/**
* Transforms the provided {@link List} of new {@link Message Messages} for caching into a suitable data
* structure.
* @param newMessages a {@link List} containing new {@link Message Messages}.
* @return a {@link Map} containing {@link List Lists} of {@link Message Messages} indexed by the date at which they
* should be delivered.
* @see #getCurrentDeliveryDate()
*/
protected Map> prepareNewMessagesForCaching(List newMessages) {
Map> messagesMap = new HashMap<>();
messagesMap.put(getCurrentDeliveryDate(), newMessages);
return messagesMap;
}
/**
* Caches a specific {@link Message} at it's planned delivery time (round number).
* @param messagesMap the {@link Map}, indexing {@link Message Messages} by their delivery date, in which the
* message should be cached.
* @param message the {@link Message} to cache.
* @param deliveryTime the round number at which the message should be delivered.
*/
protected void cacheMessageAtTime(Map> messagesMap, Message message, int deliveryTime) {
if(messagesMap.containsKey(deliveryTime))
messagesMap.get(deliveryTime).add(message);
else {
List messageList = new ArrayList<>();
messageList.add(message);
messagesMap.put(deliveryTime, messageList);
}
}
/**
* Caches (internally) the specified {@link List} of {@link Message Messages} at that the given delivery time
* (round number).
* @param messages the {@link List} of {@link Message Messages} to cache internally.
* @param deliveryTime the round number at which the messages should be delivered.
*/
protected void cacheMessagesAtTime(List messages, int deliveryTime) {
if(delayedMessages.containsKey(deliveryTime))
delayedMessages.get(deliveryTime).addAll(messages);
else
delayedMessages.put(deliveryTime, messages);
}
/**
* Retrieves the list of {@link Message Messages} which should be delivered during the current round, from the
* internal storage.
* @return a {@link List} of {@link Message Messages} containing all messages which should be delivered during the
* current round. Can be empty, but not null.
*/
protected List getMessagesForCurrentDate() {
List messages = delayedMessages.get(currentTime);
return messages != null ? messages : new ArrayList<>();
}
/**
* Computes the delay which should be applied to the provided {@link Message}.
* @param message the {@link Message} needing a delay.
* @return the delay for the provided message.
*/
protected int getDelayForMessage(Message message) {
return getDelay();
}
/**
* Computes the delivery date (round number) for a {@link Message} collected at the start of the current
* round.
* @return the delivery date for the current round, as an integer.
*/
protected int getCurrentDeliveryDate() {
return currentTime + getDelay() - 1;
}
/**
* Resets the {@link DelayMessageEngine}.
*
* - Any {@link Message} (ready to be sent, "in the air" or ready to be received) handled by the
* {@link DelayMessageEngine} is discarded.
* - Other configurations (delay or debug) remain untouched.
*
*/
@Override
public void reset() {
super.reset();
delayedMessages.clear();
}
}