Advanced CSharp Messenger

Author: Ilya Suzdalnitski

Description
This is an advanced version of a messaging system for C#. It will automatically clean up its event table after a new level has been loaded. This will prevent the programmer from accidentally invoking destroyed methods and thus will help prevent many MissingReferenceExceptions. This messaging system is based on Rod Hyde's CSharpMessenger and Magnus Wolffelt's CSharpMessenger Extended.

Foreword
Upon introduction of a messaging system into our project (CSharpMessenger Extended) we started facing very strange bugs. Unity3d would throw MissingReferenceExceptions every time a message was broadcasted. It would say that the class, where the message handler was declared in, was destroyed. The problem came out of nowhere and there was not a reasonable explanation behind it. However placing the message handler code within try-catch blocks solved the problem. We understood that it was not a good solution to have hundreds of try-catch blocks in our code. It took us some time to finally figure out where was the problem.

Cause behind the MissingReferenceException and solution
It turned out, that the MissingReferenceException bug appeared, when a new level was loaded (or current one was reloaded). For example, we have a message "start game", declared as follows:

At first glance, there's no problem at all, but after the level has been reloaded, Unity3d will throw an exception, saying that MainMenu has been destroyed. However there's no code that would EVER destroy the MainMenu script.

What actually happened is:
 * 1) We added a "start game" message listener to our Messenger.
 * 2) StartGameButtonPressed was called which in turn broadcasted the "start game" message.
 * 3) We reloaded the level with Application.LoadLevel.
 * 4) Step 1 repeated.
 * 5) Step 2 repeated.

Here's how eventTable of the messenger looks like at corresponding steps:
 * At step 1: { "start game", mainMenu1- > StartGame; }
 * At step 4: { "start game", mainMenu1- > StartGame; } { "start game", mainMenu2- > StartGame; }

So at step 4 we have two message handler for the same "start game" message - the first one is for the destroyed MainMenu object (got destroyed when reloaded a level), and the second one it for the current valid MainMenu object. It turns out that when we broadcast the "start game" message after reloading the level, the messenger invokes both - the destroyed and the valid message handlers. This is where the MissingReferenceException came from.

So the solution is obvious - clear the eventTable after unloading a level. There's nothing else the programmer has to do on his side to clean up the table, it's being done automatically.

= The Messenger = We're happy to present you an advanced version of C# messaging system.

Cleaning up the messenger
The messenger cleans up its eventTable automatically when a new level loads. This will ensure that the eventTable of the messenger gets cleaned up and will save us from unexpected MissingReferenceExceptions. In case you want to clean up manager's eventTable manually, there's such an option by calling Messenger.Cleanup;

Permanent messages
If you want a certain message to survive the Cleanup, mark it with Messenger.MarkAsPermanent(string). This may be required if a certain class responds to messages broadcasted from across different levels.

Log all messages
For debugging purposes, you can set the shouldLogAllMessages flag in Messenger to true. This will log all calls to the Messenger.

Transition from other messengers
To quickly change all calls to messaging system from older CSharpMessenger's to the advanced, do the following steps:
 * 1) In MonoDevelop go to Search => Replace in files
 * 2) In Find field enter: Messenger<([^<>]+)>.([A-Za-z0-9_]+)
 * 3) In Replace field enter: Messenger.$2<$1>
 * 4) Select scope: Whole solution.
 * 5) Check the Regex search check box.
 * 6) Press the Replace button

Code
There're two files required for the messenger to work - Callback.cs and Messenger.cs.

Messenger.cs
=== Questions (apologies for putting this on the page directly, there doesn't seem to be a discussion tab)

I'm using CSharp Messenger Extended and am wondering if I ought to switch to this. If I don't want my event listeners cleared automatically when the scene loads (many listeners are on DontDestroyOnLoad objects) then is there any advantage to using Advanced instead of Extended? Currently I just always put listener registration in Awake and OnDestroy methods and that seems to work correctly.