zoukankan      html  css  js  c++  java
  • Advanced CSharp Messenger

    http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger

    Author: Ilya Suzdalnitski

    Contents

     [hide

    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'sCSharpMessenger 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:

    public class MainMenu : MonoBehaviour {	
    	void Start ()
    	{
    		Messenger.AddListener("start game", StartGame);
    	}
     
    	void StartGame()
    	{
    		Debug.Log("StartGame called in" + gameObject);  //This is the line that would throw an exception
    	}
     
    	void StartGameButtonPressed()
    	{
    		Messenger.Broadcast("start game");
    	}
    }

    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.

    Usage

    Event listener

    void OnPropCollected( PropType propType ) {
    	if (propType == PropType.Life)
    		livesAmount++;
    }

    Registering an event listener

    void Start() {
    	Messenger.AddListener< Prop >( "prop collected", OnPropCollected );
    }

    Unregistering an event listener

    	Messenger.RemoveListener< Prop > ( "prop collected", OnPropCollected );

    Broadcasting an event

    public void OnTriggerEnter(Collider _collider) 
    {
    	Messenger.Broadcast< PropType > ( "prop collected", _collider.gameObject.GetComponent<Prop>().propType );
    }

    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.

    Misc

    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.

    Callback.cs

    public delegate void Callback();
    public delegate void Callback<T>(T arg1);
    public delegate void Callback<T, U>(T arg1, U arg2);
    public delegate void Callback<T, U, V>(T arg1, U arg2, V arg3);

    Messenger.cs

    /*
     * Advanced C# messenger by Ilya Suzdalnitski. V1.0
     * 
     * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
     * 
     * Features:
     	* Prevents a MissingReferenceException because of a reference to a destroyed message handler.
     	* Option to log all messages
     	* Extensive error detection, preventing silent bugs
     * 
     * Usage examples:
     	1. Messenger.AddListener<GameObject>("prop collected", PropCollected);
     	   Messenger.Broadcast<GameObject>("prop collected", prop);
     	2. Messenger.AddListener<float>("speed changed", SpeedChanged);
     	   Messenger.Broadcast<float>("speed changed", 0.5f);
     * 
     * Messenger cleans up its evenTable automatically upon loading of a new level.
     * 
     * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string)
     * 
     */
     
    //#define LOG_ALL_MESSAGES
    //#define LOG_ADD_LISTENER
    //#define LOG_BROADCAST_MESSAGE
    #define REQUIRE_LISTENER
     
    using System;
    using System.Collections.Generic;
    using UnityEngine;
     
    static internal class Messenger {
    	#region Internal variables
     
    	//Disable the unused variable warning
    #pragma warning disable 0414
    	//Ensures that the MessengerHelper will be created automatically upon start of the game.
    	static private MessengerHelper messengerHelper = ( new GameObject("MessengerHelper") ).AddComponent< MessengerHelper >();
    #pragma warning restore 0414
     
    	static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();
     
    	//Message handlers that should never be removed, regardless of calling Cleanup
    	static public List< string > permanentMessages = new List< string > ();
    	#endregion
    	#region Helper methods
    	//Marks a certain message as permanent.
    	static public void MarkAsPermanent(string eventType) {
    #if LOG_ALL_MESSAGES
    		Debug.Log("Messenger MarkAsPermanent 	"" + eventType + """);
    #endif
     
    		permanentMessages.Add( eventType );
    	}
     
     
    	static public void Cleanup()
    	{
    #if LOG_ALL_MESSAGES
    		Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
    #endif
     
    		List< string > messagesToRemove = new List<string>();
     
    		foreach (KeyValuePair<string, Delegate> pair in eventTable) {
    			bool wasFound = false;
     
    			foreach (string message in permanentMessages) {
    				if (pair.Key == message) {
    					wasFound = true;
    					break;
    				}
    			}
     
    			if (!wasFound)
    				messagesToRemove.Add( pair.Key );
    		}
     
    		foreach (string message in messagesToRemove) {
    			eventTable.Remove( message );
    		}
    	}
     
    	static public void PrintEventTable()
    	{
    		Debug.Log("			=== MESSENGER PrintEventTable ===");
     
    		foreach (KeyValuePair<string, Delegate> pair in eventTable) {
    			Debug.Log("			" + pair.Key + "		" + pair.Value);
    		}
     
    		Debug.Log("
    ");
    	}
    	#endregion
     
    	#region Message logging and exception throwing
        static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded) {
    #if LOG_ALL_MESSAGES || LOG_ADD_LISTENER
    		Debug.Log("MESSENGER OnListenerAdding 	"" + eventType + ""	{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
    #endif
     
            if (!eventTable.ContainsKey(eventType)) {
                eventTable.Add(eventType, null );
            }
     
            Delegate d = eventTable[eventType];
            if (d != null && d.GetType() != listenerBeingAdded.GetType()) {
                throw new ListenerException(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType().Name, listenerBeingAdded.GetType().Name));
            }
        }
     
        static public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved) {
    #if LOG_ALL_MESSAGES
    		Debug.Log("MESSENGER OnListenerRemoving 	"" + eventType + ""	{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
    #endif
     
            if (eventTable.ContainsKey(eventType)) {
                Delegate d = eventTable[eventType];
     
                if (d == null) {
                    throw new ListenerException(string.Format("Attempting to remove listener with for event type "{0}" but current listener is null.", eventType));
                } else if (d.GetType() != listenerBeingRemoved.GetType()) {
                    throw new ListenerException(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name));
                }
            } else {
                throw new ListenerException(string.Format("Attempting to remove listener for type "{0}" but Messenger doesn't know about this event type.", eventType));
            }
        }
     
        static public void OnListenerRemoved(string eventType) {
            if (eventTable[eventType] == null) {
                eventTable.Remove(eventType);
            }
        }
     
        static public void OnBroadcasting(string eventType) {
    #if REQUIRE_LISTENER
            if (!eventTable.ContainsKey(eventType)) {
                throw new BroadcastException(string.Format("Broadcasting message "{0}" but no listener found. Try marking the message with Messenger.MarkAsPermanent.", eventType));
            }
    #endif
        }
     
        static public BroadcastException CreateBroadcastSignatureException(string eventType) {
            return new BroadcastException(string.Format("Broadcasting message "{0}" but listeners have a different signature than the broadcaster.", eventType));
        }
     
        public class BroadcastException : Exception {
            public BroadcastException(string msg)
                : base(msg) {
            }
        }
     
        public class ListenerException : Exception {
            public ListenerException(string msg)
                : base(msg) {
            }
        }
    	#endregion
     
    	#region AddListener
    	//No parameters
        static public void AddListener(string eventType, Callback handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback)eventTable[eventType] + handler;
        }
     
    	//Single parameter
    	static public void AddListener<T>(string eventType, Callback<T> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler;
        }
     
    	//Two parameters
    	static public void AddListener<T, U>(string eventType, Callback<T, U> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T, U>)eventTable[eventType] + handler;
        }
     
    	//Three parameters
    	static public void AddListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] + handler;
        }
    	#endregion
     
    	#region RemoveListener
    	//No parameters
        static public void RemoveListener(string eventType, Callback handler) {
            OnListenerRemoving(eventType, handler);   
            eventTable[eventType] = (Callback)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
    	//Single parameter
    	static public void RemoveListener<T>(string eventType, Callback<T> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
    	//Two parameters
    	static public void RemoveListener<T, U>(string eventType, Callback<T, U> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T, U>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
    	//Three parameters
    	static public void RemoveListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
    	#endregion
     
    	#region Broadcast
    	//No parameters
        static public void Broadcast(string eventType) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    		Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback callback = d as Callback;
     
                if (callback != null) {
                    callback();
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
     
    	//Single parameter
        static public void Broadcast<T>(string eventType, T arg1) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    		Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T> callback = d as Callback<T>;
     
                if (callback != null) {
                    callback(arg1);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
    	}
     
    	//Two parameters
        static public void Broadcast<T, U>(string eventType, T arg1, U arg2) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    		Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T, U> callback = d as Callback<T, U>;
     
                if (callback != null) {
                    callback(arg1, arg2);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
     
    	//Three parameters
        static public void Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    		Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T, U, V> callback = d as Callback<T, U, V>;
     
                if (callback != null) {
                    callback(arg1, arg2, arg3);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
    	#endregion
    }
     
    //This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level.
    public sealed class MessengerHelper : MonoBehaviour {
    	void Awake ()
    	{
    		DontDestroyOnLoad(gameObject);	
    	}
     
    	//Clean up eventTable every time a new level loads.
    	public void OnLevelWasLoaded(int unused) {
    		Messenger.Cleanup();
    	}
    }
  • 相关阅读:
    ADO.Net——增、删、改、查
    面向对象——类库五大原则
    面向对象——设计模式和委托
    JS 函数 内置方法和对象
    js 字符串、数组类型 及方法
    复习一下 Python三元运算
    复习一下 列表
    前端 2 CSS 选择器
    前端 1 HTML
    39
  • 原文地址:https://www.cnblogs.com/mimime/p/6240239.html
Copyright © 2011-2022 走看看