Message queuing and processing

To process messages, our messaging system should maintain a queue of incoming message objects so that we can process them in the order they were broadcast:

private Queue<Message> _messageQueue = new Queue<Message>();

public bool QueueMessage(Message msg) {
if (!_listenerDict.ContainsKey(msg.type)) {
return false;
}
_messageQueue.Enqueue(msg);
return true;
}

The QueueMessage() method simply checks whether the given message type is present in our dictionary before adding it to the queue. This effectively tests whether or not an object actually cares to listen to the message before we queue it to be processed later. We have introduced a new private field, _messageQueue, for this purpose.

Next, we'll add a definition for Update(). This callback will be called regularly by the Unity Engine. Its purpose is to iterate through the current contents of the message queue, one message a time; verify whether or not too much time has passed since we began processing; and if not, pass them along to the next stage in the process:

private const int _maxQueueProcessingTime = 16667;
private System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

void Update() {
timer.Start();
while (_messageQueue.Count > 0) {
if (_maxQueueProcessingTime > 0.0f) {
if (timer.Elapsed.Milliseconds > _maxQueueProcessingTime) {
timer.Stop();
return;
}
}

Message msg = _messageQueue.Dequeue();
if (!TriggerMessage(msg)) {
Debug.Log("Error when processing message: " + msg.type);
}
}
}

The time-based safeguard is in place to make sure that it does not exceed a processing time limit threshold. This prevents the messaging system from freezing our game if too many messages get pushed through the system too quickly. If the total time limit is exceeded, then all message processing will stop, leaving any remaining messages to be processed during the next frame.

Note that we use the full namespace when creating the Stopwatch object. We could have added using System.Diagnostics, but this would lead to a namespace conflict between System.Diagnostics.Debug and UnityEngine.Debug. Omitting it allows us to continue to call Unity's debug logger with Debug.Log(), without having to explicitly call UnityEngine.Debug.Log() each time.

Lastly, we will need to define the TriggerMessage() method, which distributes messages to listeners:

public bool TriggerMessage(Message msg) {
string msgType = msg.type;
if (!_listenerDict.ContainsKey(msgType)) {
Debug.Log("MessagingSystem: Message "" + msgType + "" has no listeners!");
return false; // no listeners for message so ignore it
}

List<MessageHandlerDelegate> listenerList = _listenerDict[msgType];

for(int i = 0; i < listenerList.Count; ++i) {
if (listenerList[i](msg)) {
return true; // message consumed by the delegate
}
return true;
}
}

The preceding method is the main workhorse behind the messaging system. The TriggerEvent() method's purpose is to obtain the list of listeners for the given message type and give each of them an opportunity to process it. If one of the delegates returns true, then the processing of the current message ceases and the method exits, allowing the Update() method to process the next message.

Normally, we would want to use QueueEvent() to broadcast messages, but we also provide direct access to TriggerEvent() as an alternative. Using TriggerEvent() directly allows message senders to force their messages to be processed immediately without waiting for the next Update() event. This bypasses the throttling mechanism, which might be necessary for messages that need to be sent during critical moments of gameplay, where waiting an additional frame might result in strange-looking behavior.

For example, if we intend for two objects to be destroyed and create a Particle Effect the moment they collide with one another, and this work is handled by another subsystem (hence an event needs to be sent for it), then we would want to send the message via TriggerEvent() to prevent the objects from continuing to exist for one frame before the event is handled. Conversely, if we wanted to do something less frame-critical, such as create a pop-up message when the player walks into a new area, we could safely use a QueueEvent() call to handle it.

Try to avoid habitually using TriggerEvent() for all events, as we could end up handling too many calls simultaneously in the same frame, causing a sudden drop in frame rate. Decide which events are frame-critical, and which are not, and use the QueueEvent() and TriggerEvent() methods appropriately.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.28.50