Last active
July 7, 2021 14:01
-
-
Save Seneral/bc966cd03095946d9e635ebd89fadfac to your computer and use it in GitHub Desktop.
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; Check for updates: https://…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System; | |
using System.Collections.Generic; | |
using SerializableActionHelper; | |
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 int testInt = 62; | |
public SerializableAction unityStaticAction; | |
public SerializableAction unityGenericMethodStaticAction; | |
public SerializableAction systemStaticAction; | |
public SerializableAction systemGenericStaticAction; | |
public SerializableAction systemGenericMethodStaticAction; | |
public SerializableAction unityNormalAction; | |
public SerializableAction unityGenericMethodNormalAction; | |
public SerializableAction systemNormalAction; | |
public SerializableAction systemGenericNormalAction; | |
public SerializableAction systemGenericMethodNormalAction; | |
public SerializableAction unityAnonymousAction; | |
public SerializableAction unityGenericMethodAnonymousAction; | |
public SerializableAction unityLocalVarAnonymousAction; | |
public SerializableAction unityClassVarAnonymousAction; | |
public SerializableAction systemAnonymousAction; | |
public SerializableAction systemGenericAnonymousAction; | |
public SerializableAction systemGenericMethodAnonymousAction; | |
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 (); | |
testInt = EditorGUILayout.IntSlider ("Test Int", testInt, 0, 100); | |
ActionGUI ("Unity-Targetted ClassVar Anyonymous", ref unityClassVarAnonymousAction, () => Debug.Log ("Unity-Targetted ClassVar Anyonymous executed: " + testInt)); | |
int localInt = testInt; | |
ActionGUI ("Unity-Targetted LocalVar Anyonymous", ref unityLocalVarAnonymousAction, () => Debug.Log ("Unity-Targetted LocalVar Anyonymous executed: " + localInt)); | |
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"); | |
if (serializedAction != null && !serializedAction.IsValid ()) | |
serializedAction = null; | |
GUILayout.BeginHorizontal (); | |
if (GUILayout.Button ("Create (" + (serializedAction != null) + ")")) | |
{ | |
serializedAction = new SerializableAction (action); | |
serializedAction.Invoke (); | |
} | |
if (GUILayout.Button ("Delete")) | |
{ | |
serializedAction = null; | |
} | |
if (GUILayout.Button ("Invoke")) | |
{ | |
if (serializedAction != null) | |
serializedAction.Invoke (); | |
else | |
Debug.LogError (label + " Action is null!"); | |
} | |
GUILayout.EndHorizontal (); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
private MethodInfo _methodInfo; | |
public MethodInfo methodInfo | |
{ | |
get | |
{ | |
if (_methodInfo == null) | |
Deserialize(); | |
return _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; | |
Serialize(); | |
} | |
#region Serialization | |
public void Serialize() | |
{ | |
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 Deserialize() | |
{ | |
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace SerializableActionHelper | |
{ | |
using UnityEngine; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Runtime.Serialization.Formatters.Binary; | |
using System.Reflection; | |
/// <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<SerializableObjectTwoLevel> manuallySerializedMembers; | |
[SerializeField] | |
protected List<SerializableObjectTwoLevel> collectionObjects; | |
/// <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 (_object.GetType().IsGenericType && | |
typeof(ICollection<>).MakeGenericType(_object.GetType().GetGenericArguments()).IsAssignableFrom(_object.GetType())) | |
{ | |
IEnumerable collection = _object as IEnumerable; | |
collectionObjects = new List<SerializableObjectTwoLevel>(); | |
foreach (object obj in collection) | |
collectionObjects.Add(new SerializableObjectTwoLevel(obj)); | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(_object.GetType())) { } | |
else if (_object.GetType().IsSerializable) { } | |
else | |
{ // 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 = new List<SerializableObjectTwoLevel>(); | |
foreach (FieldInfo field in fields) | |
manuallySerializedMembers.Add(new SerializableObjectTwoLevel(field.GetValue(_object), field.Name)); | |
//manuallySerializedMembers = fields.Select ((FieldInfo field) => new SerializableObjectOneLevel (field.GetValue (_object), field.Name)).ToList (); | |
} | |
} | |
/// <summary> | |
/// Deserializes this SerializableObject | |
/// </summary> | |
protected override void Deserialize() | |
{ | |
if (isNullObject) | |
return; | |
base.Deserialize(); // Deserialize normally | |
Type type = objectType.type; | |
if (type.IsGenericType && | |
typeof(ICollection<>).MakeGenericType(type.GetGenericArguments()).IsAssignableFrom(type)) | |
{ | |
if (collectionObjects != null && collectionObjects.Count > 0) | |
{ // Add deserialized objects to collection | |
MethodInfo add = type.GetMethod("Add"); | |
foreach (SerializableObjectTwoLevel obj in collectionObjects) | |
add.Invoke(_object, new object[] { obj.Object }); | |
} | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(type)) | |
_object = unityObject; | |
else if (type.IsSerializable) | |
_object = DeserializeFromString<System.Object>(serializedSystemObject); | |
else if (manuallySerializedMembers != null && manuallySerializedMembers.Count > 0) | |
{ // This object is 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 + ") for object " + objectType.type.Name + "!"); | |
foreach (FieldInfo field in fields) | |
{ | |
SerializableObjectTwoLevel matchObj = manuallySerializedMembers.Find((SerializableObjectTwoLevel obj) => obj.Name == field.Name); | |
if (matchObj != null) | |
{ | |
object obj = null; | |
if (matchObj.Object == null) { } | |
else if (!field.FieldType.IsAssignableFrom(matchObj.Object.GetType())) | |
Debug.LogWarning("Deserialized object type " + matchObj.Object.GetType().Name + " is incompatible to field type " + field.FieldType.Name + "!"); | |
else | |
obj = matchObj.Object; | |
field.SetValue(Object, obj); | |
} | |
else | |
Debug.LogWarning("Couldn't find a matching serialized field for '" + (field.IsPublic ? "public" : "private") + (field.IsStatic ? " static" : "") + " " + field.FieldType.FullName + "'!"); | |
} | |
} | |
} | |
#endregion | |
} | |
/// <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 SerializableObjectTwoLevel : SerializableObjectOneLevel | |
{ | |
[SerializeField] | |
private List<SerializableObjectOneLevel> manuallySerializedMembers; | |
[SerializeField] | |
protected List<SerializableObjectOneLevel> collectionObjects; | |
/// <summary> | |
/// Create a new SerializableObject from an arbitrary object | |
/// </summary> | |
public SerializableObjectTwoLevel (object srcObject) : base (srcObject) { } | |
/// <summary> | |
/// Create a new SerializableObject from an arbitrary object with the specified name | |
/// </summary> | |
public SerializableObjectTwoLevel (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 (_object.GetType().IsGenericType && | |
typeof(ICollection<>).MakeGenericType(_object.GetType().GetGenericArguments()).IsAssignableFrom(_object.GetType())) | |
{ | |
IEnumerable collection = _object as IEnumerable; | |
collectionObjects = new List<SerializableObjectOneLevel>(); | |
foreach (object obj in collection) | |
collectionObjects.Add(new SerializableObjectOneLevel(obj)); | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(_object.GetType())) { } | |
else if (_object.GetType().IsSerializable) { } | |
else | |
{ // 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 = new List<SerializableObjectOneLevel>(); | |
foreach (FieldInfo field in fields) | |
manuallySerializedMembers.Add(new SerializableObjectOneLevel(field.GetValue(_object), field.Name)); | |
//manuallySerializedMembers = fields.Select ((FieldInfo field) => new SerializableObjectOneLevel (field.GetValue (_object), field.Name)).ToList (); | |
} | |
} | |
/// <summary> | |
/// Deserializes this SerializableObject | |
/// </summary> | |
protected override void Deserialize () | |
{ | |
if (isNullObject) | |
return; | |
base.Deserialize (); // Deserialize normally | |
Type type = objectType.type; | |
if (type.IsGenericType && | |
typeof(ICollection<>).MakeGenericType(type.GetGenericArguments()).IsAssignableFrom(type)) | |
{ | |
if (collectionObjects != null && collectionObjects.Count > 0) | |
{ // Add deserialized objects to collection | |
MethodInfo add = type.GetMethod("Add"); | |
foreach (SerializableObjectOneLevel obj in collectionObjects) | |
add.Invoke(_object, new object[] { obj.Object }); | |
} | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(type)) | |
_object = unityObject; | |
else if (type.IsSerializable) | |
_object = DeserializeFromString<System.Object>(serializedSystemObject); | |
else if (manuallySerializedMembers != null && manuallySerializedMembers.Count > 0) | |
{ // This object is 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 + ") for object " + objectType.type.Name + "!"); | |
foreach (FieldInfo field in fields) | |
{ | |
SerializableObjectOneLevel matchObj = manuallySerializedMembers.Find ((SerializableObjectOneLevel obj) => obj.Name == field.Name); | |
if (matchObj != null) | |
{ | |
object obj = null; | |
if (matchObj.Object == null) { } | |
else if (!field.FieldType.IsAssignableFrom(matchObj.Object.GetType())) | |
Debug.LogWarning("Deserialized object type " + matchObj.Object.GetType().Name + " is incompatible to field type " + field.FieldType.Name + "!"); | |
else | |
obj = matchObj.Object; | |
field.SetValue(Object, obj); | |
} | |
else | |
Debug.LogWarning("Couldn't find a matching serialized field for '" + (field.IsPublic ? "public" : "private") + (field.IsStatic ? " static" : "") + " " + field.FieldType.FullName + "'!"); | |
} | |
} | |
} | |
#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 | |
{ | |
[SerializeField] | |
public string Name; // Just to identify this object | |
protected object _object; | |
public object Object | |
{ | |
get | |
{ | |
if (_object == null) | |
Deserialize(); | |
return _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; | |
Serialize(); | |
} | |
public SerializableObjectOneLevel(object srcObject, string name) | |
{ | |
_object = srcObject; | |
Name = name; | |
Serialize(); | |
} | |
#region Serialization | |
/// <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 (_object.GetType().IsGenericType && | |
typeof(ICollection<>).MakeGenericType(_object.GetType().GetGenericArguments()).IsAssignableFrom(_object.GetType())) | |
{ // If levels are free to serialize, then they will get serialized, if not, then not | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(_object.GetType())) | |
{ | |
unityObject = (UnityEngine.Object)_object; | |
} | |
else if (_object.GetType().IsSerializable) | |
{ | |
serializedSystemObject = SerializeToString<System.Object>(_object); | |
if (serializedSystemObject == null) | |
Debug.LogWarning("Failed to serialize field name " + Name + "!"); | |
} | |
// else default object (and even serializable members) will be restored from the type | |
} | |
/// <summary> | |
/// Deserializes this SerializableObject | |
/// </summary> | |
protected virtual void Deserialize () | |
{ | |
_object = null; | |
if (isNullObject) | |
return; | |
if (objectType.type == null) | |
throw new Exception("Could not deserialize object as it's type could no be deserialized!"); | |
Type type = objectType.type; | |
if (type.IsGenericType && | |
typeof(ICollection<>).MakeGenericType(type.GetGenericArguments()).IsAssignableFrom(type)) | |
{ // Collection type, if still more levels free, members will be serialized, if not, then not | |
_object = Activator.CreateInstance(type); | |
} | |
else if (typeof(UnityEngine.Object).IsAssignableFrom(type)) | |
_object = unityObject; | |
else if (type.IsSerializable) | |
_object = DeserializeFromString<System.Object>(serializedSystemObject); | |
else | |
{ // Unserializable type, it will be recreated from the type (and even it's serializable members) | |
_object = Activator.CreateInstance(type); | |
} | |
// Not always critical. Can happen if GC or Unity deleted those references | |
// if (_object == null) | |
// Debug.LogWarning ("Could not deserialize object of type '" + type.Name + "'!"); | |
} | |
#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; | |
try | |
{ | |
using (MemoryStream stream = new MemoryStream()) | |
{ | |
new BinaryFormatter().Serialize(stream, value); | |
stream.Flush(); | |
return Convert.ToBase64String(stream.ToArray()); | |
} | |
} | |
catch (System.Runtime.Serialization.SerializationException) | |
{ | |
Debug.LogWarning("Failed to serialize " + value.GetType().ToString()); | |
return null; | |
} | |
} | |
/// <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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
public Type _type; | |
public Type type | |
{ | |
get | |
{ | |
if (_type == null) | |
Deserialize(); | |
return _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; | |
Serialize(); | |
} | |
#region Serialization | |
public void Serialize () | |
{ | |
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 Deserialize () | |
{ | |
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