/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* This program 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
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.simulatorPlugIns.plugins.outputStrategy;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import staticContent.evaluation.simulator.Simulator;
import staticContent.evaluation.simulator.annotations.plugin.Plugin;
import staticContent.evaluation.simulator.annotations.property.IntSimulationProperty;
import staticContent.evaluation.simulator.core.event.Event;
import staticContent.evaluation.simulator.core.event.EventExecutor;
import staticContent.evaluation.simulator.core.message.MessageFragment;
import staticContent.evaluation.simulator.core.message.MixMessage;
import staticContent.evaluation.simulator.core.message.TransportMessage;
import staticContent.evaluation.simulator.core.networkComponent.AbstractClient;
import staticContent.evaluation.simulator.core.networkComponent.Mix;
import staticContent.evaluation.simulator.core.networkComponent.NetworkNode;
import userGeneratedContent.simulatorPlugIns.pluginRegistry.Topology;
import userGeneratedContent.simulatorPlugIns.plugins.clientSendStyle.ClientSendStyleImpl;
import userGeneratedContent.simulatorPlugIns.plugins.clientSendStyle.ClientSendWithoutMixes;
import userGeneratedContent.simulatorPlugIns.plugins.clientSendStyle.ClientWaitForReply;
import userGeneratedContent.simulatorPlugIns.plugins.mixSendStyle.MixSendStyleImpl;
import userGeneratedContent.simulatorPlugIns.plugins.mixSendStyle.ReplyReceiver;
// output strategy, that collects messages until "batchSize" messages are reached
// when "batchSize" messages are reached, all messages are sent (in random order)
// accepts only one message per participant for each batch
@Plugin(pluginKey = "DISTINCT_USER_BATCH", pluginName = "Distinct User Batch")
public class DistinctUserBatch extends OutputStrategyImpl implements EventExecutor {
private boolean setupComplete;
private int batchSize;
private MixMessage[] collectedRequests;
private MixMessage[] collectedReplies;
private int requestCounter = 0;
private int replyCounter = 0;
@IntSimulationProperty(
name = "Maximum reply delay (ms)",
key = "MAX_REPLY_DELAY_DISTINCT_USER_BATCH",
min = 0
)
private int timeout;
private Event timeoutEvent = null;
private Map<String, Vector<TransportMessage>> clientReplyWaitingQueues;
private boolean outputAllowed = false;
private final static int HAS_SENT_DUMMY = 0;
private final static int DATA_AVAILABLE = 1;
private final static int TRUE = 1;
private final static int FALSE = 0;
private int[][] replyInfo;
public DistinctUserBatch(Mix mix, Simulator simulator) {
super(mix, simulator);
this.timeout = Simulator.settings.getPropertyAsInt("MAX_REPLY_DELAY_DISTINCT_USER_BATCH");
}
private void setup() {
setupComplete = true;
batchSize = Simulator.getSimulator().getNumberOfClients();
collectedRequests = new MixMessage[batchSize];
if (Simulator.settings.getProperty("COMMUNICATION_MODE").equals("SIMPLEX_REPLY") || Simulator.settings.getProperty("COMMUNICATION_MODE").equals("DUPLEX")) {
collectedReplies = new MixMessage[batchSize];
if (mix.isLastMix()) {
this.replyInfo = new int[batchSize][2];
clientReplyWaitingQueues = new HashMap<String, Vector<TransportMessage>>();
for (AbstractClient c: simulator.getClients().values()) {
clientReplyWaitingQueues.put(c.getIdentifier(), new Vector<TransportMessage>(10,10));
}
}
}
}
@Override
public void incomingRequest(MixMessage mixMessage) {
if (!setupComplete)
setup();
if (collectedRequests[mixMessage.getOwner().getClientId()] != null)
throw new RuntimeException("ERROR! two messages from the same client for one batch! " +mixMessage);
collectedRequests[mixMessage.getOwner().getClientId()] = mixMessage;
if (++requestCounter == batchSize) { // put out batch
if (mix.isLastMix()) {
for (MixMessage m: collectedRequests) {
replyInfo[m.getOwner().getClientId()][HAS_SENT_DUMMY] = m.isDummy() ? TRUE : FALSE;
mix.putOutRequest(m);
}
this.timeoutEvent = new Event(this, Simulator.getNow() + timeout, OutputStrategyEvent.TIMEOUT);
simulator.scheduleEvent(timeoutEvent, this);
outputAllowed = true;
} else {
for (MixMessage m: collectedRequests)
mix.putOutRequest(m);
}
requestCounter = 0;
collectedRequests = new MixMessage[batchSize];
}
}
@Override
public void incomingReply(MixMessage mixMessage) {
if (!setupComplete)
setup();
if (collectedReplies[mixMessage.getOwner().getClientId()] != null)
throw new RuntimeException("ERROR! two messages from the same client for one batch!"); // ggf. nachrichtenqueue einfügen, wenn das unterstützt werden soll
collectedReplies[mixMessage.getOwner().getClientId()] = mixMessage;
if (++replyCounter == batchSize) {
for (MixMessage m: collectedReplies)
mix.putOutReply(m);
replyCounter = 0;
collectedReplies = new MixMessage[batchSize];
}
}
private void timeout() {
putOutReplyBatch(true);
/*if (doOutput()) {
putOutReplyBatch();
} else {
Event timeoutEvent = new Event(this, Simulator.getNow() + timeout, OutputStrategyEvent.TIMEOUT);
simulator.scheduleEvent(timeoutEvent, this);
}*/
}
private boolean doOutput() {
for (int[] data: replyInfo)
if (data[HAS_SENT_DUMMY] == FALSE && data[DATA_AVAILABLE] == 0)
return false;
return true;
}
private void putOutReplyBatch(boolean timeoutReached) {
if (!outputAllowed)
return;
outputAllowed = false;
for (AbstractClient client: simulator.getClients().values()) {
Vector<TransportMessage> replyWaitingQueue = clientReplyWaitingQueues.get(client.getIdentifier());
boolean isDummy = replyWaitingQueue.size() == 0 ? true: false;
MixMessage mixMessage = MixMessage.getInstance(false, mix, client, client, Simulator.getNow(), isDummy);
if (isDummy) {
mix.putOutReply(mixMessage);
} else {
for (int i=0; i<replyWaitingQueue.size(); i++) {
TransportMessage noneMixMessage = replyWaitingQueue.get(i);
if (mixMessage.getFreeSpace() >= noneMixMessage.getLength() && !noneMixMessage.isFragmented()) { // noneMixMessage fits in mixMessage completely
replyWaitingQueue.remove(noneMixMessage);
i--;
mixMessage.addPayloadObject(noneMixMessage);
} else { // add Fragment
if (noneMixMessage.hasNextFragment()) {
MessageFragment messageFragment = noneMixMessage.getFragment(mixMessage.getFreeSpace());
mixMessage.addPayloadObject(messageFragment);
}
if (!noneMixMessage.hasNextFragment()) {
replyWaitingQueue.remove(i);
i--;
}
}
if (mixMessage.getFreeSpace() == 0)
break;
}
replyInfo[mixMessage.getOwner().getClientId()][DATA_AVAILABLE] -= mixMessage.getPayloadLength();
mix.putOutReply(mixMessage);
}
}
replyCounter = 0;
collectedReplies = new MixMessage[batchSize];
if (!timeoutReached) {
simulator.unscheduleEvent(this.timeoutEvent);
}
}
@Override
public void executeEvent(Event event) {
if (event.getEventType() != OutputStrategyEvent.TIMEOUT)
throw new RuntimeException("ERROR! received unsupported event!" +event);
timeout();
}
private class ReplyStyle extends MixSendStyleImpl {
public ReplyStyle(NetworkNode owner, Simulator simulator, ReplyReceiver replyReceiver) {
super(owner, simulator, replyReceiver);
}
@Override
public void incomingDataFromServer(TransportMessage transportMessage) {
if (!mix.isLastMix())
throw new RuntimeException("ERROR: BasicSynchronousBatch only supports NoneMixMessages as reply from distant proxy!");
if (!setupComplete)
setup();
clientReplyWaitingQueues.get(transportMessage.getOwner().getIdentifier()).add(transportMessage);
replyInfo[transportMessage.getOwner().getClientId()][DATA_AVAILABLE] += transportMessage.getLength();
if (doOutput())
putOutReplyBatch(false);
}
}
@Override
public ClientSendStyleImpl getClientSendStyle(AbstractClient client) {
boolean noMixes = !Topology.getTopology().containsAtLeastOneMix();
boolean noRequestChannel = Simulator.settings.getProperty("COMMUNICATION_MODE").equals("SIMPLEX_REPLY");
if (noMixes || noRequestChannel) {
return new ClientSendWithoutMixes(client, Simulator.getSimulator());
} else {
return new ClientWaitForReply(client, Simulator.getSimulator());
}
}
@Override
public MixSendStyleImpl getMixSendStyle() {
return new ReplyStyle(mix, Simulator.getSimulator(), mix);
}
}