// UltEvents // Copyright 2020 Kybernetik //
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// The type identifier of a .
public enum PersistentArgumentType
{
/// Type not set.
None,
// Uses _Int 0 or 1 as bool.
/// .
Bool,
// Uses _String.
/// .
String,
// Uses _Int.
/// .
Int,
// Uses _Int for the value and _String for the assembly qualified name of the type.
/// Any kind of .
Enum,
// Uses _X.
/// .
Float,
// Uses _X and _Y.
/// .
Vector2,
// Uses _X, _Y, and _Z.
/// .
Vector3,
// Uses _X, _Y, _Z, and _W.
/// .
Vector4,
// Uses _X, _Y, and _Z to store the euler angles.
/// .
Quaternion,
// Uses _X, _Y, _Z, and _W as RGBA.
/// .
Color,
// Uses _Int to hold the RGBA bytes.
/// .
Color32,
// Uses _X, _Y, _Z, and _W as X, Y, Width, Height.
/// .
Rect,
// Uses _Object for the value and _String for the assembly qualified name of the type.
/// .
Object,
// Uses _Int for the index of the target parameter.
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
// Otherwise the assembly qualified name of the type is stored in _String.
/// The value of a parameter passed to the event.
Parameter,
// Uses _Int for the index of the target call.
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
// Otherwise the assembly qualified name of the type is stored in _String.
/// The return value by a previous .
ReturnValue,
}
///
/// Encapsulates a variable so it can be serialized for .
///
[Serializable]
public sealed class PersistentArgument
{
/************************************************************************************************************************/
#region Fields
/************************************************************************************************************************/
[SerializeField]
internal PersistentArgumentType _Type;
[SerializeField]
internal int _Int;
[SerializeField]
internal string _String;
[SerializeField]
internal float _X, _Y, _Z, _W;
[SerializeField]
internal Object _Object;
/************************************************************************************************************************/
[NonSerialized]
private Type _SystemType;
[NonSerialized]
internal bool _HasSystemType;
[NonSerialized]
private object _Value;
/************************************************************************************************************************/
/// Constructs a new with default values.
public PersistentArgument() { }
/// Constructs a new with the specified `type`.
public PersistentArgument(Type type)
{
_Type = GetArgumentType(type, out _String, out _Int);
_SystemType = type;
_HasSystemType = true;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Properties
/************************************************************************************************************************/
/// The type identifier of this argument.
public PersistentArgumentType Type
{
get { return _Type; }
internal set
{
_Int = 0;
_X = _Y = _Z = _W = 0;
_String = "";
_Object = null;
_Type = value;
_HasSystemType = false;
_Value = null;
}
}
/************************************************************************************************************************/
///
/// The of this argument.
///
public Type SystemType
{
get
{
#if !UNITY_EDITOR// Ignore cache in the editor since it complicates the inspector GUI code.
if (!_HasSystemType)
#endif
{
_SystemType = GetArgumentType(_Type, _X, _String);
_HasSystemType = true;
}
return _SystemType;
}
internal set
{
// Can't pass _String and _Int in directly because setting the Type clears them.
string assemblyQualifiedName;
int linkIndex;
Type = GetArgumentType(value, out assemblyQualifiedName, out linkIndex);
_String = assemblyQualifiedName;
_Int = linkIndex;
_HasSystemType = true;
_SystemType = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public bool Bool
{
get
{
AssertType(PersistentArgumentType.Bool);
return _Int != 0;
}
set
{
AssertType(PersistentArgumentType.Bool);
_Int = value ? 1 : 0;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public string String
{
get
{
AssertType(PersistentArgumentType.String);
return _String;
}
set
{
AssertType(PersistentArgumentType.String);
_String = value ?? "";
_Value = value;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public int Int
{
get
{
AssertType(PersistentArgumentType.Int);
return _Int;
}
set
{
AssertType(PersistentArgumentType.Int);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public object Enum
{
get
{
AssertType(PersistentArgumentType.Enum);
return System.Enum.ToObject(SystemType, _Int);
}
set
{
AssertType(PersistentArgumentType.Enum);
_Int = (int)value;
_Value = value;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public float Float
{
get
{
AssertType(PersistentArgumentType.Float);
return _X;
}
set
{
AssertType(PersistentArgumentType.Float);
_X = value;
_Value = null;
}
}
/// The value of this argument.
public Vector2 Vector2
{
get
{
AssertType(PersistentArgumentType.Vector2);
return new Vector2(_X, _Y);
}
set
{
AssertType(PersistentArgumentType.Vector2);
_X = value.x;
_Y = value.y;
_Value = null;
}
}
/// The value of this argument.
public Vector3 Vector3
{
get
{
AssertType(PersistentArgumentType.Vector3);
return new Vector3(_X, _Y, _Z);
}
set
{
AssertType(PersistentArgumentType.Vector3);
_X = value.x;
_Y = value.y;
_Z = value.z;
_Value = null;
}
}
/// The value of this argument.
public Vector4 Vector4
{
get
{
AssertType(PersistentArgumentType.Vector4);
return new Vector4(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Vector4);
_X = value.x;
_Y = value.y;
_Z = value.z;
_W = value.w;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public Quaternion Quaternion
{
// It would be better to store the components of the Quaternion directly instead of the euler angles,
// but floating point imprecision when converting between them to show the euler angles in the inspector
// means that changes to any one axis will have a small effect on the other axes as well.
// This could be handled like the [Euler] attribute, but that still leads to small inaccuracies in the
// displayed values when they are deserialized so storing the euler angles directly is more user-friendly.
get
{
AssertType(PersistentArgumentType.Quaternion);
return Quaternion.Euler(_X, _Y, _Z);
}
set
{
AssertType(PersistentArgumentType.Quaternion);
var euler = value.eulerAngles;
_X = euler.x;
_Y = euler.y;
_Z = euler.z;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public Color Color
{
get
{
AssertType(PersistentArgumentType.Color);
return new Color(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Color);
_X = value.r;
_Y = value.g;
_Z = value.b;
_W = value.a;
_Value = null;
}
}
/// The value of this argument.
public Color32 Color32
{
get
{
AssertType(PersistentArgumentType.Color32);
return new Color32((byte)(_Int), (byte)(_Int >> 8), (byte)(_Int >> 16), (byte)(_Int >> 24));
}
set
{
AssertType(PersistentArgumentType.Color32);
_Int = value.r | (value.g << 8) | (value.b << 16) | (value.a << 24);
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public Rect Rect
{
get
{
AssertType(PersistentArgumentType.Rect);
return new Rect(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Rect);
_X = value.x;
_Y = value.y;
_Z = value.width;
_W = value.height;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public Object Object
{
get
{
AssertType(PersistentArgumentType.Object);
// Unity's fake nulls cause problems if the argument is a child type of UnityEngine.Object.
// For example, when invoking a MonoBehaviour parameter a fake null Object would fail to convert to MonoBehaviour.
// So we make sure to return actual null instead of any fake value.
if (_Object == null)
return null;
else
return _Object;
}
set
{
AssertType(PersistentArgumentType.Object);
_Object = value;
_String = value != null ? value.GetType().AssemblyQualifiedName : "";
_Value = value;
}
}
/************************************************************************************************************************/
/// The value of a parameter passed into the (see .
public object Parameter
{
get
{
AssertType(PersistentArgumentType.Parameter);
return UltEventBase.GetParameterValue(_Int);
}
}
/// The index of the parameter passed into the .
public int ParameterIndex
{
get
{
AssertType(PersistentArgumentType.Parameter);
return _Int;
}
set
{
AssertType(PersistentArgumentType.Parameter);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value returned by a previous (see .
public object ReturnedValue
{
get
{
AssertType(PersistentArgumentType.ReturnValue);
return UltEventBase.GetReturnedValue(_Int);
}
}
/// The index of the which returns the value for this argument.
public int ReturnedValueIndex
{
get
{
AssertType(PersistentArgumentType.ReturnValue);
return _Int;
}
set
{
AssertType(PersistentArgumentType.ReturnValue);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// The value of this argument.
public object Value
{
get
{
if (_Value == null)
{
switch (_Type)
{
case PersistentArgumentType.Bool: _Value = Bool; break;
case PersistentArgumentType.String: _Value = String; break;
case PersistentArgumentType.Int: _Value = Int; break;
case PersistentArgumentType.Enum: _Value = Enum; break;
case PersistentArgumentType.Float: _Value = Float; break;
case PersistentArgumentType.Vector2: _Value = Vector2; break;
case PersistentArgumentType.Vector3: _Value = Vector3; break;
case PersistentArgumentType.Vector4: _Value = Vector4; break;
case PersistentArgumentType.Quaternion: _Value = Quaternion; break;
case PersistentArgumentType.Color: _Value = Color; break;
case PersistentArgumentType.Color32: _Value = Color32; break;
case PersistentArgumentType.Rect: _Value = Rect; break;
case PersistentArgumentType.Object: _Value = Object; break;
// Don't cache parameters or returned values.
case PersistentArgumentType.Parameter: return Parameter;
case PersistentArgumentType.ReturnValue: return ReturnedValue;
default:
throw new InvalidOperationException(
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
}
}
return _Value;
}
set
{
switch (_Type)
{
case PersistentArgumentType.Bool: Bool = (bool)value; break;
case PersistentArgumentType.String: String = (string)value; break;
case PersistentArgumentType.Int: Int = (int)value; break;
case PersistentArgumentType.Enum: Enum = value; break;
case PersistentArgumentType.Float: Float = (float)value; break;
case PersistentArgumentType.Vector2: Vector2 = (Vector2)value; break;
case PersistentArgumentType.Vector3: Vector3 = (Vector3)value; break;
case PersistentArgumentType.Vector4: Vector4 = (Vector4)value; break;
case PersistentArgumentType.Quaternion: Quaternion = (Quaternion)value; break;
case PersistentArgumentType.Color: Color = (Color)value; break;
case PersistentArgumentType.Color32: Color32 = (Color32)value; break;
case PersistentArgumentType.Rect: Rect = (Rect)value; break;
case PersistentArgumentType.Object: Object = (Object)value; break;
// Don't cache parameters or returned values.
case PersistentArgumentType.Parameter: ParameterIndex = (int)value; return;
case PersistentArgumentType.ReturnValue: ReturnedValueIndex = (int)value; return;
default:
throw new InvalidOperationException(
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
}
_Value = value;
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Methods
/************************************************************************************************************************/
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void AssertType(PersistentArgumentType type)
{
if (_Type != type)
throw new InvalidOperationException(Names.PersistentArgument.Full.Type + " is " + _Type + " but should be " + type);
}
/************************************************************************************************************************/
#if UNITY_EDITOR
internal void ClearCache()
{
_Value = null;
}
#endif
/************************************************************************************************************************/
///
/// Returns the associated with the specified .
///
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be used to get the type.
///
public static Type GetArgumentType(PersistentArgumentType type, float secondaryType, string assemblyQualifiedName)
{
switch (type)
{
case PersistentArgumentType.Bool: return typeof(bool);
case PersistentArgumentType.String: return typeof(string);
case PersistentArgumentType.Int: return typeof(int);
case PersistentArgumentType.Float: return typeof(float);
case PersistentArgumentType.Vector2: return typeof(Vector2);
case PersistentArgumentType.Vector3: return typeof(Vector3);
case PersistentArgumentType.Vector4: return typeof(Vector4);
case PersistentArgumentType.Quaternion: return typeof(Quaternion);
case PersistentArgumentType.Color: return typeof(Color);
case PersistentArgumentType.Color32: return typeof(Color32);
case PersistentArgumentType.Rect: return typeof(Rect);
case PersistentArgumentType.Enum:
case PersistentArgumentType.Object:
default:
if (!string.IsNullOrEmpty(assemblyQualifiedName))
return System.Type.GetType(assemblyQualifiedName);
else
return null;
case PersistentArgumentType.Parameter:
case PersistentArgumentType.ReturnValue:
if (!string.IsNullOrEmpty(assemblyQualifiedName))
return System.Type.GetType(assemblyQualifiedName);
else
return GetArgumentType((PersistentArgumentType)secondaryType, -1, null);
case PersistentArgumentType.None:
return null;
}
}
///
/// Returns the associated with the specified .
///
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be assigned as well (otherwise null).
///
public static PersistentArgumentType GetArgumentType(Type type, out string assemblyQualifiedName, out int linkIndex)
{
linkIndex = 0;
assemblyQualifiedName = null;
if (type == typeof(bool)) return PersistentArgumentType.Bool;
else if (type == typeof(string)) return PersistentArgumentType.String;
else if (type == typeof(int)) return PersistentArgumentType.Int;
else if (type == typeof(float)) return PersistentArgumentType.Float;
else if (type == typeof(Vector2)) return PersistentArgumentType.Vector2;
else if (type == typeof(Vector3)) return PersistentArgumentType.Vector3;
else if (type == typeof(Vector4)) return PersistentArgumentType.Vector4;
else if (type == typeof(Quaternion)) return PersistentArgumentType.Quaternion;
else if (type == typeof(Color)) return PersistentArgumentType.Color;
else if (type == typeof(Color32)) return PersistentArgumentType.Color32;
else if (type == typeof(Rect)) return PersistentArgumentType.Rect;
else if (type.IsEnum)
{
if (System.Enum.GetUnderlyingType(type) == typeof(int))
{
assemblyQualifiedName = type.AssemblyQualifiedName;
return PersistentArgumentType.Enum;
}
else return PersistentArgumentType.None;
}
else if (type == typeof(Object) || type.IsSubclassOf(typeof(Object)))
{
assemblyQualifiedName = type.AssemblyQualifiedName;
return PersistentArgumentType.Object;
}
else
{
assemblyQualifiedName = type.AssemblyQualifiedName;
#if UNITY_EDITOR
PersistentArgumentType linkType;
if (Editor.DrawerState.Current.TryGetLinkable(type, out linkIndex, out linkType))
return linkType;
#endif
return PersistentArgumentType.ReturnValue;
}
}
/************************************************************************************************************************/
/// Creates an exact copy of this argument.
public PersistentArgument Clone()
{
#pragma warning disable IDE0017 // Simplify object initialization
var clone = new PersistentArgument();
#pragma warning restore IDE0017 // Simplify object initialization
clone._Type = _Type;
clone._Int = _Int;
clone._String = _String;
clone._X = _X;
clone._Y = _Y;
clone._Z = _Z;
clone._W = _W;
clone._Object = _Object;
clone._SystemType = _SystemType;
clone._HasSystemType = _HasSystemType;
clone._Value = _Value;
return clone;
}
/************************************************************************************************************************/
/// Returns a string which describes this argument.
public override string ToString()
{
switch (_Type)
{
case PersistentArgumentType.None:
return Names.PersistentArgument.Class + ": Type=None";
case PersistentArgumentType.Bool:
case PersistentArgumentType.String:
case PersistentArgumentType.Int:
case PersistentArgumentType.Enum:
case PersistentArgumentType.Float:
case PersistentArgumentType.Vector2:
case PersistentArgumentType.Vector3:
case PersistentArgumentType.Vector4:
case PersistentArgumentType.Quaternion:
case PersistentArgumentType.Color:
case PersistentArgumentType.Color32:
case PersistentArgumentType.Rect:
case PersistentArgumentType.Object:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=" + Value;
case PersistentArgumentType.Parameter:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=Parameter" + ParameterIndex;
case PersistentArgumentType.ReturnValue:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=ReturnValue" + ReturnedValueIndex;
default:
Debug.LogWarning("Unhandled " + Names.PersistentArgumentType);
return base.ToString();
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}