//********************************** 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
}
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)
{
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.");
return Internal_CreateObject(mCachedPtr, this);
}
///
/// 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);
[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 == 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(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;
}
}
/** @} */
}