Rpc.Event support for client sdk

Options
gnoblin
gnoblin
edited June 2011 in DotNet
Hello!

On the server side I have a custom event class:
class LoadSmthEvent : Event
    {
        [EventParameter(Code = 100, IsOptional = true)]
        public string smth_name = null;
        [EventParameter(Code = 101, IsOptional = true)]
        public int smth_size = 0;
    }
On the client side I receive event like this:
public void EventAction(byte eventCode, System.Collections.Hashtable photonEvent)
        {
            switch (eventCode)
            {
                case (byte)EventCode.LoadSmth:
                    on_load_smth((string)photonEvent[(byte)100], (int)photonEvent[(byte)101]);
                    break;
...
As far as I know there's no 'Event' class in the client sdk,
and it is a bit inconvenient to write (and hard to read)
(string)photonEvent[(byte)100]
many many times.

It would be nice to be able to copy LoadSmthEvent class from server code to client code (with only one attribute):
class LoadSmthEvent : Event
    {
        [EventParameter(Code = 100)]
        public string smth_name = null;
        [EventParameter(Code = 101)]
        public int smth_size = 0;
    }
and then somehow automatically construct event object
public void EventAction(byte eventCode, System.Collections.Hashtable photonEvent)
        {
            switch (eventCode)
            {
                case (byte)EventCode.LoadSmth:
                    on_load_smth(something simple and readable here);
                    break;
...
--

The question is - what's the best way to approach the problem?

thanks,
Slav

Comments

  • Boris
    Options
    I'm not sure how well reflection is supported in unity/mono..
    but you could do this manually:
    public LoadSmthEvent(Hashtable photonEvent)
    {
       this.smth_name = (string)photonEvent[(byte)100];
       this.smth_size = (int)photonEvent[(byte)101];
    }
    
    you could also generate this code (just need to write the generator - question is if writing the constructor isn't faster).
  • gnoblin
    Options
    Here's another version of how this can be done (thanks to Andrey Pakhomov aka PAX):
    using System;
    using System.Collections;
    
    
    class LoadPetEvent 
    {
        [EventParameter(Code = 100, IsOptional = true)]
        public string pet_name = null;
        [EventParameter(Code = 101, IsOptional = true)]
        public int pet_type = 0;
        [EventParameter(Code = 102, IsOptional = true)]
        public int pet_level = 0;
    }
    
    public sealed class EventParameterAttribute : ObjectDataMemberAttributeBase
    {
    
    }
    
    [AttributeUsage(AttributeTargets.Field)]
    public class ObjectDataMemberAttributeBase : Attribute
    {
    
        public short Code { get; set; }
        public bool IsOptional { get; set; }
    }
    
    public static class ServerEventMapper
    {
        public static T GetEvent<T>(Hashtable data)
        {
            T t = Activator.CreateInstance<T>();
    
            var fields = t.GetType().GetFields();
    
            foreach (var fieldInfo in fields)
            {
               EventParameterAttribute attr = (EventParameterAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof (EventParameterAttribute));
    
                if (attr!= null)
                {
                    byte code = (byte)attr.Code;
                    if (data.ContainsKey(code))
                    {
                        fieldInfo.SetValue(t, data[code]);
                    }
                    else if (!attr.IsOptional)
                    {
                        throw new Exception("Not optional value not found!");
                    }
                }
            }
    
            return t;
        }
    }
    
    // usage
    // LoadPetEvent event = ServerEventMapper.GetEvent<LoadPetEvent>(photonEvent)
    
  • gnoblin
    Options
    And probably a bit faster variant in terms of performance (still need to test it).
    Thanks again to the same person!
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;
    
    class LoadPetEvent
    {
        [EventParameter(Code = 100, IsOptional = true)]
        public string pet_name = null;
        [EventParameter(Code = 101, IsOptional = true)]
        public int pet_type = 0;
        [EventParameter(Code = 102, IsOptional = true)]
        public int pet_level = 0;
    }
    
    public sealed class EventParameterAttribute : ObjectDataMemberAttributeBase { }
    
    [AttributeUsage(AttributeTargets.Field)]
    public class ObjectDataMemberAttributeBase : Attribute
    {
        public short Code { get; set; }
        public bool IsOptional { get; set; }
    }
    
    public static class ServerEventMapper
    {
        class CachedField
        {
            public ObjectDataMemberAttributeBase attribute;
            public FieldInfo fieldInfo;
        }
    
        private static readonly Dictionary<Type, List<CachedField>> EventCache
            = new Dictionary<Type, List<CachedField>>();
    
    
        public static T GetEvent<T>(Hashtable data)
        {
            var obj = Activator.CreateInstance<T>();
    
            var objType = typeof (T);
    
            List<CachedField> fields;
            if (EventCache.ContainsKey(objType))
            {
                fields = EventCache[objType];
            }
            else
            {
                fields = new List<CachedField>();
                foreach (var fieldInfo in objType.GetFields())
                {
                    var attr = (EventParameterAttribute)Attribute.GetCustomAttribute(
                        fieldInfo, typeof(EventParameterAttribute));
    
                    if (attr != null)
                    {
                        CachedField field =
                            new CachedField
                                {
                                    attribute = attr,
                                    fieldInfo = fieldInfo
                                };
                        fields.Add(field);
                    }
                }
                EventCache[objType] = fields;
            }
    
            foreach (var fieldInfo in fields)
            {
                byte code = (byte)fieldInfo.attribute.Code;
                if (data.ContainsKey(code))
                {
                    fieldInfo.fieldInfo.SetValue(obj, data[code]);
                }
                else if (!fieldInfo.attribute.IsOptional)
                {
                    throw new Exception("Not optional value not found!");
                }
            }
    
            return obj;
        }
    }
    
    // usage:
    // LoadPetEvent event = ServerEventMapper.GetEvent<LoadPetEvent>(photonEvent)
    
  • gnoblin
    Options
    I decided to post this thing here :).

    It is convenient to print incoming events into the console (on the client).
    But manually constructing a string for each class is tedious.

    So here is an extension method for printing an instance of
    public class LoadFlowerEvent : Event //Note: Event here is just "class Event { }" (for server code compatibility pusposes)
        {
            [EventParameter(Code = 100, IsOptional = true)]
            public float pos_x = 0;
            [EventParameter(Code = 101, IsOptional = true)]
            public float pos_y = 0;
            [EventParameter(Code = 102, IsOptional = true)]
            public int flower_type = 0;
        }
    

    as
    ClientLibrary.LoadFlowerEvent (pos_x = 23.5, pos_y = 47, flower_type = 1)

    by simply doing
    Debug.Log(load_flower_event.SuperToString()); //should work for all event\operation classes (and not only them)

    Big thanks to PAX!

    The code you need to add to your project is:
    public static class ObjectExtender
        {
            public static string SuperToString(this object obj)
            {
                List<string> parametres = new List<string>(); 
                foreach (var f in obj.GetType().GetFields())
                {
                    parametres.Add(f.Name + " = " + f.GetValue(obj));
                }
    
                return string.Format("{0} ({1})", obj.GetType(), string.Join(", ", parametres.ToArray()));
            }
        }
    
  • gnoblin
    Options
    I've made the same system for Operation classes on the client (after meditating on PAX's code).
    It works OK (so I can just copy Operation class from server and be happy without manually writing parameter codes).
    I'm planning to post it here a bit later.
  • Really useful stuff here gnoblin, thanks for sharing!