package iamrescue.communication; import iamrescue.agent.ISimulationTimer; import iamrescue.communication.compression.NullCompressor; import iamrescue.communication.failuredetection.SentMessageMemory; import iamrescue.communication.failuredetection.SentMessageMemory.SentMessages; import iamrescue.communication.messages.Message; import iamrescue.communication.messages.MessageChannel; import iamrescue.communication.messages.MessageChannelType; import iamrescue.communication.messages.codec.ICommunicationBeliefBaseAdapter; import iamrescue.communication.messages.codec.UnknownMessageFormatException; import iamrescue.communication.messages.updates.EntityUpdatedMessage; import iamrescue.communication.util.ByteArray; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import javolution.util.FastList; import javolution.util.FastMap; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.Validate; import org.apache.log4j.Logger; import rescuecore2.connection.Connection; import rescuecore2.messages.Command; import rescuecore2.misc.Pair; import rescuecore2.standard.entities.Human; import rescuecore2.standard.entities.StandardPropertyURN; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.Property; /** * This class is a container for the sender and the receiver * */ public class CommunicationModule implements ICommunicationModule { private int maxOutboxSizePerChannel = -1; // a list of decoders, in the order in which they are used to attempt to // decode a message private List<IDecoder> decoders = new ArrayList<IDecoder>(); private IEncoder encoder; private Outbox outbox; private Inbox inbox = new Inbox(); private IOutgoingMessageSelector outgoingMessageSelector; private static final Logger LOGGER = Logger .getLogger(CommunicationModule.class); private IIncomingMessageService incomingMessageService; private boolean receivedRadioTransmission = false; private ISimulationTimer timer; private EntityID id; private ICommunicationBeliefBaseAdapter beliefBase; private SentMessageMemory sentMessageMemory; private int lastTime = -1; private int lastChannel = -1; private SentMessages lastSentMessages = null; public CommunicationModule(EntityID id, ISimulationTimer timer, ICommunicationBeliefBaseAdapter beliefBase, final ISimulationCommunicationConfiguration configuration, Connection connection, IMessagingSchedule messageScheduler) { this.id = id; this.timer = timer; // the beliefbase connection is necessary to load entities from the // belief base, such that their id's are encoded more efficiently this.beliefBase = beliefBase; sentMessageMemory = new SentMessageMemory(); outbox = new Outbox(configuration.getChannels()); encoder = new Encoder(new NullCompressor(), beliefBase); // The InterAgentMessageDecoder decodes messages exchanged between *our* // agents addDecoder(InterAgentMessageDecoderFactory.getInstance().create()); // This decoder decodes messages received from entities other than our // agents addDecoder(new ExternalMessageDecoder()); // The IncomingMessageService is the 'physical' wire along which // messages are received. The incoming message service calls the // processIncomingMessage method for processing the raw data incomingMessageService = new RescueCore2IncomingMessageService(id, timer, this, connection, configuration); // The OutgoingMessageService is the physical outgoing wire. IOutgoingMessageService outgoingMessageService = new RescueCore2OutgoingMessageService( id, connection, timer); // The outgoing message selector selects which messages are sent along // which channel. It uses the encoder to encode the messages into a byte // stream. outgoingMessageSelector = new ScheduledOutgoingMessageService(timer, outgoingMessageService, encoder, messageScheduler, sentMessageMemory); } private void addDecoder(IDecoder decoder) { Validate.notNull(decoder); decoders.add(decoder); } public void setMaxOutboxSizePerChannel(int maxOutboxSizePerChannel) { this.maxOutboxSizePerChannel = maxOutboxSizePerChannel; } public void enqueueMessage(Message message, MessageChannel channel) { outbox.enqueueMessage(message, channel); } public Set<MessageChannel> getChannels() { return outbox.getChannels(); } public MessageChannel getChannels(int channelNumber) { return outbox.getChannel(channelNumber); } public void flushOutbox() { // outgoingMessageSelector.sendShoutMessages(outbox.getShoutMessageQ()); reenqueueFailedMessages(); filterOutInconsistentUpdates(); consolidateUpdates(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Messages before sending:"); LOGGER.debug(outbox.getMessagesToString(true)); } else if (LOGGER.isInfoEnabled()) { LOGGER.info("Messages before sending:"); LOGGER.info(outbox.getMessagesToString(false)); } outgoingMessageSelector.sendMessages(outbox.getMessageQs()); inbox.removeReadMessages(); outbox.removeSentAndStaleMessages(); // LOGGER.info("Messages after sending"); // LOGGER.info(outbox.getMessagesToString()); // flush all subscribe commands that ensure we listen to the correct // channels incomingMessageService.flushChannelCommands(); // Now prune messages if (maxOutboxSizePerChannel >= 0) { Map<MessageChannel, List<Message>> messageQs = outbox .getMessageQs(); for (Entry<MessageChannel, List<Message>> entry : messageQs .entrySet()) { List<Message> list = entry.getValue(); while (list.size() > maxOutboxSizePerChannel) { list.remove(list.size() - 1); } } } } // TODO: This should probably be moved elsewhere, since it's inference. private void consolidateUpdates() { Map<MessageChannel, List<Message>> messageQs = outbox.getMessageQs(); Set<MessageChannel> channels = outbox.getChannels(); for (MessageChannel channel : channels) { Map<Pair<EntityID, Integer>, EntityUpdatedMessage> messageMap = new FastMap<Pair<EntityID, Integer>, EntityUpdatedMessage>(); List<Message> msgList = messageQs.get(channel); Iterator<Message> messageIterator = msgList.iterator(); while (messageIterator.hasNext()) { Message m = messageIterator.next(); if (m instanceof EntityUpdatedMessage) { messageIterator.remove(); EntityUpdatedMessage update = (EntityUpdatedMessage) m; Pair<EntityID, Integer> index = new Pair<EntityID, Integer>( update.getObjectID(), (int) update.getTimestamp()); EntityUpdatedMessage existingMessage = messageMap .get(index); if (existingMessage != null) { Set<String> changedProperties = update .getChangedProperties(); for (String propertyURN : changedProperties) { if (!existingMessage.getChangedProperties() .contains(propertyURN)) { existingMessage.addUpdatedProperty(update .getProperty(propertyURN)); } } } else { // Need to add this message messageMap.put(index, update); } } } // End of while through messages Set<Entry<Pair<EntityID, Integer>, EntityUpdatedMessage>> entrySet = messageMap .entrySet(); // Now re-enqueue consolidated messages for (Entry<Pair<EntityID, Integer>, EntityUpdatedMessage> entry : entrySet) { msgList.add(entry.getValue()); } } } // TODO: This should probably be moved elsewhere, since it's inference. private void filterOutInconsistentUpdates() { Map<MessageChannel, List<Message>> messageQs = outbox.getMessageQs(); Set<MessageChannel> channels = outbox.getChannels(); for (MessageChannel channel : channels) { List<Message> msgList = messageQs.get(channel); Iterator<Message> messageIterator = msgList.iterator(); while (messageIterator.hasNext()) { Message m = messageIterator.next(); if (m instanceof EntityUpdatedMessage) { EntityUpdatedMessage message = (EntityUpdatedMessage) m; short timestamp = message.getTimestamp(); if (timestamp < timer.getTime()) { Entity entity = beliefBase.getObjectByID(message .getObjectID().getValue()); if (entity == null) { // This entity has been deleted! messageIterator.remove(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Message " + message + " is about a deleted object " + "and has been removed."); } } else { // This is potentially out of date Collection<Property> properties = message .getProperties(); List<String> toRemove = new FastList<String>(); for (Property p : properties) { Property currentProperty = entity.getProperty(p .getURN()); boolean same; if (!currentProperty.isDefined()) { if (!p.isDefined()) { same = true; } else { same = false; } } else { if (!p.isDefined()) { same = false; } else { if (currentProperty.getValue() instanceof int[]) { same = Arrays.equals( (int[]) currentProperty .getValue(), (int[]) p.getValue()); } else { same = currentProperty.getValue() .equals(p.getValue()); } } } if (!same) { // Not the same! toRemove.add(p.getURN()); if (LOGGER.isTraceEnabled()) { LOGGER .trace("Removing property " + p + ", because current property is " + currentProperty); } } else { // Also check if hp is needed if (p.isDefined() && p.getURN().equals( StandardPropertyURN.HP)) { // We're only interested in HP if agent // is buried if (entity instanceof Human) { Human h = (Human) entity; if (!h.isBuriednessDefined() || !(h.getBuriedness() > 0)) { toRemove.add(p.getURN()); } } else { toRemove.add(p.getURN()); } } } } for (String removeURN : toRemove) { message.removeUpdatedProperty(removeURN); } // Is message empty now? if (message.getProperties().size() == 0) { messageIterator.remove(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Message " + message + " is now empty " + "and has been removed."); } } } } } } } } public Collection<Message> getUnsentMessages(MessageChannel channel) { return outbox.getMessageQs().get(channel); } public Collection<Message> getUnreadMessages() { return inbox.getUnreadMessages(); } protected void processIncomingMessage(EntityID senderAgentID, MessageChannel channel, int timestep, byte[] messageContents) { Validate.isTrue(!decoders.isEmpty(), "No decoders registered, cannot decode messages"); if (lastTime != timestep || channel.getChannelNumber() != lastChannel || lastSentMessages == null) { lastSentMessages = sentMessageMemory.getSentMessages(channel); lastTime = timestep; lastChannel = channel.getChannelNumber(); } if (channel.getType() == MessageChannelType.RADIO) { receivedRadioTransmission = true; } if (messageContents.length == 0) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Drop-out message found from " + senderAgentID + " on channel " + channel); } // if (senderAgentID.equals(id)) { // My own message dropped out // if (channel.getInputDropoutProbability() == 0) { // No input drop-out possible - must have dropped out on // output. This is ok. Means message was sent. // sentMessageMemory.clear(channel.getChannelNumber()); // } // } if (beliefBase.isRescueEntity(senderAgentID)) { // No need to do anything else return; } } boolean decoded = false; List<UnknownMessageFormatException> errors = new ArrayList<UnknownMessageFormatException>(); // try to decode the message with each decoder for (IDecoder decoder : decoders) { if (!decoder.canDecode(senderAgentID, channel, timestep, messageContents, beliefBase)) { continue; } try { List<Message> messages = decoder.decode(senderAgentID, channel, timestep, messageContents, beliefBase); for (Message message : messages) { // communicatedWithAgents.add(message.getSenderAgentID()); // if this message has not been received from myself if (!message.getSenderAgentID().equals(id)) { inbox.addMessage(message); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Added message to inbox: " + message); } } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Ignored message from myself: " + message); } if (lastSentMessages != null) { lastSentMessages.received(new ByteArray( messageContents)); } // sentMessageMemory.clear(channel.getChannelNumber()); // if (log.isInfoEnabled()) // log.info("Received message from " // + message.getSenderAgentID()); } } decoded = true; break; } catch (UnknownMessageFormatException e) { if (LOGGER.isTraceEnabled()) LOGGER.trace("Could not decode the message with decoder " + decoder.getClass()); errors.add(e); } } if (!decoded) { LOGGER.error("Message could not be decoded with any available" + " message decoder. Contents: " + ArrayUtils.toString(messageContents)); LOGGER.error("Errors: "); for (UnknownMessageFormatException error : errors) { LOGGER.error(error); } LOGGER.error("Message origin: " + senderAgentID); LOGGER.error("Channel : " + channel); LOGGER.error("Time step : " + timestep); } } public Collection<Message> getUnreadMessages(IMessageFilter messageFilter) { return inbox.getUnreadMessages(messageFilter); } public Outbox getOutbox() { return outbox; } public boolean isRadioCommunicationPossible() { // TODO check return receivedRadioTransmission || timer.getTime() <= 8; } @Override public void hear(Collection<Command> heard) { incomingMessageService.hear(heard); } @Override public void enqueueMessage(Message message, List<MessageChannel> channels) { for (MessageChannel channel : channels) { enqueueMessage(message, channel); } } public void reenqueueFailedMessages() { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Reenqueuing failed messages"); } Set<Integer> sentChannels = sentMessageMemory.getSentChannels(); for (Integer channel : sentChannels) { SentMessages sentMessages = sentMessageMemory .getSentMessages(channel); Collection<Message> messages = sentMessages.getMessages(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Failed messages for channel " + channel + ": " + messages); } if (incomingMessageService.getSubscribedChannels().contains( sentMessages.getChannel())) { for (Message message : messages) { // Reduce TTL, because it was sent last time step message.setTTL(message.getTTL() - 1); if (message.getTTL() > 0) { if (message.getRepeats() < 0) { message.setRepeats(1); } else { message.setRepeats(message.getRepeats() + 1); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Re-sending failed message: " + message); enqueueMessage(message, sentMessages.getChannel()); } } } } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Ignoring these because " + "not subscribed to channel."); } } } sentMessageMemory.clearAll(); } public void subscribeToChannels(List<MessageChannel> channels) { for (MessageChannel channel : channels) { incomingMessageService.startListeningToChannel(channel); } } /* * public static void main(String[] args) { List<Message> messages = new * ArrayList<Message>(); messages.add(new PingMessage()); messages.add(new * PingMessage()); messages.add(new PingMessage()); messages.add(new * PingMessage()); messages.add(new PingMessage()); messages.add(new * PingMessage()); messages.get(0).setPriority(MessagePriority.HIGH); * messages.get(1).setPriority(MessagePriority.VERY_HIGH); * messages.get(2).setPriority(MessagePriority.CRITICAL); * messages.get(3).setPriority(MessagePriority.VERY_LOW); * messages.get(4).setPriority(MessagePriority.LOW); * messages.get(5).setPriority(MessagePriority.NORMAL); * System.out.println("Before: " + messages); * Collections.sort(messages,Outbox.messagePriorityComparator); * System.out.println("After: " + messages); } */ }