Skip to content
Thomas Cashman edited this page Dec 2, 2022 · 13 revisions

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.

Set Up

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"

Creating a Message Bus

First, create a MessageBus instance to share across your components and ensure it is updated each frame.

MessageBus messageBus = new MessageBus();
...
messageBus.update(delta);

Create a Message Handler

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);
	}
}

Connect the Message Handler to a MessageExchange

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

Broadcast a message

Finally, broadcast a message to be consumed by all exchanges.

messageBus.broadcast("playerMovementMessageType");

Broadcast a message with data

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));

Send messages directly to or between exchanges

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));

Broadcasting a query and handling a response

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);		
	}
});

Message Data Pooling (v1.3.0+)

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);

Troubleshooting

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?

Clone this wiki locally