ObjectReferenceField.cs
7.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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;
}
}
}