/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.StateSystem { using Opsive.Shared.Utility; using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; /// /// Represents a set of values for any number of component properties. In order for the value to be applied to a property a getter and setter must exist, /// along with a derived class from BaseDelegate which creates the delegate which interfaces with the property getter and setter. Properties can be /// ignored with the [Opsive.UltimateCharacterController.Opsive.Shared.Utility.NonSerialized] attribute. /// public class Preset : ScriptableObject { protected BaseDelegate[] m_Delegates; public bool IsInitialized { get { return m_Delegates != null; } } public BaseDelegate[] Delegates { get { return m_Delegates; } } /// /// Creates a preset based off of the specified component. /// /// The object to retrieve the property values of. /// The created preset. Null if no properties have been found to create the preset with. public static Preset CreatePreset() { var preset = CreateInstance(); preset.hideFlags = HideFlags.HideAndDontSave; return preset; } /// /// Initializes the preset. The preset must be initialized before the preset values are applied so the delegates can be created. /// /// The object to map the delegates to. public void Initialize(object obj) { Initialize(obj, MemberVisibility.Public); } /// /// Initializes the preset with the specified visiblity. The preset must be initialized before the preset values are applied so the delegates can be created. /// /// The object to map the delegates to. /// Specifies the visibility of the field/properties that should be retrieved. public virtual void Initialize(object obj, MemberVisibility visibility) { var properties = Serialization.GetSerializedProperties(obj.GetType(), visibility); var valueCount = 0; m_Delegates = new BaseDelegate[properties.Length]; for (int i = 0; i < properties.Length; ++i) { // The property may not be valid. if (Serialization.GetValidGetMethod(properties[i], visibility) == null) { continue; } // Create a generic delegate based on the property type. var genericDelegateType = typeof(GenericDelegate<>).MakeGenericType(properties[i].PropertyType); m_Delegates[valueCount] = Activator.CreateInstance(genericDelegateType) as BaseDelegate; // Initialize the delegate. if (m_Delegates[valueCount] != null) { m_Delegates[valueCount].Initialize(obj, properties[i], visibility); } else { Debug.LogWarning("Warning: Unable to create preset of type " + properties[i].PropertyType); } valueCount++; } if (m_Delegates.Length != valueCount) { Array.Resize(ref m_Delegates, valueCount); } } /// /// Updates the stored value with the current property value. /// public virtual void UpdateValue() { for (int i = 0; i < m_Delegates.Length; ++i) { m_Delegates[i].UpdateValue(); } } /// /// Applies the values to the component. /// public void ApplyValues() { for (int i = 0; i < m_Delegates.Length; ++i) { m_Delegates[i].ApplyValue(); } } /// /// Applies the values to the component specified by the delegates. /// /// The properties that were changed. public virtual void ApplyValues(BaseDelegate[] delegates) { } /// /// Abstract class which allows for a delegate to be created which can be called on when the preset value should be applied. /// [UnityEngine.Scripting.Preserve] public abstract class BaseDelegate { public abstract MethodInfo SetMethod { get; } /// /// Initialize the delegate and value. /// /// The object which the delegate operates on. /// The property that the delegate will invoke. /// A mapping between the value hash and position. /// The serialization data which contains the values for the property (as well as all other properties). /// Specifies the visibility of the field/properties that should be retrieved. public abstract void Initialize(object obj, PropertyInfo property, Dictionary valuePositionMap, Serialization data, MemberVisibility visibility); /// /// Initialize the delegate. /// /// The object which the delegate operates on. /// The property that the delegate will invoke. /// Specifies the visibility of the field/properties that should be retrieved. public abstract void Initialize(object obj, PropertyInfo property, MemberVisibility visibility); /// /// Updates the stored value with the current property value. /// /// The object which the delegate operates on. /// The property that the delegate will invoke. /// Specifies the visibility of the field/properties that should be retrieved. public abstract void UpdateValue(); /// /// Applies the preset value to the delegate. /// public abstract void ApplyValue(); } /// /// Generic class which implements a type specific delegate and value that can be called on when the preset value should be applied. /// See AOTLinker for an explanation of why a different class name is used for AOT platforms. /// [UnityEngine.Scripting.Preserve] public class GenericDelegate : BaseDelegate { private T m_Value; private MethodInfo m_SetMethod; private Action m_Setter; private Func m_Getter; private bool m_IsIList; public override MethodInfo SetMethod { get { return m_SetMethod; } } /// /// Initialize the delegate and value. /// /// The object which the delegate operates on. /// The property that the delegate will invoke. /// A mapping between the value hash and position. /// The serialization data which contains the values for the property (as well as all other properties). /// Specifies the visibility of the field/properties that should be retrieved. public override void Initialize(object obj, PropertyInfo property, Dictionary valuePositionMap, Serialization data, MemberVisibility visibility) { m_SetMethod = property.GetSetMethod(visibility != MemberVisibility.Public); if (m_SetMethod != null) { m_Setter = (Action)Delegate.CreateDelegate(typeof(Action), obj, m_SetMethod); var bitwiseHash = new Version(data.Version).CompareTo(new Version("3.1")) >= 0; var value = Serializer.BytesToValue(typeof(T), property.Name, valuePositionMap, 0, data.Values, data.ValuePositions, data.UnityObjects, false, visibility, bitwiseHash); if (value != null && !value.Equals(null)) { m_Value = (T)value; } var type = typeof(T); m_IsIList = typeof(IList).IsAssignableFrom(type); if (m_IsIList) { // The Get method only needs to be assigned if the type is an IList because the actual object isn't copied by reference for arrays. // Each individual element within the array needs to be interated on. var getMethod = property.GetGetMethod(visibility != MemberVisibility.Public); if (getMethod != null) { m_Getter = (Func)Delegate.CreateDelegate(typeof(Func), obj, getMethod); } } } } /// /// Initialize the delegate. /// /// The object which the delegate operates on. /// The property that the delegate will invoke. /// Specifies the visibility of the field/properties that should be retrieved. public override void Initialize(object obj, PropertyInfo property, MemberVisibility visibility) { m_SetMethod = property.GetSetMethod(visibility != MemberVisibility.Public); if (m_SetMethod != null) { m_Setter = (Action)Delegate.CreateDelegate(typeof(Action), obj, m_SetMethod); } var getMethod = property.GetGetMethod(visibility != MemberVisibility.Public); if (getMethod != null) { m_Getter = (Func)Delegate.CreateDelegate(typeof(Func), obj, getMethod); // Create an instance of the value if it is an array or a list. This will allow a snapshot of the array/list elements to be saved without having the // array/list change because it is later modified by reference. var type = typeof(T); m_IsIList = typeof(IList).IsAssignableFrom(type); if (m_IsIList) { if (typeof(T).IsArray) { var value = m_Getter() as Array; m_Value = (T)(object)Array.CreateInstance(type.GetElementType(), value == null ? 0 : value.Length); } else { var baseType = type; while (!baseType.IsGenericType) { baseType = baseType.BaseType; } var elementType = baseType.GetGenericArguments()[0]; if (type.IsGenericType) { m_Value = (T)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); } else { m_Value = (T)Activator.CreateInstance(type); } } } // The value should be set at the same time the delegate is initailized. UpdateValue(); } } /// /// Updates the stored value with the current property value. /// public override void UpdateValue() { if (m_Getter == null) { Debug.LogError("Error: Unable to retrieve an updated value - the Get method is null."); return; } // Update the individual elements if the value is a list or an array. This will prevent the array/list from being changed because it is // later modified by reference. if (m_IsIList) { UpdateIList(m_Getter(), m_Value); } else { // Not an array/list. m_Value = m_Getter(); } } /// /// Applies the preset value to the delegate. /// public override void ApplyValue() { if (m_IsIList) { UpdateIList(m_Value, m_Getter()); } else { m_Setter(m_Value); } } /// /// Updates the source array/list elements to the destination elements. /// /// The array/list to copy the references from. /// The array/list to copy the references to. private void UpdateIList(T source, T destination) { var type = typeof(T); if (type.IsArray) { var sourceArray = source as Array; var destinationArray = destination as Array; if (sourceArray != null && destinationArray != null) { // The array sizes need to match. if (destinationArray != null && sourceArray.Length != destinationArray.Length) { destinationArray = Array.CreateInstance(typeof(T).GetElementType(), sourceArray.Length); // There's no way to avoid the boxing/unboxing. It is recommended that arrays are not changed to avoid this. m_Setter((T)(object)destinationArray); } for (int i = 0; i < sourceArray.Length; ++i) { destinationArray.SetValue(sourceArray.GetValue(i), i); } } } else { var sourceList = source as IList; var destinationList = destination as IList; // Remove any extra elements. if (destinationList.Count > sourceList.Count) { var removeCount = destinationList.Count - sourceList.Count; for (int i = 0; i < removeCount; ++i) { destinationList.RemoveAt(destinationList.Count - 1); } } // Update the element referance. for (int i = 0; i < sourceList.Count; ++i) { if (i < destinationList.Count) { destinationList[i] = sourceList[i]; } else { destinationList.Add(sourceList[i]); } } } } } } }