Last updated on 16th July 2024
Once agents have been created, and then linked together either directly or via a topology they will then communicate via messaging. There are multiple ways to send a message. For more information on messages see Messaging
Simudyne SDK provides a fluent API with the capability to filter and customise messages sent based on the link the message will be sent along.
All message sending is done by the agents, and so can only be done from inside an agent class.
Messages can be sent along specific links that an agent holds. Methods must be chained in a particular order.
getLinks()
must be called providing the link class you want to send messages along.send()
must be called, which takes the message class and possibly either a biconsumer or message body, used to construct the message. This will send the messages along the previously gotten links.Below shows how to use a generic message class.
Generic message along a link (Java)
getLinks(Links.Neighbour.class)
.send(Messages.Alive.class, (message, link) -> message.alive=true);
We see the send message takes as second argument a BiConsumer
allowing access to link data while constructing the message. In this example we did not require accessing any data on the link, but it is available if needed. Our message class looks like this.
public static class Alive extends Message {
boolean alive;
}
However, if our message only contains a single primitive field, like above, extend one of the specialised message classes. Below, a value given is assigned into the body of the constructed message automatically.
Specialised messages
cell.getLinks(Links.Neighbour.class)
.send(Messages.Alive.class, cell.alive)
Where we now extend Message.Boolean.
public static class Alive extends Message.Boolean {}
Messages can also be sent directly to an agent by providing the id associated with the recipient. This is useful when you wish to employ a request-response pattern, where an agent replies directly to the sender of a message.
Request-response pattern (Java)
Messages.Bid bid = seller.getMessageOfType(Messages.Bid.class);
if (bid.getBody() > minBid) {
seller.send(Messages.Asset.class)
.to(bid.getSender());
}
It is useful to know that you can succinctly filter the list of links getLinks
returns. Simply chain filter
on the end of getLinks
and pass in the predicate you wish to filter on.
Filtering links (Java)
bank.getLinks(Links.MortgageLinks)
.filter(mortgageLink -> mortgageLink == isActive)
...
This will return only mortgages that are currently active.
By default messages are not received in a defined order. While one can implicitly be viewed as messages sent by agents processing may appear to be same, the usage of Split actions and processing across multiple nodes/threads may result in messages being recieved in a different order than intended. This can also cause issues with determinism if the processing of how messages are received can cause different results.
To resolve this you can specify within your messages a parameter, and upon receipt order your messages based on that parameter, or a set of parameters. (Such as making sure to handle high-priority messages before low, but then to go by agent name, or ID)
However if you do not wish to work with this type of sorting you may also make usage of OrderedMessages. An OrderedMessage
works functionally the same as a regular Message
and can even be received in an unordered fashion. Like Message
all relevant subtypes have also been created.
When using an OrderedMessage a unique timestamp will be created that will be used by default for the sorting of the inbox when used in conjunction with getOrderedMessagesOfType
.
Sending an OrderedMessage (Java)
getLinks(Links.Neighbour.class)
.sendOrdered(Messages.Alive.class, (message, link) -> message.alive=true);
Receiving messages is driven by the message types. Any message received is the message type itself.
This works for getting a single message. If there are multiple messages of this type, it will return one of them (at random).
Receiving Messages (Java)
class Cell extends Agent<GlobalState> {
public Action<Cell> getAliveMessage() {
return Action.create(
Cell.class,
cell -> {
Message<Messages.Aliveness> messages = cell.getMessageOfType(Messages.Aliveness.class);
boolean isAlive = isAliveMessage.getBody();
}
);
}
}
If expecting multiple message of a particular type use getMessagesOfType
. This will return the messages in a List
.
Receiving a List of Messages (Java)
class Cell extends Agent<GlobalState> {
public Action<Cell> getAliveMessage() {
return Action.create(
Cell.class,
cell -> {
List<Messages.Aliveness> aliveMessages = cell.getMessagesOfType(Messages.Aliveness.class);
long count = aliveMessages.stream().filter(Message::getBody).count();
}
);
}
}
hasMessage
can be used to check if a message of a particular type has been received.
Checking for messages of given type (Java)
class Cell extends Agent<GlobalState> {
public Action<Cell> getAliveMessage() {
return Action.create(
Cell.class,
cell -> {
Messages.Aliveness messages = cell.hasMessageOfType(Messages.Aliveness.class);
}
);
}
}
As explained above with sending ordered messages you must also modify your code on receipt in order to make sure that the messages you sent will be properly sorted in the order of sending.
Receiving a List of Ordered Messages (Java)
class Cell extends Agent<GlobalState> {
public Action<Cell> getAliveMessage() {
return Action.create(
Cell.class,
cell -> {
List<Messages.Aliveness> aliveMessages = cell.getOrderedMessagesOfType(Messages.Aliveness.class);
long count = aliveMessages.stream().filter(Message::getBody).count();
}
);
}
}