com.badlogic.gdx.ai.msg.MessageDispatcher Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.ai.msg;
import com.badlogic.gdx.ai.GdxAI;
import com.badlogic.gdx.ai.Timepiece;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.reflect.ClassReflection;
/** A {@code MessageDispatcher} is in charge of the creation, dispatch, and management of telegrams.
*
* @author davebaol */
public class MessageDispatcher implements Telegraph {
private static final String LOG_TAG = MessageDispatcher.class.getSimpleName();
private static final Pool pool = new Pool(16) {
protected Telegram newObject () {
return new Telegram();
}
};
private PriorityQueue queue;
private IntMap> msgListeners;
private IntMap> msgProviders;
private boolean debugEnabled;
/** Creates a {@code MessageDispatcher} */
public MessageDispatcher () {
this.queue = new PriorityQueue();
this.msgListeners = new IntMap>();
this.msgProviders = new IntMap>();
}
/** Returns true if debug mode is on; false otherwise. */
public boolean isDebugEnabled () {
return debugEnabled;
}
/** Sets debug mode on/off. */
public void setDebugEnabled (boolean debugEnabled) {
this.debugEnabled = debugEnabled;
}
/** Registers a listener for the specified message code. Messages without an explicit receiver are broadcasted to all its
* registered listeners.
* @param listener the listener to add
* @param msg the message code */
public void addListener (Telegraph listener, int msg) {
Array listeners = msgListeners.get(msg);
if (listeners == null) {
// Associate an empty unordered array with the message code
listeners = new Array(false, 16);
msgListeners.put(msg, listeners);
}
listeners.add(listener);
// Dispatch messages from registered providers
Array providers = msgProviders.get(msg);
if (providers != null) {
for (int i = 0, n = providers.size; i < n; i++) {
TelegramProvider provider = providers.get(i);
Object info = provider.provideMessageInfo(msg, listener);
if (info != null) {
Telegraph sender = ClassReflection.isInstance(Telegraph.class, provider) ? (Telegraph)provider : null;
dispatchMessage(0, sender, listener, msg, info, false);
}
}
}
}
/** Registers a listener for a selection of message types. Messages without an explicit receiver are broadcasted to all its
* registered listeners.
*
* @param listener the listener to add
* @param msgs the message codes */
public void addListeners (Telegraph listener, int... msgs) {
for (int msg : msgs)
addListener(listener, msg);
}
/** Registers a provider for the specified message code.
* @param msg the message code
* @param provider the provider to add */
public void addProvider (TelegramProvider provider, int msg) {
Array providers = msgProviders.get(msg);
if (providers == null) {
// Associate an empty unordered array with the message code
providers = new Array(false, 16);
msgProviders.put(msg, providers);
}
providers.add(provider);
}
/** Registers a provider for a selection of message types.
* @param provider the provider to add
* @param msgs the message codes */
public void addProviders (TelegramProvider provider, int... msgs) {
for (int msg : msgs)
addProvider(provider, msg);
}
/** Unregister the specified listener for the specified message code.
* @param listener the listener to remove
* @param msg the message code */
public void removeListener (Telegraph listener, int msg) {
Array listeners = msgListeners.get(msg);
if (listeners != null) {
listeners.removeValue(listener, true);
}
}
/** Unregister the specified listener for the selection of message codes.
*
* @param listener the listener to remove
* @param msgs the message codes */
public void removeListener (Telegraph listener, int... msgs) {
for (int msg : msgs)
removeListener(listener, msg);
}
/** Unregisters all the listeners for the specified message code.
* @param msg the message code */
public void clearListeners (int msg) {
msgListeners.remove(msg);
}
/** Unregisters all the listeners for the given message codes.
*
* @param msgs the message codes */
public void clearListeners (int... msgs) {
for (int msg : msgs)
clearListeners(msg);
}
/** Removes all the registered listeners for all the message codes. */
public void clearListeners () {
msgListeners.clear();
}
/** Unregisters all the providers for the specified message code.
* @param msg the message code */
public void clearProviders (int msg) {
msgProviders.remove(msg);
}
/** Unregisters all the providers for the given message codes.
*
* @param msgs the message codes */
public void clearProviders (int... msgs) {
for (int msg : msgs)
clearProviders(msg);
}
/** Removes all the registered providers for all the message codes. */
public void clearProviders () {
msgProviders.clear();
}
/** Removes all the telegrams from the queue and releases them to the internal pool. */
public void clearQueue () {
for (int i = 0; i < queue.size(); i++) {
pool.free(queue.get(i));
}
queue.clear();
}
/** Removes all the telegrams from the queue and the registered listeners for all the messages. */
public void clear () {
clearQueue();
clearListeners();
clearProviders();
}
/** Sends an immediate message to all registered listeners, with no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* null, null, msg, null, false)}
*
* @param msg the message code */
public void dispatchMessage (int msg) {
dispatchMessage(0f, null, null, msg, null, false);
}
/** Sends an immediate message to all registered listeners, with no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, null, msg, null, false)}
*
* @param sender the sender of the telegram
* @param msg the message code */
public void dispatchMessage (Telegraph sender, int msg) {
dispatchMessage(0f, sender, null, msg, null, false);
}
/** Sends an immediate message to all registered listeners, with no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, null, msg, null, needsReturnReceipt)}
*
* @param sender the sender of the telegram
* @param msg the message code
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (Telegraph sender, int msg, boolean needsReturnReceipt) {
dispatchMessage(0f, sender, null, msg, null, needsReturnReceipt);
}
/** Sends an immediate message to all registered listeners, with extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* null, null, msg, extraInfo, false)}
*
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (int msg, Object extraInfo) {
dispatchMessage(0f, null, null, msg, extraInfo, false);
}
/** Sends an immediate message to all registered listeners, with extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, null, msg, extraInfo, false)}
*
* @param sender the sender of the telegram
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (Telegraph sender, int msg, Object extraInfo) {
dispatchMessage(0f, sender, null, msg, extraInfo, false);
}
/** Sends an immediate message to all registered listeners, with extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, null, msg, extraInfo, needsReturnReceipt)}
*
* @param sender the sender of the telegram
* @param msg the message code
* @param extraInfo an optional object
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (Telegraph sender, int msg, Object extraInfo, boolean needsReturnReceipt) {
dispatchMessage(0f, sender, null, msg, extraInfo, needsReturnReceipt);
}
/** Sends an immediate message to the specified receiver with no extra info. The receiver doesn't need to be a register listener
* for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, receiver, msg, null, false)}
*
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code */
public void dispatchMessage (Telegraph sender, Telegraph receiver, int msg) {
dispatchMessage(0f, sender, receiver, msg, null, false);
}
/** Sends an immediate message to the specified receiver with no extra info. The receiver doesn't need to be a register listener
* for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, receiver, msg, null, needsReturnReceipt)}
*
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (Telegraph sender, Telegraph receiver, int msg, boolean needsReturnReceipt) {
dispatchMessage(0f, sender, receiver, msg, null, needsReturnReceipt);
}
/** Sends an immediate message to the specified receiver with extra info. The receiver doesn't need to be a register listener
* for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, receiver, msg, extraInfo, false)}
*
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (Telegraph sender, Telegraph receiver, int msg, Object extraInfo) {
dispatchMessage(0f, sender, receiver, msg, extraInfo, false);
}
/** Sends an immediate message to the specified receiver with extra info. The receiver doesn't need to be a register listener
* for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean) dispatchMessage(0,
* sender, receiver, msg, extraInfo, needsReturnReceipt)}
*
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param extraInfo an optional object
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (Telegraph sender, Telegraph receiver, int msg, Object extraInfo, boolean needsReturnReceipt) {
dispatchMessage(0f, sender, receiver, msg, extraInfo, needsReturnReceipt);
}
/** Sends a message to all registered listeners, with the specified delay but no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, null, null, msg, null, null)}
*
* @param delay the delay in seconds
* @param msg the message code */
public void dispatchMessage (float delay, int msg) {
dispatchMessage(delay, null, null, msg, null, false);
}
/** Sends a message to all registered listeners, with the specified delay but no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, null, msg, null, false)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param msg the message code */
public void dispatchMessage (float delay, Telegraph sender, int msg) {
dispatchMessage(delay, sender, null, msg, null, false);
}
/** Sends a message to all registered listeners, with the specified delay but no extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, null, msg, null, needsReturnReceipt)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param msg the message code
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (float delay, Telegraph sender, int msg, boolean needsReturnReceipt) {
dispatchMessage(delay, sender, null, msg, null, needsReturnReceipt);
}
/** Sends a message to all registered listeners, with the specified delay and extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, null, null, msg, extraInfo, false)}
*
* @param delay the delay in seconds
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (float delay, int msg, Object extraInfo) {
dispatchMessage(delay, null, null, msg, extraInfo, false);
}
/** Sends a message to all registered listeners, with the specified delay and extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, null, msg, extraInfo, false)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (float delay, Telegraph sender, int msg, Object extraInfo) {
dispatchMessage(delay, sender, null, msg, extraInfo, false);
}
/** Sends a message to all registered listeners, with the specified delay and extra info.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, null, msg, extraInfo, needsReturnReceipt)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param msg the message code
* @param extraInfo an optional object
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (float delay, Telegraph sender, int msg, Object extraInfo, boolean needsReturnReceipt) {
dispatchMessage(delay, sender, null, msg, extraInfo, needsReturnReceipt);
}
/** Sends a message to the specified receiver, with the specified delay but no extra info. The receiver doesn't need to be a
* register listener for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, receiver, msg, null, false)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code */
public void dispatchMessage (float delay, Telegraph sender, Telegraph receiver, int msg) {
dispatchMessage(delay, sender, receiver, msg, null, false);
}
/** Sends a message to the specified receiver, with the specified delay but no extra info. The receiver doesn't need to be a
* register listener for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, receiver, msg, null, needsReturnReceipt)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (float delay, Telegraph sender, Telegraph receiver, int msg, boolean needsReturnReceipt) {
dispatchMessage(delay, sender, receiver, msg, null, needsReturnReceipt);
}
/** Sends a message to the specified receiver, with the specified delay but no extra info. The receiver doesn't need to be a
* register listener for the specified message code.
*
* This is a shortcut method for {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object, boolean)
* dispatchMessage(delay, sender, receiver, msg, extraInfo, false)}
*
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param extraInfo an optional object */
public void dispatchMessage (float delay, Telegraph sender, Telegraph receiver, int msg, Object extraInfo) {
dispatchMessage(delay, sender, receiver, msg, extraInfo, false);
}
/** Given a message, a receiver, a sender and any time delay, this method routes the message to the correct agents (if no delay)
* or stores in the message queue to be dispatched at the correct time.
* @param delay the delay in seconds
* @param sender the sender of the telegram
* @param receiver the receiver of the telegram; if it's {@code null} the telegram is broadcasted to all the receivers
* registered for the specified message code
* @param msg the message code
* @param extraInfo an optional object
* @param needsReturnReceipt whether the return receipt is needed or not
* @throws IllegalArgumentException if the sender is {@code null} and the return receipt is needed */
public void dispatchMessage (float delay, Telegraph sender, Telegraph receiver, int msg, Object extraInfo,
boolean needsReturnReceipt) {
if (sender == null && needsReturnReceipt)
throw new IllegalArgumentException("Sender cannot be null when a return receipt is needed");
// Get a telegram from the pool
Telegram telegram = pool.obtain();
telegram.sender = sender;
telegram.receiver = receiver;
telegram.message = msg;
telegram.extraInfo = extraInfo;
telegram.returnReceiptStatus = needsReturnReceipt ? Telegram.RETURN_RECEIPT_NEEDED : Telegram.RETURN_RECEIPT_UNNEEDED;
// If there is no delay, route telegram immediately
if (delay <= 0.0f) {
// TODO: should we set the timestamp here?
// telegram.setTimestamp(GdxAI.getTimepiece().getTime());
if (debugEnabled) {
float currentTime = GdxAI.getTimepiece().getTime();
GdxAI.getLogger().info(
LOG_TAG,
"Instant telegram dispatched at time: " + currentTime + " by " + sender + " for " + receiver
+ ". Message code is " + msg);
}
// Send the telegram to the recipient
discharge(telegram);
} else {
float currentTime = GdxAI.getTimepiece().getTime();
// Set the timestamp for the delayed telegram
telegram.setTimestamp(currentTime + delay);
// Put the telegram in the queue
boolean added = queue.add(telegram);
// Return it to the pool if has been rejected
if (!added) pool.free(telegram);
if (debugEnabled) {
if (added)
GdxAI.getLogger().info(
LOG_TAG,
"Delayed telegram from " + sender + " for " + receiver + " recorded at time " + currentTime
+ ". Message code is " + msg);
else
GdxAI.getLogger().info(LOG_TAG,
"Delayed telegram from " + sender + " for " + receiver + " rejected by the queue. Message code is " + msg);
}
}
}
/** Dispatches any delayed telegrams with a timestamp that has expired. Dispatched telegrams are removed from the queue.
*
* This method must be called regularly from inside the main game loop to facilitate the correct and timely dispatch of any
* delayed messages. Notice that the message dispatcher internally calls {@link Timepiece#getTime()
* GdxAI.getTimepiece().getTime()} to get the current AI time and properly dispatch delayed messages. This means that
*
* - if you forget to {@link Timepiece#update(float) update the timepiece} the delayed messages won't be dispatched.
* - ideally the timepiece should be updated before the message dispatcher.
*
*/
public void update () {
float currentTime = GdxAI.getTimepiece().getTime();
// Peek at the queue to see if any telegrams need dispatching.
// Remove all telegrams from the front of the queue that have gone
// past their time stamp.
Telegram telegram;
while ((telegram = queue.peek()) != null) {
// Exit loop if the telegram is in the future
if (telegram.getTimestamp() > currentTime) break;
if (debugEnabled) {
GdxAI.getLogger().info(LOG_TAG,
"Queued telegram ready for dispatch: Sent to " + telegram.receiver + ". Message code is " + telegram.message);
}
// Send the telegram to the recipient
discharge(telegram);
// Remove it from the queue
queue.poll();
}
}
/** Scans the queue and passes pending messages to the given callback in any particular order.
*
* Typically this method is used to save (serialize) pending messages and restore (deserialize and schedule) them back on game
* loading.
* @param callback The callback used to report pending messages individually. **/
public void scanQueue (PendingMessageCallback callback) {
float currentTime = GdxAI.getTimepiece().getTime();
int queueSize = queue.size();
for (int i = 0; i < queueSize; i++) {
Telegram telegram = queue.get(i);
callback.report(telegram.getTimestamp() - currentTime, telegram.sender, telegram.receiver, telegram.message,
telegram.extraInfo, telegram.returnReceiptStatus);
}
}
/** This method is used by {@link #dispatchMessage(float, Telegraph, Telegraph, int, Object) dispatchMessage} for immediate
* telegrams and {@link #update(float) update} for delayed telegrams. It first calls the message handling method of the
* receiving agents with the specified telegram then returns the telegram to the pool.
* @param telegram the telegram to discharge */
private void discharge (Telegram telegram) {
if (telegram.receiver != null) {
// Dispatch the telegram to the receiver specified by the telegram itself
if (!telegram.receiver.handleMessage(telegram)) {
// Telegram could not be handled
if (debugEnabled) GdxAI.getLogger().info(LOG_TAG, "Message " + telegram.message + " not handled");
}
} else {
// Dispatch the telegram to all the registered receivers
int handledCount = 0;
Array listeners = msgListeners.get(telegram.message);
if (listeners != null) {
for (int i = 0; i < listeners.size; i++) {
if (listeners.get(i).handleMessage(telegram)) {
handledCount++;
}
}
}
// Telegram could not be handled
if (debugEnabled && handledCount == 0) GdxAI.getLogger().info(LOG_TAG, "Message " + telegram.message + " not handled");
}
if (telegram.returnReceiptStatus == Telegram.RETURN_RECEIPT_NEEDED) {
// Use this telegram to send the return receipt
telegram.receiver = telegram.sender;
telegram.sender = this;
telegram.returnReceiptStatus = Telegram.RETURN_RECEIPT_SENT;
discharge(telegram);
} else {
// Release the telegram to the pool
pool.free(telegram);
}
}
/** Handles the telegram just received. This method always returns {@code false} since usually the message dispatcher never
* receives telegrams. Actually, the message dispatcher implements {@link Telegraph} just because it can send return receipts.
* @param msg The telegram
* @return always {@code false}. */
@Override
public boolean handleMessage (Telegram msg) {
return false;
}
/** A {@code PendingMessageCallback} is used by the {@link MessageDispatcher#scanQueue(PendingMessageCallback) scanQueue} method
* of the {@link MessageDispatcher} to report its pending messages individually.
*
* @author davebaol */
public interface PendingMessageCallback {
/** Reports a pending message.
* @param delay The remaining delay in seconds
* @param sender The message sender
* @param receiver The message receiver
* @param message The message code
* @param extraInfo Any additional information that may accompany the message
* @param returnReceiptStatus The return receipt status of the message */
public void report (float delay, Telegraph sender, Telegraph receiver, int message, Object extraInfo,
int returnReceiptStatus);
}
}