1

I am trying to roll an event handling system in C# - in my case it's for a game in Unity but it's abstract enough to apply to any system.

A singleton class "EventManager" has a private Dictionary(System.Type,Dictionary(long, EventListener)) __listeners along with a public methods to Register, Unregister and ThrowEvent(EventInfo ei). The dictionary's key is a type derived from EventInfo, so there will be keys for EventInfoFoo, EventInfoBar and so forth, and these may not necessarily have the same fields.

I would also like to be able to only listen for specific conditions within these classes derived from EventInfo such as "only fire when ei.CreatureType==Animal" or "position.x between 1 and 5".

I have a working solution using Reflection, however its performance is just not good enough. My next idea was to have this filter be a delegate method passed on by the class registering the listener, but since I expect almost all, if not all filters to be equality/range checks, I wonder if there's a cleaner way of handling it.

Here are the pertaining classes:

EventListener:

public class EventListener {

public Dictionary<string, string> eventFilter;
public delegate void eventHandler(EventInfo ei);

public eventHandler Eh;

public EventListener( eventHandler evH,Dictionary<string, string> filter)
    {
    Eh= evH;
    eventFilter = filter;
    }}

EventInfo:

public  class EventInfo  {
    public Object Caller;
    public EventInfo (Object __caller)
    {
        Caller = __caller;        
    }
    public EventInfo()
    { Caller = null; }
}
public class EventInfoExample : EventInfo
{
    public int Testint;
    public EventInfoExample(Object __caller)
{
    Caller = __caller;
}
}

EventManager:

public class EventManager : MonoBehaviour {

private static EventManager __em;
public static EventManager Em
{
    get  { return EventManager.__em; }
}

private Dictionary<System.Type,Dictionary<long, EventListener>> __listeners;    
private long __idcounter = 1;

private long getNewID()
{
    long __ret = __idcounter;
    __idcounter++;
    return __ret;
}

//true on let through , false on block
private bool __doFilter(Dictionary<string,string>eventFilter , EventInfo ei)
{
    // if no filters, accept
    if (eventFilter == null || eventFilter.Count < 1)
        return true;

    System.Type __eit = ei.GetType();
    FieldInfo[] __fields = ei.GetType().GetFields();
    List<string> __fieldlist = __fields.Select(f => f.Name).ToList();

    foreach (KeyValuePair<string,string> kvp in eventFilter)
    {
        if (__fieldlist.Contains(kvp.Key) == false)
            Debug.LogError("Fieldlist for " + __eit.ToString() + " does not contain a field named " + kvp.Key);

        //this is what we are filtering for 
        //TODO add support for operators, for now its just == 
        if (__eit.GetField(kvp.Key).GetValue(ei).ToString() != kvp.Value)
            return false;
    }
    return true;

}

public Object ThrowEvent(EventInfo ei)
{
    Debug.Assert(__listeners != null);
    Debug.Assert(ei != null);

    if (__listeners.ContainsKey(ei.GetType()) == false)
        return null;

    //call all 
     foreach ( KeyValuePair<long,EventListener>  __kvp2 in __listeners[ei.GetType()])
        {              
            // apply listener filters         
            if (__doFilter(__kvp2.Value.eventFilter , ei))
            {
                Debug.Log("Invoking ID " + __kvp2.Key.ToString() + " for " + ei.GetType().ToString());
                __kvp2.Value.Eh(ei);
            }                    
        }     

    return null;
}

public long Register(System.Type eventType,EventListener el)
{
    Debug.Assert(eventType.IsSubclassOf(typeof(EventInfo)));
    Debug.Assert(el != null);

    Debug.Assert(__listeners != null);

    // if we dont have a key for this type, create new dict, then add to dict 
    if (__listeners.ContainsKey(eventType) == false)
        __listeners.Add(eventType, new Dictionary<long, EventListener>());

    long __newID = getNewID();
    //add to list
    __listeners[eventType].Add(__newID, el);
    return __newID;    
}

public bool Unregister(long ID)
{
    return true;
}

// Use this for initialization
void Start () {
    // pop singleton
    EventManager.__em = this;       
}


// Update is called once per frame
void Update () {      
}}
Jazz
  • 11
  • 2
  • Have you heard of ReactiveX? It might be what you need, see https://stackoverflow.com/questions/1596158/good-introduction-to-the-net-reactive-framework and http://reactivex.io/ – Rui Jarimba Jul 22 '18 at 16:56
  • `ThrowEvent` is odd terminology, usually you'd call it `RaiseEvent`. Your `getNewID` method is not thread safe, you may end up reusing an Id. Try the `Interlocked` class to get around that. I highly recommend looking into c# naming conventions. As you mention Unity, I know they may differ there, but double underscores is very odd (and hard on the eyes), most especially when used for local variables and parameters. At the very least be consistent. Also, as you have "a working solution", this question is probably a better fit for codereview.stackexchange. – pinkfloydx33 Jul 22 '18 at 20:44
  • This question would probably be better served on codereview.stackexchange.com – pinkfloydx33 Jul 22 '18 at 20:46

0 Answers0