I am trying to set up a networked event system in my Unity Project that allows triggering events on other clients. The event also is supposed to pass an EventArgs-type specific to that event.
However, apparently System.EventArgs is not Serializable and thus can not be directly sent over the MLAPI Network. Therefore I convert the class to a string with Unity's JsonUtility class and instead send that string. Additionally, I send another string which is the specific eventArgsType.ToString().
On client side, I am then converting the eventArgsType to a Type with GetType, and would then like to Deserialize the data-string into the correct eventArgs class. Which I currently do by nasty if-else case comparing to the possible eventArgs-Subtypes and then Deserialize as that. That works, but is extremely ugly and will get out of hand the more subtypes I have. But I am not quite sure of how to do that and would appreciate any tips.
What I currently have:
public class EventSystem : NetworkBehaviour
{
[...]
public void TriggerLocalAndNetwork(string eventName, EventArgs data)
{
ulong clientId = NetworkManager.Singleton.LocalClientId;
if (Instance._eventDictionary.TryGetValue(eventName, out var triggeredEvent))
{
triggeredEvent.Invoke(clientId, data); // Local Trigger
string argsTyp = data.ToString();
EventToServerRpc(eventName, clientId, EventArgsSerializer.Serialize(data), argsTyp);
}
}
[ServerRpc(RequireOwnership = false)]
private void EventToServerRpc(string eventName, ulong clientId, string data, string argsType)
{
EventToClientRpc(eventName, clientId, data, argsType);
}
[ClientRpc]
private void EventToClientRpc(string eventName, ulong clientId, string data, string argsType)
{
if (clientId == NetworkManager.Singleton.LocalClientId)
return;
if (!Instance._eventDictionary.TryGetValue(eventName, out var triggeredEvent))
return;
Type args = Type.GetType(argsType);
EventArgs eventArgs = EventArgs.Empty;
// This is what I want to get rid off
if (args == typeof(MagicAttackEventArgs))
{
eventArgs = EventArgsSerializer.Deserialize<MagicAttackEventArgs>(data);
}
else if (args == typeof(HealthSystem.HealthSystem.TakeDamageEventArgs))
{
eventArgs = EventArgsSerializer.Deserialize<HealthSystem.HealthSystem.TakeDamageEventArgs>(data);
}
triggeredEvent.Invoke(clientId, eventArgs);
}
}
public static class EventArgsSerializer
{
public static string Serialize(EventArgs eventArgs)
{
string json = JsonUtility.ToJson(eventArgs);
return json;
}
public static T Deserialize<T>(string input)
{
T eventArgs = JsonUtility.FromJson<T>(input);
return eventArgs;
}
}
What I tried instead of the if-else part is this:
Type args = Type.GetType(argsType);
MethodInfo method = typeof(EventArgsSerializer).GetMethod(nameof(EventArgsSerializer.Deserialize));
MethodInfo generic = method.MakeGenericMethod(args);
var result = generic.Invoke(null, new object[]{data});
EventArgs eventArgs = (EventArgs) Convert.ChangeType(result, typeof(EventArgs));
triggeredEvent.Invoke(clientId, eventArgs);
But it yield a casting error, and I am not sure if I used the tools correctly at all, it was inspired just inspired by https://stackoverflow.com/a/232621 this answer.
CodePudding user response:
As per the docs there is an overload of that FromJson
method which takes a Type
Type args = Type.GetType(argsType);
EventArgs eventArgs = (EventArgs)JsonUtility.FromJson(data,args);
CodePudding user response:
I would suggest using a serialization library with support for polymorphism. See for example the article for System.Text.Json, but this is a rather common feature for most serialization libraries.
This lets you use a common base class for messages that can be used when de-serializing. I would recommend defining your own message-type, rather than just using EventArgs
.
There are a few different ways to do different things depending on the message:
- A virtual method, probably the simplest option, but may be more difficult to use if the method needs different parameters depending on the actual type.
- pattern matching
- The visitor pattern