using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace Unity.Cinemachine
{
/// An ad-hoc collection of helpers for reflection, used by Cinemachine
/// or its editor tools in various places
static class ReflectionHelpers
{
/// Copy the fields from one object to another
/// The source object to copy from
/// The destination object to copy to
/// The mask to filter the attributes.
/// Only those fields that get caught in the filter will be copied
public static void CopyFields(
object src, object dst,
BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
{
if (src != null && dst != null)
{
Type type = src.GetType();
FieldInfo[] fields = type.GetFields(bindingAttr);
for (int i = 0; i < fields.Length; ++i)
if (!fields[i].IsStatic)
fields[i].SetValue(dst, fields[i].GetValue(src));
}
}
/// Search the assembly for all types that match a predicate
/// The assembly to search
/// The type to look for
/// A list of types found in the assembly that inherit from the predicate
public static IEnumerable GetTypesInAssembly(
Assembly assembly, Predicate predicate)
{
var list = new List();
if (assembly != null)
{
try
{
var allTypes = assembly.GetTypes();
if (allTypes != null)
{
for (int i = 0; i < allTypes.Length; ++i)
{
var t = allTypes[i];
if (t != null && predicate(t))
list.Add(t);
}
}
}
catch (Exception) {} // Can't load the types in this assembly
}
return list;
}
/// Get a type from a name
/// The name of the type to search for
/// The type matching the name, or null if not found
public static Type GetTypeInAllDependentAssemblies(string typeName)
{
var iter = GetTypesInAllDependentAssemblies(t => t.Name == typeName).GetEnumerator();
if (iter.MoveNext())
return iter.Current;
return null;
}
/// Search all assemblies for all types that match a predicate
/// The type to look for
/// A list of types found in the assembly that inherit from the predicate
public static IEnumerable GetTypesInAllDependentAssemblies(Predicate predicate)
{
List foundTypes = new(100);
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var definedIn = typeof(CinemachineComponentBase).Assembly.GetName().Name;
for (int i = 0; i < assemblies.Length; ++i)
{
var assembly = assemblies[i];
if (assembly.GlobalAssemblyCache)
continue;
// Note that we have to call GetName().Name. Just GetName() will not work.
bool skip = assembly.GetName().Name != definedIn;
if (skip)
{
var referencedAssemblies = assembly.GetReferencedAssemblies();
for (int j = 0; skip && j < referencedAssemblies.Length; ++j)
if (referencedAssemblies[j].Name == definedIn)
skip = false;
}
if (skip)
continue;
try
{
var iter = GetTypesInAssembly(assembly, predicate).GetEnumerator();
while (iter.MoveNext())
foundTypes.Add(iter.Current);
}
catch (Exception) {} // Just skip uncooperative assemblies
}
return foundTypes;
}
/// Cheater extension to access internal field of an object
/// The field type
/// The type of the field
/// The object to access
/// The string name of the field to access
/// The value of the field in the objects
public static T AccessInternalField(this Type type, object obj, string memberName)
{
if (string.IsNullOrEmpty(memberName) || (type == null))
return default;
BindingFlags bindingFlags = BindingFlags.NonPublic;
if (obj != null)
bindingFlags |= BindingFlags.Instance;
else
bindingFlags |= BindingFlags.Static;
FieldInfo field = type.GetField(memberName, bindingFlags);
if ((field != null) && (field.FieldType == typeof(T)))
return (T)field.GetValue(obj);
return default;
}
/// Get the object owner of a field. This method processes
/// the '.' separator to get from the object that owns the compound field
/// to the object that owns the leaf field
/// The name of the field, which may contain '.' separators
/// the owner of the compound field
/// The object owner of the field
public static object GetParentObject(string path, object obj)
{
var fields = path.Split('.');
if (fields.Length <= 1)
return obj;
var type = obj.GetType();
if (type.IsArray || typeof(IList).IsAssignableFrom(type))
{
var elements = fields[1].Split('[');
if (elements.Length > 1)
{
var index = Int32.Parse(elements[1].Trim(']'));
if (type.IsArray)
{
if (obj is not Array a || a.Length <= index)
return null;
obj = a.GetValue(index);
}
else
{
var list = obj as IList;
if (list != null || list.Count <= index)
return null;
obj = list[index];
}
if (fields.Length <= 3)
return obj;
}
}
else
{
var info = type.GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
obj = info.GetValue(obj);
}
return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj);
}
/// Returns a string path from an expression - mostly used to retrieve serialized properties
/// without hardcoding the field path. Safer, and allows for proper refactoring.
/// Magic expression
/// Magic expression
/// Magic expression
/// The string version of the field path
public static string GetFieldPath(Expression> expr)
{
MemberExpression me;
switch (expr.Body.NodeType)
{
case ExpressionType.MemberAccess:
me = expr.Body as MemberExpression;
break;
default:
throw new InvalidOperationException();
}
var members = new List();
while (me != null)
{
members.Add(me.Member.Name);
me = me.Expression as MemberExpression;
}
var sb = new StringBuilder();
for (int i = members.Count - 1; i >= 0; i--)
{
sb.Append(members[i]);
if (i > 0) sb.Append('.');
}
return sb.ToString();
}
public delegate MonoBehaviour ReferenceUpdater(Type expectedType, MonoBehaviour oldValue);
///
/// Recursive scan that calls handler for all serializable fields that reference a MonoBehaviour
///
public static bool RecursiveUpdateBehaviourReferences(GameObject go, ReferenceUpdater updater)
{
bool doneSomething = false;
var components = go.GetComponentsInChildren(true);
for (int i = 0; i < components.Length; ++i)
{
var c = components[i];
var obj = c as object;
if (ScanFields(ref obj, updater))
{
doneSomething = true;
if (UnityEditor.PrefabUtility.IsPartOfAnyPrefab(go))
UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(c);
}
}
return doneSomething;
// local function
static bool ScanFields(ref object obj, ReferenceUpdater updater)
{
if (obj == null)
return false;
bool changed = false;
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
if (obj is MonoBehaviour)
bindingFlags |= BindingFlags.NonPublic; // you can inspect non-public fields if thy have the attribute
var fields = obj.GetType().GetFields(bindingFlags);
for (int j = 0; j < fields.Length; ++j)
{
var f = fields[j];
if (!f.IsPublic && f.GetCustomAttribute(typeof(SerializeField)) == null)
continue;
// Process the field
var type = f.FieldType;
if (typeof(MonoBehaviour).IsAssignableFrom(type))
{
var fieldValue = f.GetValue(obj);
var mb = fieldValue as MonoBehaviour;
if (mb != null)
{
var newValue = updater(type, mb);
if (newValue != mb)
{
changed = true;
f.SetValue(obj, newValue);
}
}
}
// Handle arrays and nested types
else if (type.IsArray)
{
if (f.GetValue(obj) is Array fieldValue)
{
for (int i = 0; i < fieldValue.Length; ++i)
{
var element = fieldValue.GetValue(i);
if (ScanFields(ref element, updater))
{
fieldValue.SetValue(element, i);
changed = true;
}
}
if (changed)
f.SetValue(obj, fieldValue);
}
}
else if (typeof(IList).IsAssignableFrom(type))
{
if (f.GetValue(obj) is IList fieldValue)
{
for (int i = 0; i < fieldValue.Count; ++i)
{
var element = fieldValue[i];
if (ScanFields(ref element, updater))
{
fieldValue[i] = element;
changed = true;
}
}
if (changed)
f.SetValue(obj, fieldValue);
}
}
else
{
// If the field type has fields of its own, process them
var fieldValue = f.GetValue(obj);
if (ScanFields(ref fieldValue, updater))
changed = true;
}
}
return changed;
}
}
}
}