using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
///
/// Contains specialized utility functions for creating SerializedObject and SerializedProperty objects from
/// , , and .
///
public static class SerializedPropertyUtility
{
const string k_ArrayIndicator = ".Array.data[";
static Dictionary s_SerializedObjectCache = new();
static Dictionary s_SerializedPropertyCache = new();
static readonly Regex k_ExtractArrayPath = new Regex("(?!\\[)[0-9]+(?=\\])", RegexOptions.RightToLeft | RegexOptions.Compiled);
static SerializedPropertyUtility()
{
Selection.selectionChanged += ClearCaches;
Undo.undoRedoPerformed += ClearPropertyCache;
}
static void ClearCaches()
{
s_SerializedObjectCache.Clear();
s_SerializedPropertyCache.Clear();
}
///
/// Clear cached SerializedProperty objects. This is automatically called on every selection change. Use this
/// function if you need to insert or remove properties that may have been cached earlier in the frame.
///
public static void ClearPropertyCache()
{
s_SerializedPropertyCache.Clear();
}
internal static object GetSerializedPropertyObject(SerializedProperty property)
{
var mbObject = property.serializedObject.targetObject;
// Array fields in SerializedProperty paths have this slightly complicated pathing in the format of
// arrayFieldName.Array.data[i] which we can simplify to just arrayFieldName[i] for easier parsing.
var path = property.propertyPath.Replace(k_ArrayIndicator, "[");
var splitPath = path.Split('.');
var parentObject = (object)mbObject;
var pathPartObject = default(object);
for (int i = 0; i < splitPath.Length; ++i)
{
var propertyPathPart = splitPath[i];
var isArray = IsPropertyIndexer(propertyPathPart, out var fieldName, out var arrayIndex);
var fieldInfo = parentObject.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
pathPartObject = fieldInfo.GetValue(parentObject);
if (isArray)
{
if (pathPartObject is IList list)
pathPartObject = list[arrayIndex];
}
parentObject = pathPartObject;
}
return pathPartObject;
}
static bool IsPropertyIndexer(string propertyPart, out string fieldName, out int index)
{
var regex = new Regex(@"(.+)\[(\d+)\]");
var match = regex.Match(propertyPart);
if (match.Success) // Property refers to an array or list
{
fieldName = match.Groups[1].Value;
index = int.Parse(match.Groups[2].Value);
return true;
}
else
{
fieldName = propertyPart;
index = -1;
return false;
}
}
///
/// Create a SerializedObject for a . This value is cached.
///
/// The to create a SerializedObject for.
/// A SerializedObject for the requested , or null if container is null.
///
public static SerializedObject GetSerializedObject(SplineContainer container)
{
var hash = container.GetInstanceID();
if (!s_SerializedObjectCache.TryGetValue(hash, out var so))
s_SerializedObjectCache.Add(hash, so = new SerializedObject(container));
return so;
}
///
/// Create a SerializedProperty for a at the requested index in the
/// .
///
/// The to reference.
/// The index of the Spline in the array.
/// A SerializedProperty for the requested , or null if not found.
public static SerializedProperty GetSplineSerializedProperty(SerializedObject splineContainer, int splineIndex)
{
if (splineContainer == null || splineContainer.targetObject == null)
return null;
var hash = HashCode.Combine(splineContainer.targetObject.GetInstanceID(), splineIndex);
if (!s_SerializedPropertyCache.TryGetValue(hash, out var splineProperty))
{
var splines = splineContainer?.FindProperty("m_Splines");
splineProperty = splines == null || splineIndex < 0 || splineIndex >= splines.arraySize
? null
: splines.GetArrayElementAtIndex(splineIndex);
s_SerializedPropertyCache.Add(hash, splineProperty);
}
return splineProperty;
}
static string GetEmbeddedSplineDataPropertyName(EmbeddedSplineDataType type)
{
return type switch
{
EmbeddedSplineDataType.Int => "m_IntData",
EmbeddedSplineDataType.Float => "m_FloatData",
EmbeddedSplineDataType.Float4 => "m_Float4Data",
EmbeddedSplineDataType.Object => "m_ObjectData",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
///
/// Create a SerializedProperty for a value embedded in a
/// class. These are keyed collections of that are managed by the
/// instance. See , , etc.
///
/// The that contains the target .
/// The index of the Spline in the array.
/// The .
/// A string value used to identify and access a .
/// A SerializedProperty for the requested , or null if not found.
public static SerializedProperty GetEmbeddedSplineDataProperty(
SplineContainer container,
int index,
EmbeddedSplineDataType type,
string key)
{
var splineProperty = GetSplineSerializedProperty(GetSerializedObject(container), index);
if (splineProperty == null)
return null;
return GetEmbeddedSplineDataProperty(splineProperty, type, key);
}
///
/// Create a SerializedProperty for a value embedded in a
/// class. These are keyed collections of that are managed by the
/// instance. See , , etc.
///
/// The SerializedProperty for the target .
/// The .
/// A string value used to identify and access a .
/// A SerializedProperty for the requested , or null if not found.
public static SerializedProperty GetEmbeddedSplineDataProperty(SerializedProperty splineProperty,
EmbeddedSplineDataType type,
string key)
{
var hash = HashCode.Combine(splineProperty.serializedObject.targetObject.GetInstanceID(),
splineProperty.propertyPath.GetHashCode(),
type.GetHashCode(),
key.GetHashCode());
if (s_SerializedPropertyCache.TryGetValue(hash, out var splineDataProperty) && splineDataProperty?.serializedObject != null)
return splineDataProperty;
var dict = splineProperty.FindPropertyRelative(GetEmbeddedSplineDataPropertyName(type));
var data = dict?.FindPropertyRelative("m_Data");
for (int i = 0; i < data?.arraySize; ++i)
{
var kvp = data.GetArrayElementAtIndex(i);
var k = kvp.FindPropertyRelative("Key");
if (k.stringValue == key)
{
s_SerializedPropertyCache[hash] = splineDataProperty = kvp.FindPropertyRelative("Value");
return splineDataProperty;
}
}
s_SerializedPropertyCache[hash] = null;
return null;
}
internal static SerializedProperty GetEmbeddedSplineDataProperty(SerializedProperty embeddedSplineDataProperty)
{
var container = embeddedSplineDataProperty.FindPropertyRelative("m_Container");
var index = embeddedSplineDataProperty.FindPropertyRelative("m_SplineIndex");
var type = embeddedSplineDataProperty.FindPropertyRelative("m_Type");
var key = embeddedSplineDataProperty.FindPropertyRelative("m_Key");
if (container == null || !(container.objectReferenceValue is SplineContainer containerBehaviour))
return null;
var containerSerializedObject = GetSerializedObject(containerBehaviour);
var spline = GetSplineSerializedProperty(containerSerializedObject, index.intValue);
if (spline == null)
return null;
return GetEmbeddedSplineDataProperty(spline, (EmbeddedSplineDataType)type.enumValueIndex, key.stringValue);
}
internal static bool GetContainerAndIndex(SerializedProperty spline, out ISplineContainer container, out int index)
{
container = spline?.serializedObject?.targetObject as ISplineContainer;
index = 0;
if (container == null)
return false;
if(TryGetSplineIndex(spline, out index))
return true;
return container.Splines.Count == 1;
}
// Extracts the index of a Spline in the ISplineContainer array, or 0 if not part of an array.
internal static bool TryGetSplineIndex(SerializedProperty splineProperty, out int index)
{
index = 0;
var match = k_ExtractArrayPath.Match(splineProperty.propertyPath);
return match.Success && int.TryParse(match.Value, out index);
}
internal static bool TryGetSpline(SerializedProperty splineProperty, out Spline spline)
{
if (GetContainerAndIndex(splineProperty, out var container, out var index))
{
spline = container.Splines[index];
return true;
}
spline = null;
return false;
}
}
}