dimanche 22 juillet 2018

C# -A clean / performant way to filter certain class instances fields

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 () {      
}}





Aucun commentaire:

Enregistrer un commentaire