Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fully capable SerializableAction for Unity. Supports targets of both UnityEngine.Object and System.Object and one-layer serialization of unserializable types. Supports static and generic methods and classes; Supports most anonymous actions, fully capable of using the context. Support: forum.unity3d.com/threads/406299
namespace SerializableActionHelper
{
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
/// <summary>
/// Wrapper for a single-cast Action without parameters that handles serialization supporting System- and UnityEngine Objects aswell as anonymous methods to some degree
/// </summary>
[Serializable]
public class SerializableAction
{
private Action action;
[SerializeField]
private SerializableObject serializedTarget;
[SerializeField]
private SerializableMethodInfo serializedMethod;
// Accessor Properties
public Action Action
{
get
{
if (action == null)
action = DeserializeAction ();
return action;
}
}
public object Target { get { return Action.Target; } }
public MethodInfo Method { get { return Action.Method; } }
/// <summary>
/// Create a new SerializableAction from a non-anonymous action (System or Unity)
/// </summary>
public SerializableAction (Action srcAction)
{
if (srcAction.GetInvocationList ().Length > 1)
throw new UnityException ("Cannot create SerializableAction from a multi-cast action!");
SerializeAction (srcAction);
}
#region General
/// <summary>
/// Invoke this serialized action
/// </summary>
public void Invoke ()
{
if (Action != null)
Action.Invoke();
}
/// <summary>
/// Returns whether this action is valid
/// </summary>
public bool IsValid ()
{
if (action == null)
{
try
{
action = DeserializeAction ();
}
catch
{
return false;
}
}
return true;
}
#endregion
#region Serialization
/// <summary>
/// Serializes the given action depending on the type (System or Unity) and stores it into this SerializableAction
/// </summary>
private void SerializeAction (Action srcAction)
{
action = srcAction;
//Debug.Log ("Serializing action for method '" + srcAction.Method.Name + "'!"); // TODO: DEBUG REMOVE
serializedMethod = new SerializableMethodInfo (srcAction.Method);
serializedTarget = new SerializableObject (action.Target);
}
/// <summary>
/// Deserializes the action depending on the type (System or Unity) and returns it
/// </summary>
private Action DeserializeAction ()
{
// Target
object target = serializedTarget.Object;
// Method
MethodInfo method = serializedMethod.methodInfo;
if (method == null)
throw new DataMisalignedException ("Could not deserialize action method '" + serializedMethod.SignatureName + "'!");
return Delegate.CreateDelegate (typeof(Action), target, method) as Action;
}
#endregion
}
}
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
public class SerializableActionTestWindow : EditorWindow
{
[MenuItem ("Window/SerializableAction Test")]
private static void Open ()
{
EditorWindow.GetWindow<SerializableActionTestWindow> ("SerializableAction Test");
}
#region Test Methods: Unity
private void UnityTargettedNormal ()
{
Debug.Log ("Unity-Targetted Normal executed!");
}
private void UnityTargettedGenericMethodNormal<T> ()
{
Debug.Log ("Unity-Targetted GenericMethod<" + typeof(T).Name + "> Normal executed!");
}
private static void UnityTargettedStatic ()
{
Debug.Log ("Unity-Targetted Static executed!");
}
private static void UnityTargettedGenericMethodStatic<T> ()
{
Debug.Log ("Unity-Targetted GenericMethod<" + typeof(T).Name + "> Static executed!");
}
private static Action getUnityTargettedAnonymous ()
{
return new Action (() => Debug.Log ("Unity-Targetted Anonymous executed!"));
}
private static Action getUnityTargettedAnonymous<T> ()
{
return new Action (() => Debug.Log ("Unity-Targetted GenericMethod<" + typeof(T).Name + "> Anonymous executed!"));
}
#endregion
#region Test Methods: System
[Serializable]
public class SystemClass : System.Object
{
public void SystemTargettedNormal ()
{
Debug.Log ("System-Targetted Normal executed!");
}
public void SystemTargettedGenericMethodNormal<T> ()
{
Debug.Log ("System-Targetted GenericMethod<" + typeof(T).Name + "> Normal executed!");
}
public static void SystemTargettedStatic ()
{
Debug.Log ("System-Targetted Static executed!");
}
public static void SystemTargettedGenericMethodStatic<T> ()
{
Debug.Log ("System-Targetted GenericMethod<" + typeof(T).Name + "> Static executed!");
}
public Action getSystemTargettedAnonymous ()
{
return new Action (() => Debug.Log ("System-Targetted Anonymous executed!"));
}
public Action getSystemTargettedGenericAnonymous<T> ()
{
return new Action (() => Debug.Log ("System-Targetted GenericMethod<" + typeof(T).Name + "> Anonymous executed!"));
}
}
#endregion
#region Test Methods: System (Generic Class)
[Serializable]
public class SystemGenericTypeClass<T> : System.Object
{
public void SystemTargettedGenericNormal ()
{
Debug.Log ("System-Targetted GenericClass<" + typeof(T).Name + "> Normal executed!");
}
public static void SystemTargettedGenericStatic ()
{
Debug.Log ("System-Targetted GenericClass<" + typeof(T).Name + "> Static executed!");
}
public Action getSystemTargettedGenericAnonymous ()
{
return new Action (() => Debug.Log ("System-Targetted GenericClass<" + typeof(T).Name + "> Anonymous executed!"));
}
}
#endregion
public SystemClass systemClass = new SystemClass ();
public SystemGenericTypeClass<Vector3> systemGenericTypeClass = new SystemGenericTypeClass<Vector3> ();
public SerializableAction unityStaticAction = null;
public SerializableAction unityGenericMethodStaticAction = null;
public SerializableAction systemStaticAction = null;
public SerializableAction systemGenericStaticAction = null;
public SerializableAction systemGenericMethodStaticAction = null;
public SerializableAction unityNormalAction = null;
public SerializableAction unityGenericMethodNormalAction = null;
public SerializableAction systemNormalAction = null;
public SerializableAction systemGenericNormalAction = null;
public SerializableAction systemGenericMethodNormalAction = null;
public SerializableAction unityAnonymousAction = null;
public SerializableAction unityGenericMethodAnonymousAction = null;
public SerializableAction systemAnonymousAction = null;
public SerializableAction systemGenericAnonymousAction = null;
public SerializableAction systemGenericMethodAnonymousAction = null;
public void OnGUI ()
{
// -----
EditorGUILayout.Space ();
GUILayout.Label ("STATIC");
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted Static", ref unityStaticAction, SerializableActionTestWindow.UnityTargettedStatic);
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted GenericMethod<Vector3> Static", ref unityGenericMethodStaticAction, SerializableActionTestWindow.UnityTargettedGenericMethodStatic<Vector3>);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted Static", ref systemStaticAction, SystemClass.SystemTargettedStatic);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericClass<Vector3> Static", ref systemGenericStaticAction, SystemGenericTypeClass<Vector3>.SystemTargettedGenericStatic);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericMethod<Vector3> Static", ref systemGenericMethodStaticAction, SystemClass.SystemTargettedGenericMethodStatic<Vector3>);
// -----
EditorGUILayout.Space ();
GUILayout.Label ("NORMAL");
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted Normal", ref unityNormalAction, this.UnityTargettedNormal);
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted GenericMethod<Vector3> Normal", ref unityGenericMethodNormalAction, this.UnityTargettedGenericMethodNormal<Vector3>);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted Normal", ref systemNormalAction, systemClass.SystemTargettedNormal);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericClass<Vector3> Normal", ref systemGenericNormalAction, systemGenericTypeClass.SystemTargettedGenericNormal);
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericMethod<Vector3> Normal", ref systemGenericMethodNormalAction, systemClass.SystemTargettedGenericMethodNormal<Vector3>);
// -----
EditorGUILayout.Space ();
GUILayout.Label ("ANONYMOUS");
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted Anyonymous", ref unityAnonymousAction, getUnityTargettedAnonymous ());
EditorGUILayout.Space ();
ActionGUI ("Unity-Targetted GenericMethod<Vector3> Anyonymous", ref unityGenericMethodAnonymousAction, getUnityTargettedAnonymous<Vector3> ());
EditorGUILayout.Space ();
ActionGUI ("System-Targetted Anyonymous", ref systemAnonymousAction, systemClass.getSystemTargettedAnonymous ());
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericClass<Vector3> Anyonymous", ref systemGenericAnonymousAction, systemGenericTypeClass.getSystemTargettedGenericAnonymous ());
EditorGUILayout.Space ();
ActionGUI ("System-Targetted GenericMethod<Vector3> Anyonymous", ref systemGenericMethodAnonymousAction, systemClass.getSystemTargettedGenericAnonymous<Vector3> ());
// -----
Repaint ();
}
private void ActionGUI (string label, ref SerializableAction serializedAction, Action action)
{
GUILayout.Label (label + " SerializableAction");
GUILayout.BeginHorizontal ();
if (GUILayout.Button ("Create (" + (serializedAction != null) + ")"))
{
serializedAction = new SerializableAction (action);
serializedAction.Invoke ();
}
if (GUILayout.Button ("Invoke"))
{
if (serializedAction != null)
serializedAction.Invoke ();
else
Debug.LogError (label + " Action is null!");
}
GUILayout.EndHorizontal ();
}
}
namespace SerializableActionHelper
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using System.Runtime.CompilerServices;
/// <summary>
/// Wrapper for MethodInfo that handles serialization.
/// Stores declaringType, methodName, parameters and flags only and supports generic types (one level for class, two levels for method).
/// </summary>
[Serializable]
public class SerializableMethodInfo : ISerializationCallbackReceiver
{
public MethodInfo methodInfo;
[SerializeField]
private SerializableType declaringType;
[SerializeField]
private string methodName;
[SerializeField]
private List<SerializableType> parameters = null;
[SerializeField]
private List<SerializableType> genericTypes = null;
[SerializeField]
private int flags = 0;
// Accessors
public string SignatureName { get { return (((BindingFlags)flags&BindingFlags.Public) != 0? "public" : "private") + (((BindingFlags)flags&BindingFlags.Static) != 0? " static" : "") + " " + methodName; } }
public bool IsAnonymous { get { return Attribute.GetCustomAttribute (methodInfo, typeof(CompilerGeneratedAttribute), false) != null || declaringType.isCompilerGenerated; } }
public SerializableMethodInfo (MethodInfo MethodInfo)
{
methodInfo = MethodInfo;
}
#region Serialization
public void OnBeforeSerialize()
{
if (methodInfo == null)
return;
declaringType = new SerializableType (methodInfo.DeclaringType);
methodName = methodInfo.Name;
// Flags
if (methodInfo.IsPrivate)
flags |= (int)BindingFlags.NonPublic;
else
flags |= (int)BindingFlags.Public;
if (methodInfo.IsStatic)
flags |= (int)BindingFlags.Static;
else
flags |= (int)BindingFlags.Instance;
// Parameter
ParameterInfo[] param = methodInfo.GetParameters ();
if (param != null && param.Length > 0)
parameters = param.Select ((ParameterInfo p) => new SerializableType (p.ParameterType)).ToList ();
else
parameters = null;
// Generic types
if (methodInfo.IsGenericMethod)
{
methodName = methodInfo.GetGenericMethodDefinition ().Name;
genericTypes = methodInfo.GetGenericArguments ().Select ((Type genArgT) => new SerializableType (genArgT)).ToList ();
}
else
genericTypes = null;
}
public void OnAfterDeserialize()
{
if (declaringType == null || declaringType.type == null || string.IsNullOrEmpty (methodName))
return;
// Parameters
Type[] param;
if (parameters != null && parameters.Count > 0) // With parameters
param = parameters.Select ((SerializableType t) => t.type).ToArray ();
else
param = new Type[0];
methodInfo = declaringType.type.GetMethod (methodName, (BindingFlags)flags, null, param, null);
if (methodInfo == null)
{ // Retry with private flags, because in some compiler generated methods flags will be uncertain (?) which then return public but are private
methodInfo = declaringType.type.GetMethod (methodName, (BindingFlags)flags | BindingFlags.NonPublic, null, param, null);
if (methodInfo == null)
throw new Exception ("Could not deserialize '" + SignatureName + "' in declaring type '" + declaringType.type.FullName + "'!");
}
if (methodInfo.IsGenericMethodDefinition && genericTypes != null && genericTypes.Count > 0)
{ // Generic Method
Type[] genArgs = genericTypes.Select ((SerializableType t) => t.type).ToArray ();
MethodInfo genMethod = methodInfo.MakeGenericMethod (genArgs);
if (genMethod != null)
methodInfo = genMethod;
else
Debug.LogError ("Could not make generic-method definition '" + methodName + "' generic!");
}
}
#endregion
}
}
namespace SerializableActionHelper
{
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using System.Runtime.CompilerServices;
/// <summary>
/// Wrapper for an arbitrary object that handles basic serialization, both System.Object, UnityEngine.Object, and even basic unserializable types (the same way, but one-level only, unserializable members will be default or null if previously null)
/// </summary>
[Serializable]
public class SerializableObject : SerializableObjectOneLevel
{
[SerializeField]
private List<SerializableObjectOneLevel> manuallySerializedMembers;
/// <summary>
/// Create a new SerializableObject from an arbitrary object
/// </summary>
public SerializableObject (object srcObject) : base (srcObject) { }
/// <summary>
/// Create a new SerializableObject from an arbitrary object with the specified name
/// </summary>
public SerializableObject (object srcObject, string name) : base (srcObject, name) { }
#region Serialization
/// <summary>
/// Serializes the given object and stores it into this SerializableObject
/// </summary>
protected override void Serialize ()
{
if (isNullObject = Object == null)
return;
base.Serialize (); // Serialized normally
if (unityObject == null && String.IsNullOrEmpty (serializedSystemObject))
{ // Object is unserializable so it will later be recreated from the type, now serialize the serializable field values of the object
FieldInfo[] fields = objectType.type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
manuallySerializedMembers = fields.Select ((FieldInfo field) => new SerializableObjectOneLevel (field.GetValue (Object), field.Name)).ToList ();
}
}
/// <summary>
/// Deserializes this SerializableObject
/// </summary>
protected override void Deserialize ()
{
Object = null;
if (isNullObject)
return;
base.Deserialize (); // Deserialize normally
if ((Object == null || !Object.GetType ().IsSerializable) && manuallySerializedMembers != null)
{ // This object ha an unserializable type, and previously the object was recreated from that type
// Now, restore the serialized field values of the object
FieldInfo[] fields = objectType.type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != manuallySerializedMembers.Count)
Debug.LogError ("Field length and serialized member length doesn't match (" + fields.Length + ":" + manuallySerializedMembers.Count + ")!");
foreach (FieldInfo field in fields)
{
SerializableObjectOneLevel matchObj = manuallySerializedMembers.Find ((SerializableObjectOneLevel obj) => obj.Name == field.Name && obj.Object.GetType () == field.FieldType);
if (matchObj != null)
field.SetValue (Object, matchObj.Object);
else
Debug.LogError ("Couldn't find a matching serialized field for '" + (field.IsPublic? "public" : "private") + (field.IsStatic? " static" : "") + " " + field.FieldType.FullName + "'!");
}
manuallySerializedMembers = null;
}
}
#endregion
}
/// <summary>
/// Wrapper for an arbitrary object that handles basic serialization, both System.Object, UnityEngine.Object; unserializable types will be default or null if previously null;
/// NO RECOMMENDED TO USE, it is primarily built to support SerializableObject!
/// </summary>
[Serializable]
public class SerializableObjectOneLevel : ISerializationCallbackReceiver
{
[SerializeField]
public string Name; // Just to identify this object
[NonSerialized]
public object Object;
// Serialized Data
[SerializeField]
protected bool isNullObject;
[SerializeField]
protected SerializableType objectType;
[SerializeField]
protected UnityEngine.Object unityObject;
[SerializeField]
protected string serializedSystemObject;
public SerializableObjectOneLevel (object srcObject)
{
Object = srcObject;
}
public SerializableObjectOneLevel (object srcObject, string name)
{
Object = srcObject;
Name = name;
}
#region Serialization
public void OnBeforeSerialize ()
{
Serialize ();
}
/// <summary>
/// Serializes the given object and stores it into this SerializableObject
/// </summary>
protected virtual void Serialize ()
{
if (isNullObject = Object == null)
return;
unityObject = null;
serializedSystemObject = String.Empty;
objectType = new SerializableType (Object.GetType ());
if (typeof(UnityEngine.Object).IsAssignableFrom (Object.GetType ()))
unityObject = (UnityEngine.Object)Object;
else if (Object.GetType ().IsSerializable)
serializedSystemObject = SerializeToString<System.Object> (Object);
// else default object (and even serializable members) will be restored from the type
}
public void OnAfterDeserialize ()
{
Deserialize ();
}
/// <summary>
/// Deserializes this SerializableObject
/// </summary>
protected virtual void Deserialize ()
{
Object = null;
if (isNullObject)
return;
if (unityObject != null)
Object = unityObject;
else if (!String.IsNullOrEmpty (serializedSystemObject))
Object = DeserializeFromString<System.Object> (serializedSystemObject);
else
{ // Unserializable type, it will be recreated from the type (and even it's serializable members)
if (objectType.type == null)
throw new Exception ("Could not deserialize object as it's type could no be deserialized!");
Object = Activator.CreateInstance (objectType.type);
// If it is an inherited SerializableObject then serializable members will be restored, too
}
if (Object == null)
throw new DataMisalignedException ("Could not deserialize object of type '" + objectType.type + "'!");
}
#endregion
#region Embedded Util
/// <summary>
/// Serializes 'value' to a string, using BinaryFormatter
/// </summary>
protected static string SerializeToString<T> (T value)
{
if (value == null)
return null;
using (MemoryStream stream = new MemoryStream ())
{
new BinaryFormatter ().Serialize (stream, value);
stream.Flush ();
return Convert.ToBase64String (stream.ToArray());
}
}
/// <summary>
/// Deserializes an object of type T from the string 'data'
/// </summary>
protected static T DeserializeFromString<T> (string data)
{
if (String.IsNullOrEmpty (data))
return default(T);
byte[] bytes = Convert.FromBase64String (data);
using (MemoryStream stream = new MemoryStream(bytes))
{
return (T)new BinaryFormatter().Deserialize (stream);
}
}
#endregion
}
}
namespace SerializableActionHelper
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEngine;
/// <summary>
/// Wrapper for System.Type that handles serialization.
/// Serialized Data contains assembly type name and generic arguments (one level) only.
/// </summary>
[System.Serializable]
public class SerializableType : ISerializationCallbackReceiver
{
public Type type;
[SerializeField]
private string typeName;
[SerializeField]
private string[] genericTypes;
public bool isCompilerGenerated { get { return Attribute.GetCustomAttribute (type, typeof(CompilerGeneratedAttribute), false) != null; } }
public SerializableType (Type Type)
{
type = Type;
}
#region Serialization
public void OnBeforeSerialize ()
{
if (type == null)
{
typeName = String.Empty;
genericTypes = null;
return;
}
if (type.IsGenericType)
{ // Generic type
typeName = type.GetGenericTypeDefinition ().AssemblyQualifiedName;
genericTypes = type.GetGenericArguments ().Select ((Type t) => t.AssemblyQualifiedName).ToArray ();
}
else
{ // Normal type
typeName = type.AssemblyQualifiedName;
genericTypes = null;
}
}
public void OnAfterDeserialize ()
{
if (String.IsNullOrEmpty (typeName))
return;
type = Type.GetType (typeName);
if (type == null)
throw new Exception ("Could not deserialize type '" + typeName + "'!");
if (type.IsGenericTypeDefinition && genericTypes != null && genericTypes.Length > 0)
{ // Generic type
Type[] genArgs = new Type[genericTypes.Length];
for (int i = 0; i < genericTypes.Length; i++)
genArgs[i] = Type.GetType (genericTypes[i]);
Type genType = type.MakeGenericType (genArgs);
if (genType != null)
type = genType;
else
Debug.LogError ("Could not make generic-type definition '" + typeName + "' generic!");
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.