You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
7.6 KiB
195 lines
7.6 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Reflection; |
|
using UnityEngine; |
|
using UnityEngine.Timeline; |
|
using UnityEditor; |
|
using UnityEditor.SceneManagement; |
|
using UnityEngine.Playables; |
|
using Object = UnityEngine.Object; |
|
|
|
namespace UnityEditor.Timeline |
|
{ |
|
// Describes the object references on a ScriptableObject, ignoring script fields |
|
struct ObjectReferenceField |
|
{ |
|
public string propertyPath; |
|
public bool isSceneReference; |
|
public System.Type type; |
|
|
|
private readonly static ObjectReferenceField[] None = new ObjectReferenceField[0]; |
|
private readonly static Dictionary<System.Type, ObjectReferenceField[]> s_Cache = new Dictionary<System.Type, ObjectReferenceField[]>(); |
|
|
|
public static ObjectReferenceField[] FindObjectReferences(System.Type type) |
|
{ |
|
if (type == null) |
|
return None; |
|
|
|
if (type.IsAbstract || type.IsInterface) |
|
return None; |
|
|
|
if (!typeof(ScriptableObject).IsAssignableFrom(type)) |
|
return None; |
|
|
|
ObjectReferenceField[] result = null; |
|
if (s_Cache.TryGetValue(type, out result)) |
|
return result; |
|
|
|
result = SearchForFields(type); |
|
s_Cache[type] = result; |
|
return result; |
|
} |
|
|
|
public static ObjectReferenceField[] FindObjectReferences<T>() where T : ScriptableObject, new() |
|
{ |
|
return FindObjectReferences(typeof(T)); |
|
} |
|
|
|
private static ObjectReferenceField[] SearchForFields(System.Type t) |
|
{ |
|
Object instance = ScriptableObject.CreateInstance(t); |
|
var list = new List<ObjectReferenceField>(); |
|
|
|
var serializableObject = new SerializedObject(instance); |
|
var prop = serializableObject.GetIterator(); |
|
bool enterChildren = true; |
|
while (prop.NextVisible(enterChildren)) |
|
{ |
|
enterChildren = true; |
|
var ppath = prop.propertyPath; |
|
if (ppath == "m_Script") |
|
{ |
|
enterChildren = false; |
|
} |
|
else if (prop.propertyType == SerializedPropertyType.ObjectReference || prop.propertyType == SerializedPropertyType.ExposedReference) |
|
{ |
|
enterChildren = false; |
|
var exposedType = GetTypeFromPath(t, prop.propertyPath); |
|
if (exposedType != null && typeof(Object).IsAssignableFrom(exposedType)) |
|
{ |
|
bool isSceneRef = prop.propertyType == SerializedPropertyType.ExposedReference; |
|
list.Add( |
|
new ObjectReferenceField() { propertyPath = prop.propertyPath, isSceneReference = isSceneRef, type = exposedType } |
|
); |
|
} |
|
} |
|
} |
|
|
|
Object.DestroyImmediate(instance); |
|
if (list.Count == 0) |
|
return None; |
|
return list.ToArray(); |
|
} |
|
|
|
private static System.Type GetTypeFromPath(System.Type baseType, string path) |
|
{ |
|
if (string.IsNullOrEmpty(path)) |
|
return null; |
|
|
|
System.Type parentType = baseType; |
|
FieldInfo field = null; |
|
var pathTo = path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); |
|
var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | |
|
BindingFlags.Instance; |
|
foreach (string s in pathTo) |
|
{ |
|
field = parentType.GetField(s, flags); |
|
while (field == null) |
|
{ |
|
if (parentType.BaseType == null) |
|
return null; // Should not happen really. Means SerializedObject got the property, but the reflection missed it |
|
parentType = parentType.BaseType; |
|
field = parentType.GetField(s, flags); |
|
} |
|
|
|
parentType = field.FieldType; |
|
} |
|
|
|
// dig out exposed reference types |
|
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<Object>).GetGenericTypeDefinition()) |
|
{ |
|
return field.FieldType.GetGenericArguments()[0]; |
|
} |
|
|
|
return field.FieldType; |
|
} |
|
|
|
public Object Find(ScriptableObject sourceObject, Object context = null) |
|
{ |
|
if (sourceObject == null) |
|
return null; |
|
|
|
SerializedObject obj = new SerializedObject(sourceObject, context); |
|
var prop = obj.FindProperty(propertyPath); |
|
if (prop == null) |
|
throw new InvalidOperationException("sourceObject is not of the proper type. It does not contain a path to " + propertyPath); |
|
|
|
Object result = null; |
|
if (isSceneReference) |
|
{ |
|
if (prop.propertyType != SerializedPropertyType.ExposedReference) |
|
throw new InvalidOperationException(propertyPath + " is marked as a Scene Reference, but is not an exposed reference type"); |
|
if (context == null) |
|
Debug.LogWarning("ObjectReferenceField.Find " + " is called on a scene reference without a context, will always be null"); |
|
|
|
result = prop.exposedReferenceValue; |
|
} |
|
else |
|
{ |
|
if (prop.propertyType != SerializedPropertyType.ObjectReference) |
|
throw new InvalidOperationException(propertyPath + "is marked as an asset reference, but is not an object reference type"); |
|
result = prop.objectReferenceValue; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// Check if an Object satisfies this field, including components |
|
/// </summary> |
|
public bool IsAssignable(Object obj) |
|
{ |
|
if (obj == null) |
|
return false; |
|
|
|
// types match |
|
bool potentialMatch = type.IsAssignableFrom(obj.GetType()); |
|
|
|
// field is component, and it exists on the gameObject |
|
if (!potentialMatch && typeof(Component).IsAssignableFrom(type) && obj is GameObject) |
|
potentialMatch = ((GameObject)obj).GetComponent(type) != null; |
|
|
|
return potentialMatch && isSceneReference == obj.IsSceneObject(); |
|
} |
|
|
|
/// <summary> |
|
/// Assigns a value to the field |
|
/// </summary> |
|
public bool Assign(ScriptableObject scriptableObject, Object value, IExposedPropertyTable exposedTable = null) |
|
{ |
|
var serializedObject = new SerializedObject(scriptableObject, exposedTable as Object); |
|
var property = serializedObject.FindProperty(propertyPath); |
|
if (property == null) |
|
return false; |
|
|
|
// if the value is a game object, but the field is a component |
|
if (value is GameObject && typeof(Component).IsAssignableFrom(type)) |
|
value = ((GameObject)value).GetComponent(type); |
|
|
|
if (isSceneReference) |
|
{ |
|
property.exposedReferenceValue = value; |
|
|
|
// the object gets dirtied, but not the scene which is where the reference is stored |
|
var component = exposedTable as Component; |
|
if (component != null && !EditorApplication.isPlaying) |
|
EditorSceneManager.MarkSceneDirty(component.gameObject.scene); |
|
} |
|
else |
|
property.objectReferenceValue = value; |
|
|
|
serializedObject.ApplyModifiedProperties(); |
|
return true; |
|
} |
|
} |
|
}
|
|
|