Rpc.Event support for client sdk
Options
Hello!
On the server side I have a custom event class:
and it is a bit inconvenient to write (and hard to read)
It would be nice to be able to copy LoadSmthEvent class from server code to client code (with only one attribute):
The question is - what's the best way to approach the problem?
thanks,
Slav
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)
many many times.(string)photonEvent[(byte)100]
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
0
Comments
-
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).0 -
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)
0 -
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)
0 -
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 ofpublic 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; }
asClientLibrary.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())); } }
0 -
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.0 -
Really useful stuff here gnoblin, thanks for sharing!0