//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace BansheeEngine { /** @addtogroup Serialization * @{ */ /// /// Allows you to transparently retrieve and set values of an entry, whether that entry is an object field or /// an array/list/dictionary entry. /// public sealed class SerializableProperty : ScriptObject { /// /// Object field types support by the serializable property. /// public enum FieldType { Int, Float, Bool, String, Color, Vector2, Vector3, Vector4, GameObjectRef, Resource, Object, Array, List, Dictionary, RRef, ColorGradient, Curve, FloatDistribution, ColorDistribution, Quaternion, Enum, Vector2Distribution, Vector3Distribution } public delegate object Getter(); public delegate void Setter(object value); private FieldType type; private Type internalType; private Getter getter; private Setter setter; /// /// Constructor for internal use by the native code. /// private SerializableProperty() { } /// /// Creates a new serializable property. /// /// Type of data the property contains. /// Type of data the property contains, as C# type. /// Method that allows you to retrieve contents of the property. /// Method that allows you to set contents of the property public SerializableProperty(FieldType type, Type internalType, Getter getter, Setter setter) { this.type = type; this.internalType = internalType; this.getter = getter; this.setter = setter; Internal_CreateInstance(this, internalType); } /// /// Finalizes construction of the serializable property. Must be called after creation. /// /// Type of data the property contains. /// Type of data the property contains, as C# type. /// Method that allows you to retrieve contents of the property. /// Method that allows you to set contents of the property internal void Construct(FieldType type, Type internalType, Getter getter, Setter setter) { this.type = type; this.internalType = internalType; this.getter = getter; this.setter = setter; } /// /// Returns type of data the property contains. /// public FieldType Type { get { return type; } } /// /// Returns type of data the property contains, as C# type. /// public Type InternalType { get { return internalType; } } /// /// Is the containing type a value type (true), or a reference type (false). /// public bool IsValueType { get { return internalType.IsValueType; } } /// /// Retrieves the value contained in the property. /// /// Type of the value to retrieve. Caller must ensure the type matches the property type. /// Value of the property. public T GetValue() { // Cast if possible if (typeof(T) != internalType) { // Note: Not checking cast operators if (internalType.IsPrimitive || internalType.IsEnum) { if (internalType == typeof(bool) || typeof(T) == typeof(bool)) throw new Exception("Attempted to retrieve a serializable value using an invalid type. " + "Provided type: " + typeof(T) + ". Needed type: " + internalType); return (T) Convert.ChangeType(getter(), typeof(T)); } if(!typeof(T).IsAssignableFrom(internalType)) throw new Exception("Attempted to retrieve a serializable value using an invalid type. " + "Provided type: " + typeof(T) + ". Needed type: " + internalType); } return (T)getter(); } /// /// Changes the value of the property. /// /// Type of the value to set. Caller must ensure the type matches the property type. /// New value to assign to the property. public void SetValue(T value) { // Cast if possible if (typeof(T) != internalType) { // Note: Not checking cast operators if (internalType.IsPrimitive || internalType.IsEnum) { if (internalType == typeof(bool) || typeof(T) == typeof(bool)) throw new Exception("Attempted to set a serializable value using an invalid type. " + "Provided type: " + typeof(T) + ". Needed type: " + internalType); setter((T) Convert.ChangeType(getter(), typeof(T))); return; } if (!typeof(T).IsAssignableFrom(internalType)) throw new Exception("Attempted to set a serializable value using an invalid type. " + "Provided type: " + typeof(T) + ". Needed type: " + internalType); } setter(value); } /// /// Returns a serializable object wrapper around the value contained in the property. /// /// Serializable object wrapper around the value contained in the property. public SerializableObject GetObject() { if (type != FieldType.Object) throw new Exception("Attempting to retrieve object information from a field that doesn't contain an object."); // Get the most-derived type so the serializable object lists the exact fields object obj = GetValue(); Type actualType = obj != null ? obj.GetType() : internalType; return Internal_CreateObject(mCachedPtr, this, actualType); } /// /// Returns a serializable array around the value contained in the property. Caller must ensure the property /// contains an array. /// /// Serializable array around the value contained in the property. public SerializableArray GetArray() { if (type != FieldType.Array) throw new Exception("Attempting to retrieve array information from a field that doesn't contain an array."); return Internal_CreateArray(mCachedPtr, this); } /// /// Returns a serializable list around the value contained in the property. Caller must ensure the property /// contains a list. /// /// Serializable list around the value contained in the property. public SerializableList GetList() { if (type != FieldType.List) throw new Exception("Attempting to retrieve array information from a field that doesn't contain a list."); return Internal_CreateList(mCachedPtr, this); } /// /// Returns a serializable dictionary around the value contained in the property. Caller must ensure the property /// contains a dictionary. /// /// Serializable dictionary around the value contained in the property. public SerializableDictionary GetDictionary() { if (type != FieldType.Dictionary) throw new Exception("Attempting to retrieve array information from a field that doesn't contain a dictionary."); return Internal_CreateDictionary(mCachedPtr, this); } /// /// Creates a new instance of the type wrapped by this property. /// /// Type of the object to create. Caller must ensure the type matches the property type and that /// the property wraps an object. /// A new instance of an object of type . public T CreateObjectInstance() { if (type != FieldType.Object) throw new Exception("Attempting to retrieve object information from a field that doesn't contain an object."); return (T)Internal_CreateManagedObjectInstance(mCachedPtr); } /// /// Creates a new instance of the array wrapped by this property. Caller must ensure this property contains an array. /// /// Size of each dimension of the array. Number of dimensions must match the number /// of dimensions in the array wrapped by this property. /// A new array containing the same element type as the array wrapped by this property, of /// sizes. public Array CreateArrayInstance(int[] lengths) { if (type != FieldType.Array) throw new Exception("Attempting to retrieve array information from a field that doesn't contain an array."); return Internal_CreateManagedArrayInstance(mCachedPtr, lengths); } /// /// Creates a new instance of the list wrapped by this property. Caller must ensure this property contains a list. /// /// Size of the list. /// A new list containing the same element type as the list wrapped by this property, of /// size. public IList CreateListInstance(int length) { if (type != FieldType.List) throw new Exception("Attempting to retrieve array information from a field that doesn't contain a list."); return Internal_CreateManagedListInstance(mCachedPtr, length); } /// /// Creates a new instance of the dictionary wrapped by this property. Caller must ensure this property contains /// a dictionary. /// /// A new dictionary containing the same key/value types as the dictionary wrapped by this property. /// public IDictionary CreateDictionaryInstance() { if (type != FieldType.Dictionary) throw new Exception("Attempting to retrieve array information from a field that doesn't contain a dictionary."); return Internal_CreateManagedDictionaryInstance(mCachedPtr); } /// /// Helper method used for finding child properties of the specified property, using a property path. /// . /// /// Path elements representing field names and keys to look for. /// Index in the array to start the search at. /// Property representing the final path element, or null if not found (array index is out of range, or /// property with that path doesn't exist). internal SerializableProperty FindProperty(PropertyPathElement[] pathElements, int elementIdx) { switch (type) { case FieldType.Object: { SerializableObject childObject = GetObject(); return childObject.FindProperty(pathElements, elementIdx); } case FieldType.Array: { SerializableArray childArray = GetArray(); return childArray.FindProperty(pathElements, elementIdx); } case FieldType.List: { SerializableList childList = GetList(); return childList.FindProperty(pathElements, elementIdx); } case FieldType.Dictionary: { SerializableDictionary childDictionary = GetDictionary(); return childDictionary.FindProperty(pathElements, elementIdx); } } return null; } [MethodImpl(MethodImplOptions.InternalCall)] private static extern void Internal_CreateInstance(SerializableProperty instance, Type type); [MethodImpl(MethodImplOptions.InternalCall)] private static extern SerializableObject Internal_CreateObject(IntPtr nativeInstance, object managedInstance, Type actualType); [MethodImpl(MethodImplOptions.InternalCall)] private static extern SerializableArray Internal_CreateArray(IntPtr nativeInstance, object managedInstance); [MethodImpl(MethodImplOptions.InternalCall)] private static extern SerializableList Internal_CreateList(IntPtr nativeInstance, object managedInstance); [MethodImpl(MethodImplOptions.InternalCall)] private static extern SerializableDictionary Internal_CreateDictionary(IntPtr nativeInstance, object managedInstance); [MethodImpl(MethodImplOptions.InternalCall)] private static extern object Internal_CreateManagedObjectInstance(IntPtr nativeInstance); [MethodImpl(MethodImplOptions.InternalCall)] private static extern Array Internal_CreateManagedArrayInstance(IntPtr nativeInstance, int[] lengths); [MethodImpl(MethodImplOptions.InternalCall)] private static extern IList Internal_CreateManagedListInstance(IntPtr nativeInstance, int length); [MethodImpl(MethodImplOptions.InternalCall)] private static extern IDictionary Internal_CreateManagedDictionaryInstance(IntPtr nativeInstance); [MethodImpl(MethodImplOptions.InternalCall)] private static extern object Internal_CloneManagedInstance(IntPtr nativeInstance, object original); /// /// Converts a C# type into Banshee-specific serialization type. /// /// C# to convert. /// Banshee-specific serialization type. Throws an exception if matching type cannot be found. public static FieldType DetermineFieldType(Type internalType) { if (!internalType.IsArray) { if (internalType.IsEnum) return FieldType.Enum; else if (internalType == typeof (Byte)) return FieldType.Int; else if (internalType == typeof (SByte)) return FieldType.Int; else if (internalType == typeof (Int16)) return FieldType.Int; else if (internalType == typeof (UInt16)) return FieldType.Int; else if (internalType == typeof (Int32)) return FieldType.Int; else if (internalType == typeof (UInt32)) return FieldType.Int; else if (internalType == typeof (Int64)) return FieldType.Int; else if (internalType == typeof (UInt64)) return FieldType.Int; else if (internalType == typeof (bool)) return FieldType.Bool; else if (internalType == typeof (float)) return FieldType.Float; else if (internalType == typeof (double)) return FieldType.Float; else if (internalType == typeof (string)) return FieldType.String; else if (internalType == typeof (Vector2)) return FieldType.Vector2; else if (internalType == typeof (Vector3)) return FieldType.Vector3; else if (internalType == typeof (Vector4)) return FieldType.Vector4; else if (internalType == typeof(Quaternion)) return FieldType.Quaternion; else if (internalType == typeof (Color)) return FieldType.Color; else if (internalType == typeof(ColorGradient)) return FieldType.ColorGradient; else if (internalType == typeof(AnimationCurve)) return FieldType.Curve; else if (internalType == typeof(FloatDistribution)) return FieldType.FloatDistribution; else if (internalType == typeof(Vector2Distribution)) return FieldType.Vector2Distribution; else if (internalType == typeof(Vector3Distribution)) return FieldType.Vector3Distribution; else if (internalType == typeof(ColorDistribution)) return FieldType.ColorDistribution; else if (internalType.IsSubclassOf(typeof (GameObject))) return FieldType.GameObjectRef; else if (internalType.IsSubclassOf(typeof (Resource))) return FieldType.Resource; else if (internalType.IsSubclassOf(typeof (RRefBase))) return FieldType.RRef; else if (internalType.IsGenericType) { Type genericType = internalType.GetGenericTypeDefinition(); if (genericType == typeof (List<>)) return FieldType.List; else if (genericType == typeof (Dictionary<,>)) return FieldType.Dictionary; else if (genericType == typeof(RRef<>)) return FieldType.RRef; // Shouldn't happen because native code should only supply us with supported types throw new Exception("Cannot determine field type. Found an unsupported generic type."); } // Otherwise the type must be an object, unless some error occurred return FieldType.Object; } return FieldType.Array; } } /** @} */ }