Search Unity

Advanced C# Messenger

Discussion in 'Assets and Asset Store' started by ilya_ca, Nov 19, 2011.

  1. ilya_ca

    ilya_ca

    Joined:
    Nov 19, 2011
    Posts:
    274
    Hello everyone,
    Check out my improved version of messaging system for c#: Advanced C# Messenger

    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.

    It's not a big tool, but I hope many will find it useful.
     
    Last edited: Dec 7, 2011
    eclipse130300 and boysenberry like this.
  2. pixelshaded

    pixelshaded

    Joined:
    Oct 23, 2010
    Posts:
    40
    ty for sharing
     
  3. blacksnow

    blacksnow

    Joined:
    Oct 24, 2011
    Posts:
    3
    check that link but .. so sad

    "Unify Community has been hacked by spammers and is temporarily offline while we clean up the mess and make things secure. This may take quite some time. Please bear with us.

    -NCarter (2012-01-09)"

    C# Messenger . any alternative link ?
     
  4. blacksnow

    blacksnow

    Joined:
    Oct 24, 2011
    Posts:
    3
    oh google-ing and found here
     
  5. joeyxiong

    joeyxiong

    Joined:
    Dec 13, 2011
    Posts:
    10
    thank you for sharing, but all of the messager system aren't the perfect solution, it's not gameobject specifically, not the same as built-in SendMessage system.
     
  6. deskchicken

    deskchicken

    Joined:
    Oct 10, 2008
    Posts:
    9
    Thanks for sharing! I've been using this quite a bit in recent projects and it works well. Very simple.
     
  7. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Great work on improving the extended CSharp messenger script. I'm liking this so far, and it can be extended for additional functionality quite easily. For instance, I have added an optional Broadcast method that takes in a delayTime argument (in addition to anything else you normally provide). This can be used to delay the broadcast of a message by a specified time. For instance:

    Code (csharp):
    1. void CookInMicrowave(){ StartCoroutine(MessengerManager.Broadcast<string>("Finished Cooking", "Pretzel", 42f)); }
    Of course, this is a quickly contrived example. You probably would not want to set an event to go off in 42 seconds with no way to interrupt it, but it serves to prove the concept.

    Here are the methods. Please let me know if anyone has trouble with them or sees a problem with this code, as I haven't thoroughly tested it. Enjoy!

    Code (csharp):
    1.  
    2.     //No Generic parameters
    3.     static public IEnumerator Broadcast(string eventType, float delayTime)
    4.     {
    5.         #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    6.         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
    7.         #endif
    8.        
    9.         OnBroadcasting(eventType);
    10.  
    11.         Delegate d;
    12.         if (eventTable.TryGetValue(eventType, out d))
    13.         {
    14.             Callback callback = d as Callback;
    15.  
    16.             if (callback != null)
    17.             {
    18.                 yield return new WaitForSeconds(delayTime);
    19.                 callback();
    20.             }
    21.             else { throw CreateBroadcastSignatureException(eventType); }
    22.         }
    23.     }
    24.    
    25.     //Single Generic parameter
    26.     static public IEnumerator Broadcast<T>(string eventType, T arg1, float delayTime)
    27.     {
    28.         #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    29.         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
    30.         #endif
    31.         OnBroadcasting(eventType);
    32.  
    33.         Delegate d;
    34.         if (eventTable.TryGetValue(eventType, out d))
    35.         {
    36.            
    37.             Callback<T> callback = d as Callback<T>;
    38.  
    39.             if (callback != null)
    40.             {
    41.                 yield return new WaitForSeconds(delayTime);
    42.                 callback(arg1);
    43.             }
    44.             else { throw CreateBroadcastSignatureException(eventType); }
    45.         }
    46.     }
    47.    
    48.     //Two Generic parameters
    49.     static public IEnumerator Broadcast<T, U>(string eventType, T arg1, U arg2, float delayTime)
    50.     {
    51.         #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    52.         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
    53.         #endif
    54.         OnBroadcasting(eventType);
    55.  
    56.         Delegate d;
    57.         if (eventTable.TryGetValue(eventType, out d))
    58.         {
    59.             Callback<T, U> callback = d as Callback<T, U>;
    60.  
    61.             if (callback != null)
    62.             {
    63.                 yield return new WaitForSeconds(delayTime);
    64.                 callback(arg1, arg2);
    65.             }
    66.             else { throw CreateBroadcastSignatureException(eventType); }
    67.         }
    68.     }
    69.    
    70.    
    71.     //Three Generic parameters
    72.     static public IEnumerator Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3, float delayTime)
    73.     {
    74.         #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
    75.         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
    76.         #endif
    77.         OnBroadcasting(eventType);
    78.  
    79.         Delegate d;
    80.         if (eventTable.TryGetValue(eventType, out d))
    81.         {
    82.             Callback<T, U, V> callback = d as Callback<T, U, V>;
    83.  
    84.             if (callback != null)
    85.             {
    86.                 yield return new WaitForSeconds(delayTime);
    87.                 callback(arg1, arg2, arg3);
    88.             }
    89.             else { throw CreateBroadcastSignatureException(eventType); }
    90.         }
    91.     }
    As you can hopefully see, it would be very easy to apply this same logic to new methods that utilize WaitForFixedUpdate or WaitForEndOfFrame!
     
  8. user150221

    user150221

    Joined:
    Nov 10, 2012
    Posts:
    25
    Cool! Thanks.

    I'll throw in my two cents too. Cleanup() can be improved a little. For one, using a HashSet:

    Code (csharp):
    1. // Message handlers that should never be removed, regardless of calling Cleanup
    2.     static public HashSet<string> permanentMessages = new HashSet<string>();
    will work better because List<> iteration or List<>.Contains() are at least O(n) operations. HashSet<>.Contains() on the other hand is O(1).

    Thus, Cleanup() can be changed to:

    Code (csharp):
    1.     static public void Cleanup()
    2.     {
    3.         #region Debug
    4.         #if LOG_ALL_MESSAGES
    5.                 Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
    6.         #endif
    7.         #endregion
    8.        
    9.         foreach (string message in eventTable
    10.             .Where(pair => !permanentMessages.Contains(pair.Key))
    11.             .Select(pair => pair.Key)
    12.             .ToList())
    13.         {
    14.             eventTable.Remove(message);
    15.         }
    16.     }

    Edit: Fixed a booboo. (List<> still needed since you can't change the thing you're iterating over)


    And now checking if a Key exists in permanentMessages is always an O(1) operation.

    Or, if you're not a fan of LINQ:

    Code (csharp):
    1.     static public void Cleanup()
    2.     {
    3.         #region Debug
    4.         #if LOG_ALL_MESSAGES
    5.                 Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
    6.         #endif
    7.         #endregion
    8.        
    9.         List< string > messagesToRemove = new List<string>();
    10.  
    11.         foreach (KeyValuePair<string, Delegate> pair in eventTable) {
    12.             if (!permanentMessages.Contains( pair.Key ))
    13.                 messagesToRemove.Add( pair.Key );
    14.         }
    15.  
    16.         foreach (string message in messagesToRemove) {
    17.             eventTable.Remove( message );
    18.         }
    19.     }
     
    Last edited: Jul 14, 2013
  9. Deleted User

    Deleted User

    Guest

    Looks neat. I see a couple little things to change, though:

    - All the Callback delegates can just be replaced with the standard .NET Action delegate types. They're exactly the same thing.
    - You're using OnDisable to clean up your table for level loading. While that may work, it also will clean things up if you just disable the object. The "correct" way to handle level load events is with the OnLevelWasLoaded message: http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.OnLevelWasLoaded.html

    Still looks pretty useful as a central dispatch system. Thanks for sharing!
     
  10. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Nice input!
     
  11. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Okay, I've modified the original with (what I believe to be) improvements. The biggest change is the transfer from a Dictionary<string, Delegate> to a Delegate[] array. I figured I (and most other programmers) will have all events created before runtime, and so the max number of events is a known quantity at start. Using this knowledge, an array of type Delegate can be created that can hold every single possible event. Each enum then maps perfectly to an index of this array. We just have to convert the enum to an int, then get the delegate from the array using this index. We don't ever have to worry about the delegate not being there, as all delegates are there from the start (set to null). It's just a matter of checking if a delegate at a specific index is null, and if not, if it matches the signature of the listener delegate (in the case of AddListener). I don't think you can ask for a faster system.

    Also, even though it's kind of annoying to have to add new enums to the list when you want a new event, when using the Broadcasting/AddListener/etc. methods, if you type GlobalEvent., you will get a list of all available events, which is incredibly useful. Just make sure you don't define your enum values manually, as then adding/removing events from the enum becomes a real pain. Also, you don't have to worry about the enum changing size. In the static class's constructor, I automatically get the number of events in the enum using GlobalEvent.GetNames().Length.

    Now, this will most likely result in more memory. I have tried populating the array with varying amounts of delegates, and the memory size doesn't seem to increase (from 10 to 100 delegates). I have also tried adding 1-5 listeners to each of these delegates, and again the memory doesn't seem to change much. I don't know if I need to use a bigger sample, or I'm just profiling wrong (I'm using both the Pro Profiler and Profiler.usedHeapSize in a standalone build). If someone else would like to test the memory usage that'd be great, as I'm a newb at that sort of thing (well, that goes for most programming stuff actually ;)).

    I also changed the Cleanup code to run be called from OnLevelWasLoaded instead of OnDisable, and got rid of the callback delegates to use Action, Action<T>, etc. instead (credit to nickgravelyn). Finally, I implemented four new Broadcast methods for delayed broadcast functionality. These methods take in a YieldInstruction[] array as the last paramater, so you can pass different instructions (pass new WaitForSeconds(2f) or new WaitForFixedUpdate() for instance). Because it's an array you can even pass multiple yield instructions to chain some commands together.

    You can even send in a started coroutine as a paramater, like this:

    Code (csharp):
    1. StartCoroutine(GlobalEventManager.Broadcast(GlobalEvent.AddCheese, new YieldInstruction[]{new WaitForSeconds(2f),
    2.             StartCoroutine(ExecuteBeforeEventIsDispatched())}));
    Though I wouldn't actually advise using this, as

    1) The Coroutine ExecuteBeforeEventIsDispatched is started immediately, which is probably not what you want.

    2) Once the Broadcast method hits its first yield, the ExecuteBeforeEventIsDispatched method will resume, even if it is the second YieldInstruction.

    3) It's pointless. Even if you put the StartCoroutine as the first member of the YieldInstruction array, which will probably give you the kind of functionality you're looking for, why not just call the Coroutine from within the script that's broadcasting the event. By the same token, the other yield instructions are pointless as well, as you could just use them from within the broadcasting script.

    Still, it's a neat feature and maybe someone can think of a better use for it (please let me know if you do!).

    You'll notice I changed the names of quite a bit of stuff out of personal preference. This is still just a modified version of the script posting in the original post. Also, I'm including a file with the GlobalEvent enum, populated with 100 values (most of which are just nonsense), for testing purposes. You can use it or not, but if you don't, make sure the one you use is called GlobalEvent (or you can change the names of everything like I did :)).

    Finally, I moved (and renamed) the Helper MonoBehaviour class to its own file so it can be attached to a gameObject. The GlobalEventManager static class now has a constructor that will search for the GlobalEventManagerMonoBehaviour class in the scene, and if it doesn't find it, it will create a gameObject with it attached (as before). This way you can add some editable variables to the monobehaviour, and retrieve these values in the static class's constructor.

    I'm just providing the files in a zip to avoid a huge post with code. Here it is.

    View attachment $GlobalEventManager.zip
     
  12. user150221

    user150221

    Joined:
    Nov 10, 2012
    Posts:
    25
    Neat. I thought of using Enums last night, for code completion purposes. Though having one monolithic enum seems kind of unattractive to me. I think "global" is a bit of a misnomer too, since not every single thing listens to all Broadcasts. Instead it seems more coder-friendly to keep it as a dictionary, and instead take MyEnum.SomethingHappened and turn it into a string "MyEnum.SomethingHappened" for the dictionary key. That way you can categorize your enums into whatever groups you want and get code completion.

    Then you also don't need to pre-fill a container with all of the enum values either.

    I haven't thought too much about it yet, it could be a worse idea. Just putting the idea out there though. :)
     
  13. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Good points. I used global to indicate that any script could broadcast/listen to a the event with no restrictions, so for me it makes sense, but of course everyone has their own preferences. As for converting the enums to a string and using a dictionary, I'm sure it would be fine, as dictionary lookups are pretty fast from what I can gather. I was just going for the fastest solution I could think of for the fun of it :D. A dictionary may be preferable even for my method, since new space for delegates are added only when needed. I'm not really sure of the memory implications for my array method.

    Whatever you decide to do, just make sure you settle on what you want to pass into a broadcast/add listener method (in terms of the even signature, is it a string or an enum or something else?). That way you can change how the events are stored/accessed in the static event manager/messenger class with no fear of effecting calling code.
     
  14. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I've actually gone ahead and refined things a bit more to create a super flexible system. Now, the primary manager class is a generic class called EventManager<TEventType>, where TEventType is an enum. Of course, .Net doesn't support System.Enum as a generic constraint, so instead you have to use this format:

    Code (csharp):
    1. where TEventType : struct, IComparable, IConvertible, IFormattable
    The struct part indicates the generic type argument must be a value type (which Enum is), and implement the three interfaces (which Enum does). Now, this is not a perfect method, as it means you could pass other non-Enum entities as the type paramater, but for our purposes it should be fine because:

    1) The likelihood of you having a value type that also implements those three interfaces is very low.
    Edit: Actually, there are quite a bit of value types that implement this interface (int, Int16, etc.), but still, this
    shouldn't be a problem.

    2) We do not have clients who will use our custom class, so we don't have to worry about them being confused as to what type they can pass in. At worst you will have to explain to your team that the EventManager<TEventType> takes in an enum, and an enum only. No big deal.

    3) We can apply a runtime check to make sure the type is actually an enum in the constructor for the class, like so:
    Code (csharp):
    1. if(!typeof(TEventType).IsEnum)
    2.     throw new InvalidGenericTypeException("TEventType for class 'Event Manager' must be an enum!");
    Not an ideal solution, but it'll do just fine.

    To use this class you would just do something like this:

    Code (csharp):
    1. EventManager<GlobalEvent> globalEventManager = new EventManager<GlobalEvent>();
    2. globalEventManager.AddListener<float>(GlobalEvent.EnemyKilled, OnEnemyKilled);
    3.  
    4. private void OnEnemyKilled(float cashLooted)
    5. {
    6.     cash += cashLooted;
    7. }
    You will notice that the event manager is no longer a static class, and I also decided against implementing it as a singleton. I decided it wouldn't make sense to have every event manager (of which there are infinite possible types with the generic method) exist for the lifetime of the game (so static class was ruled out). A singleton could have been used, but I didn't like the idea of having to null out the singleton Instance when I got to a level that didn't require a specific manager. Perhaps the singleton could be created as a monobehaviour, but that would produce a great deal of game objects in the scene when using a lot of managers.

    Another benefit of using a non-singleton pattern is if you want to have multiple event managers of the same Enum type. For example, you might have an event type called "EnemyOnlyEvents," of which there are 5 different events in the enum list. But perhaps you have one group of enemies that will communicate only among themselves, and then another group separate from the first who's members also only communicate with themselves. It wouldn't make sense in this case to have one event manager for both groups, as they'd each be receiving communication they didn't need and you would then need to filter out messages based on some criteria. With a non singleton pattern, you can just create two event managers of the same type for each group.

    With that said, I also recognize that you may want a global event manager (not necessarily of type 'GlobalEvent') that anyone can access, so I added a separate Singleton that manages these 'single instance' event managers. This singleton is called the EventManagerRepository, and you can access a specific event manager from it like so:

    Code (csharp):
    1. EventManager<SomeEventType> manager = EventManagerRepository.Instance.GetSingleInstanceEventManager<SomeEventType>()
    The EventManagerRepository has a Dictionary<System.Type, EventManager> which managers are stored in. EventManager is a non-generic abstract class from which every generic event manager class inherits from (this is necessary to allow the repository to store the event managers). Internally, the GetSingleInstanceEventManager method looks like this:

    Code (csharp):
    1. public EventManager<TEventType> GetSingleInstanceEventManager<TEventType>() where TEventType : struct, IComparable, IConvertible, IFormattable
    2. {
    3.     EventManager eventManager;
    4.        
    5.     eventManagers.TryGetValue(typeof(TEventType), out eventManager);
    6.     if(eventManager == null)
    7.     {
    8.         UnityEngine.Debug.Log("EventManger(" + typeof(TEventType) + ") being created . . .");
    9.         EventManager<TEventType> tempManager = new EventManager<TEventType>();
    10.         if(tempManager != null) //In case construction throws an error
    11.             eventManagers.Add(typeof(TEventType), tempManager);
    12.         return tempManager;
    13.     }
    14.     else
    15.         return (EventManager<TEventType>)eventManager;
    16. }
    A couple things with this:

    1) I have not tested passing in a non-enum yet, so I'm not sure what exactly will happen when tempManager = new EventManager<TEventType>() fails. I believe tempManager just remains null, but I will need to test this.

    2) As you can see, each call to this method requires a TryGetValue operation and a bit of casting, so if you are using an event manager in a class quite a bit, you will want to only call this once and cache the reference.

    3) EventManagers are only created when they're requested for the first time. This, in addition to some other code I'll explain later, means EventManagers only exist in scenes where they're needed. When a new scene is loaded, any event manager that doesn't have one or more persistent message is removed from the dictionary and (I believe) free to be collected by the garbage collector.

    Edit: Actually, this last statement isn't quite right. The event manager will not be garbage collected until it is no longer referenced anywhere in the program. Destroyed components have their references released, so this should only be a concern when dealing with references on gameObjects marked as 'DontDestroyOnLoad'.

    If you've made it this far, thanks for bearing with me! I know that was very long winded. I am going to post the code soon, but I'd love to get some feedback on improvements or issues anyone sees first!
     
    Last edited: Jul 17, 2013
  15. user150221

    user150221

    Joined:
    Nov 10, 2012
    Posts:
    25
    How are you storing the event managers? Enums as Dict keys is slow. Enum to string is abysmally slow, which I discuss below.

    To speed up Enum keys in a dictionary you can use this EnumComparer<TEnum> class. But make sure to replace generateEquals() and generateGetHashCode()with the expression trees version in the last part.

    Unfortunately the entire Dict has to be the same Enum type.

    This part below I'd written before your last post:

    Turns out enum to string is abysmally slow. Slower than seemed possible... orders of magnitude slower. There are various string-caching strategies for enums, but instead it is faster to instead just use a Tuple<int, int> as the key, where it's (My.Enum.GetType().GetHashcode(), (int)(object)My.Enum) for the integer pair. The enum to string version only required adding some overloads for Add/Remove/Broadcast, but switching to Tuple<,> meant overloading a lot more of the functions. (I'm keeping the original string-based code intact until I finalize my implementation.)

    Tuples may be unnecessary, considering that the hash of the Tuples just appears to be the hash of the Enum type + the Enum value (cast to int). Which makes sense, but I could just add them up myself and insert them into a Dictionary<int, Delegate>.

     
  16. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Actually I don't use dictionaries at all, as when I was originally working on this I wanted the fastest thing I could think of. So instead the delegates for each event are stored in an array of delegates, where each index in the array maps to an enum value. As long as you don't manually assign any of the values in your enum, they automatically start at 0, so this works quite well. The enums are converted to an int (which was somewhat tricky in a generic class, which I'll explain later), and that int is used as the index. Unfortunately this process requires the array to be the same size as the number of values in each of your enums (in order for the enums to map correctly), which means you will be (in some instances) using additional memory on wasted space. However, the good news is (as far as I understand things) this wasted space is just made up of reference values, which don't take up that much memory, so I doubt it will be an issue (it hasn't been in my test so far).

    But you seem to want one collection/event manager for several different event/enum types, so this method won't work for you. May I ask why you are casting your enum to an int with (int)(object)My.Enum? From my test (int)My.Enum proved at least 10x faster than casting to (int)(object)My.Enum. Are you're enums backed by strings?
     
  17. user150221

    user150221

    Joined:
    Nov 10, 2012
    Posts:
    25
    You can't cast directly to int. I'm not sure how it'd even compile for you. The input is actually of type System.Enum, since the whole point of the Tuple<int, int> key was to store any enumeration type + enumeration value, instead of having one monolithic enum. You can't cast directly to int with a generic version either.

    Regardless, it's many many times slower to convert an enum to string than it is to get the hashcode of the enum type and cast the enum value to object then to int. Like 350ms vs <20ms for 1M iterations.

    Altogether, the slowdown doesn't matter in the least, since GetType.GetHashcode() is required either way. It's not even 2x slower to do SomeEnum.EnumValue.GetType().GetHashCode() + (int)(object)SomeEnum.EnumValue than it is without the object cast.


    Edit: Still curious as to how you converted to an int in a generic without casting to object. If it's using GetHashCode(), that is both slower than (int)(object) and less reliable. GetHashCode() is fine for the enum type, since it just needs to produce the same integer when storing and retrieving the Tuple, but for GetHashCode() on the enum value, if you're relying on it to produce the same integer as casting to int, that's not guaranteed.
     
    Last edited: Jul 18, 2013
  18. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I spent a couple hours trying to figure that one out :D. I stumbled into this post (Edit: The code is in the second post on that link). Basically the code he posted creates a dynamic method for converting from/to an enum to/from a value type. To use you declare a variable like this:
    Code (csharp):
    1. Func<yourEnumType, int> ConvertEnumToInt;
    Then assign to that variable like so:
    Code (csharp):
    1. ConvertEnumToInt = CreateFromEnumConverter<yourEnumType, int>();
    Then you use it like so:
    Code (csharp):
    1. int enumAsInt = ConvertEnumToInt(yourEnum);
    Unfortunately, I don't think this will work if you're using System.Enum, as I don't' believe that has an underlying type, which the conversion method creation process depends on. I could be wrong though, so it's worth a try. Still, it's very handy when using a generic enum, since you can just create the Func<GenericEnumType, int> once and use it repeatedly. Don't ask me how it works though, I know nothing about the process used to create the dynamic function (except that it works). In my test, this dynamic function proved to be significantly faster than (int)(object)enum, (int)(ValueType)enum, and Convert.ToInt32(enum). It is a tiny bit slower than (int) enum, but not by much.
     
    Last edited: Jul 18, 2013
  19. user150221

    user150221

    Joined:
    Nov 10, 2012
    Posts:
    25
    Yeah.... I implemented it, and it worked, but it's considerably slower than (int)(object). That is, without caching the delegate.

    Code (csharp):
    1.     public static int ToInt<T>(this T t) where T : struct, IComparable, IFormattable, IConvertible
    2.     {
    3.         Func<T, int> convertEnumToInt = CreateFromEnumConverter<T, int>();
    4.  
    5.         return convertEnumToInt(t);
    6.     }
    1000 iterations

    [TABLE="width: 500"]
    [TR]
    [TD](int)SomeEnum.EnumValue (does not work for generics)[/TD]
    [TD]~0.0ms[/TD]
    [/TR]
    [TR]
    [TD](int)(ValueType)SomeEnum.EnumValue[/TD]
    [TD]<0.2ms[/TD]
    [/TR]
    [TR]
    [TD](int)(object)SomeEnum.EnumValue[/TD]
    [TD]<0.2ms[/TD]
    [/TR]
    [TR]
    [TD]FormatEnumToInt(SomeEnum.EnumValue)[/TD]
    [TD]<0.2ms[/TD]
    [/TR]
    [TR]
    [TD]FormatEnumToString(SomeEnum.EnumValue)[/TD]
    [TD]3-6ms[/TD]
    [/TR]
    [TR]
    [TD]SomeEnum.EnumValue.ToInt()[/TD]
    [TD]~40ms[/TD]
    [/TR]
    [/TABLE]

    The various formatters:
    Code (csharp):
    1.     static public string FormatEnumToString(Enum e)
    2.     {
    3.         return e.GetType() + "." + e;
    4.     }
    5.  
    6.     static public Tuple<int,int> FormatEnumToTuple(Enum e)
    7.     {
    8.         return Tuple.Create(e.GetType().GetHashCode(), (int)(object)e);
    9.     }
    10.  
    11.     static public int FormatEnumToInt(Enum e)
    12.     {
    13.         return e.GetType().GetHashCode() + (int)(object)e;
    14.     }
    So then of course I attempt to cache the generic Func, like you say. And it's still slower... ish.

    Given the nature of multiple enum types, a Dictionary<Type, Delegate> is required, which you then modify CreateFromEnumConverter<>() to do a TryGetValue() on the Type. If it exists, return the value, but first it needs to be cast to Func<TEnum, TResult>. If it doesn't exist, generate the IL, store the delegate and then return it.

    However, due to a lot of GC on the (int)(object) version, on 100k iterations it generates like 1.1MB of garbage per frame, and doubles the run time (~14ms total). This new EnumConverter method w/ caching still takes about 50% longer (~20.5ms), but there is also no garbage.

    Obviously, storing a single static delegate is faster because retrieval is basically instantaneous. Putting a lot of delegates into a cache with a Type key incurs quite a bit of overhead. But at least it's free of boxing. There are evil things which bring the speed of this method to about the same as the (int)(object) method, but mostly it involves no-nos like caching the enum's Type hashcode and using that as the Dict key, like I considered with the Tuples for the Enum type / Enum value pair.

    Though, I just adapted the idea I had about storing the Type hashcode by having a generic static class for each enum Type, and instead use that generic static class to store the delegate for that Type. Then the syntax would, instead of just being SomeEnum.EnumValue.ToInt(), it would be ForEnumType<SomeEnum>.ToInt(SomeEnum.EnumValue).

    Less pretty, no longer being an extension method, but it's fast.

    100k iterations

    [TABLE="width: 500"]
    [TR]
    [TD](int)SomeEnum.EnumValue (does not work for generics)[/TD]
    [TD]~0.45ms[/TD]
    [/TR]
    [TR]
    [TD]ForEnumType<SomeEnum>.ToInt(SomeEnum.EnumValue)[/TD]
    [TD]~1.3ms[/TD]
    [/TR]
    [TR]
    [TD](int)(ValueType)SomeEnum.EnumValue[/TD]
    [TD]~14ms[/TD]
    [/TR]
    [TR]
    [TD](int)(object)SomeEnum.EnumValue[/TD]
    [TD]~14ms[/TD]
    [/TR]
    [TR]
    [TD]FormatEnumToInt(SomeEnum.EnumValue)[/TD]
    [TD]~16ms[/TD]
    [/TR]
    [TR]
    [TD]SomeEnum.EnumValue.ToInt()[/TD]
    [TD]~20.5ms[/TD]
    [/TR]
    [TR]
    [TD]FormatEnumToTuple(SomeEnum.EnumValue)[/TD]
    [TD]~28ms[/TD]
    [/TR]
    [TR]
    [TD]FormatEnumToString(SomeEnum.EnumValue)[/TD]
    [TD]~307ms[/TD]
    [/TR]
    [/TABLE]

    And the generic class:

    Code (csharp):
    1. public static class ForEnumType<T> where T : struct, IComparable, IFormattable, IConvertible
    2. {
    3.     private static readonly Func<T, int> Del;
    4.  
    5.     static ForEnumType()
    6.     {
    7.         Del = EnumExtensions.CreateFromEnumConverter<T, int>();
    8.     }
    9.  
    10.     public static int ToInt(T t)
    11.     {
    12.         return Del(t);
    13.     }
    14.  
    15. }
    (Note: This is just a prototype.)

    Overall the # of enum types used for messaging shouldn't be too high, and this allows you to create generic classes on an as-needed basis. Thus this has the benefit of not needing to create a delegate until it's needed, nor the need to pre-fill an array of all the delegates. It also is completely separate from the messaging system, so this can be used to accelerate the retrieval of the int values for any enum where you need to use generics.


    Edit: For my Tuple<int, int>, which is what allows the storage of multiple enum types in the first place, ToInt() obviously only gets you the second value. I am still going to store the result of GetType.GetHashcode() in the generic class. It's only a no-no to serialize the hashcode since they change on each run. That way both components of the Tuple get cached for each enum Type.

    I am realizing however that it is remotely possible to get collisions since the Type hashcode and the enum value integer are basically only added together, thus a Type + low index could result in the same number as another Type + high index, vice versa.

    Thus to reliably retrieve a Tuple<,> representing an enum Type and one of its enum Values, the enum Type hashcode should be multiplied by a couple numbers (primes are used generally), so that the distance between hashcode values is greater than the length of any of the enums.
     
    Last edited: Jul 19, 2013
  20. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Good stuff. That generic static class you created for the converter is especially interesting. Creating a dynamic method each time is definitely going to incur a performance penalty, same with using the dictionary to retrieve the delegate storing the dynamic method, so that generic static class is a great solution.
     
  21. geroppo

    geroppo

    Joined:
    Dec 24, 2012
    Posts:
    140
    Hello there and super thanks for this awesome messenger, i was looking for something like this. I wanted to ask if you had the updated version of the messenger since the link in the first post still seems to have the "1.0" version, and after all the feedback you got here i was wondering if you could post an updated version. Cheers
     
  22. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I am not the author of the version in the first post, so I'm not sure if you're question was directed at me or not, but I have finished the next iteration of my own version of the C# advanced messenger system.

    View attachment $EventSystem.zip

    Just unzip that and then import the package into a project. It should work with any version after 3.5, but please let me know if you have any problems. There's no documentation really, but I have included some sample scenes showcasing a couple ways the system can be used, with .txt file explanations of what's going on. Let me know if you have any questions though!
     
  23. geroppo

    geroppo

    Joined:
    Dec 24, 2012
    Posts:
    140
    Yes thank you very much, im aware you are not the original author but you are the one who made a better version of an already enhanced version of the original messenger :p so thanks to you and the others too, included the ones who gave you feedback to improve this even more. And thanks for the documentation, it really helped.

    EDIT: oh i forgot to ask, this messenger isn't made for a continuous call in Update() right ? its more for like a callback thing ?
    EDIT2: lol i feel stupid, i just realized what you meant by "not the autor in the first post", but still thanks for the zip, i was looking at it and was like "oh wow this has so many more things than in the first post" then came back here and re read everything :p
     
    Last edited: Aug 30, 2013
  24. averybloom

    averybloom

    Joined:
    Jun 25, 2014
    Posts:
    1
    HI there @gilley033 I'm new to Unity, but not programming, and I'd love to use your library in my project. However, I'm having issues importing the zip package above. Is this package valid with Unity 4.5?

    Also, would you be open to putting this open source on github?
     
  25. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Try this package instead. I just tested all the sample scenes on Unity 4.5 and it works fine. I do have Unity Pro, but I don't think there should be an issue with using this with Indie.

    All the required files for the event system are in the root EventSystem folder, and everything is in the Events namespace, so to use the events as is you will need to add a using Events statement. Feel free to delete all the sample scene assets and files after you go through them. Each sample scene has a read me explaining what's going on. These scenes are mainly there to show some of the different ways to use the system.

    As for github, I've never used it before. You are welcome to put it up there, as I doubt I will do so myself. As far as I'm concerned, these scripts can be used/modified without restriction, though do note that since the original scripts came from the Unity Wiki, I believe selling them is a no no. Otherwise, just make sure to leave the original author's names; you can add me in as well if you wish, it doesn't make much difference to me.

    One important thing to keep in mind, however. Reflection.Emit is vital to being able to use the enums in the way I am using them, and unfortunately I believe a Unity staff member has stated this function will not work in a future version of Unity.

    So there's a chance this code may become unusable at some future date. Perhaps there is an alternative method for doing what Reflection.Emit does. As you can see in the credits for the code, someone else developed the code I am using, and I don't know nearly enough to try and find an alternative solution myself.


    @geroppo Yes this is meant as more of a call back thing. Feel free to use it in whatever way you wish though!
     

    Attached Files:

    boysenberry likes this.
  26. sz-Bit-Barons

    sz-Bit-Barons

    Joined:
    Nov 12, 2013
    Posts:
    150
    Hi,
    I love your event system and i use it massively in my project :)

    However, it doesn't work with iOS (or other AOT platforms) because of reflection used for converting an enum to an int.
    I fixed this issue so it works with AOT compilation:

    replace in EventManager.cs the following line inside the constructor:
    Code (CSharp):
    1. ConvertEnumToInt = EnumConverterCreator.CreateFromEnumConverter<TEventType, int>();
    replace with this:
    Code (CSharp):
    1. ConvertEnumToInt = (o) => o.ToInt32(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
    Then you can safely remove EnumConverterCreator.cs.
     
  27. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Yes, I thought about using that method at first, but went with the Reflection code for performance reasons. Although, depending on how much you use the Broadcast methods each frame, the speed boost is probably negligible.

    Also, that Reflection code may be obsolete in a future version of Unity for all platforms, so it's probably better to go with the ToInt32 method.
     
  28. sonicviz

    sonicviz

    Joined:
    May 19, 2009
    Posts:
    1,051
  29. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I'm not the original creator, and I'm not sure if you are using my version or the original one, but I'll throw my thoughts out there anyway.

    As far as I can tell, the new Event System is geared toward UI events, such as button presses, mouse clicks, etc. I may be wrong, but it's not really meant as an all purpose message passing service, such as the systems in the thread you linked.

    Then again, I haven't used it much, so that may be incorrect. Hopefully someone will chime in if that's the case.
     
  30. sonicviz

    sonicviz

    Joined:
    May 19, 2009
    Posts:
    1,051
    I'm using the original but I'll test yours today.

    "The messaging system is generic and designed for use not just by the UI system but also by general game code. It is relatively trivial to add custom messaging events and they will work using the same framework that the UI system uses for all event handling."
    According to http://docs.unity3d.com/Manual/MessagingSystem.html
     
  31. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Ahh, my mistake; thanks for the link. The new messaging system looks to be very interesting and useful, especially as an alternative to SendMessage.

    However, one of the main reasons for switching to an event based messaging system is the idea that the event broadcaster does not know/care who is listening. For that purpose, the other messaging systems still have a place I think, since the new messaging system (like the old SendMessage) requires an explicit definition of which game object should receive the message.

    A performance analysis of the new system vs the systems in that other thread would be very interesting!
     
  32. tpainton

    tpainton

    Joined:
    Jun 4, 2014
    Posts:
    16
    Great system. I'm using it and likeing it but lately, since adding a custom editor exentension, I'm getting multiple instances of MessengerHelper in the same scene. Ideas?? I would think MessengerHelper could be a singleton so this is never a problem.