-
Notifications
You must be signed in to change notification settings - Fork 41
Message Bus
Sometimes you require multiple components to communicate with each other but giving them access to each causes your code to become tangled.
One solution to this is to use a Message Bus pattern with the minibus library. The library implements a Message Bus, multiple types of message exchangers, and, is specifically designed for video game development.
First, add the following to your core project's dependencies in build.gradle. At the time of writing, the current minibus version is 1.4.0 so update the line with the latest version.
compile "org.mini2Dx:minibus:1.8.0"First, create a MessageBus instance to share across your components and ensure it is updated each frame.
MessageBus messageBus = new MessageBus();
...
messageBus.update(delta);Next, create a class that will receive messages.
public class MessageHandlerExample implements MessageHandler {
@Override
public void onMessageReceived(String messageType, MessageExchange source, MessageExchange receiver, MessageData messageData) {
System.out.println(messageType);
}
}Next, connect your message handler to a message exchange.
MessageHandlerExample handler = new MessageHandlerExample();
...
messageBus.createOnUpdateExchange(handler);There are several types of message exchanges available in minibus. Note that each exchange has its own queue for receiving messages.
| Exchange | Method | Description |
|---|---|---|
| ImmediateMessageExchange | createImmediateExchange(handler) | Creates an exchange that will process messages immediately as they are published |
| IntervalMessageExchange | createIntervalExchange(handler, interval); | Creates an exchange that will process messages at a regular interval (float value - in seconds) |
| OnUpdateMessageExchange | createOnUpdateExchange(handler); | Creates an exchange that will process messages each update |
| ConcurrentMessageExchange | createConcurrentExchange(handler); | Creates an exchange that will process messages on a separate thread |
Finally, broadcast a message to be consumed by all exchanges.
messageBus.broadcast("playerMovementMessageType");In the previous example, messages have no data and are just strings representing the message type. To include data with the message, create a class to store the data and implement the MessageData interface.
public class ExampleMessageData implements MessageData {
private final int value;
public DummyMessageData(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}Then broadcast your message with the data.
messageBus.broadcast("playerMovementMessageType", new ExampleMessageData(7));It is possible to send messages directly to a message exchange.
messageBus.sendTo(messageExchange, "playerMovementMessageType", new ExampleMessageData(7));Or from one exchange to another.
messageExchange.sendTo(messageExchange2, "playerMovementMessageType", new ExampleMessageData(7));It is also possible to broadcast a message and handle the first response received from any message exchange.
First set up your message handler to respond to the query.
public class MessageHandlerExample implements MessageHandler {
@Override
public void onMessageReceived(String messageType, MessageExchange source, MessageExchange receiver, MessageData messageData) {
//Send from the receiving message exchange to the source
receiver.sendTo(source, "responseMessageType");
}
}Then broadcast your query and handle the response.
messageBus.broadcastQuery("playerMovementMessageType", new ExampleMessageData(7), "responseMessageType", new MessageHandler() {
@Override
public void onMessageReceived(String messageType, MessageExchange source, MessageExchange receiver, MessageData messageData) {
System.out.println("Received response: " + messageType);
}
});To reduce the load on the garbage collector, it is possible to pool instances of your message data. All built-in message data types (IntMessageData, ListMessageData, etc.) support pooling out of the box.
First, modify your existing MessageData implementations to implement PooledMessageData and accept a MessagePool in the constructor. To reduce duplicate code, it's possible to also extend OptionallyPooledMessageData.
public class ExampleMessageData extends OptionallyPooledMessageData {
private int value;
public DummyMessageData(MessageDataPool<PooledMessageData> pool) {
super(pool);
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}From there, create a MessageDataPool and use that to create instances when you need.
MessageDataPool<ExampleMessageData> messageDataPool = new MessageDataPool<ExampleMessageData>(ExampleMessageData.class);
...
ExampleMessageData messageData = messageDataPool.allocate();
messageData.setValue(7);
//Message data will return to the pool once all exchanges have received and processed the data
messageBus.broadcast("playerMovementMessageType", messageData);If your messages aren't being received, first check these common problems before reporting an issue:
- Is messageBus.update(delta) called each frame?
- Have you connected your handlers to an exchange?
Getting Started
Graphics
Gameplay
Data
- Asset Management
- Saving and loading player data
- Converting objects to/from JSON
- Converting objects to/from XML
User Interfaces / HUDs
Structuring your code
Releasing your game