A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Reflection;
4using UnityEngine;
5using UnityEngine.Timeline;
6using UnityEditor;
7using UnityEditor.SceneManagement;
8using UnityEngine.Playables;
9using Object = UnityEngine.Object;
10
11namespace UnityEditor.Timeline
12{
13 // Describes the object references on a ScriptableObject, ignoring script fields
14 struct ObjectReferenceField
15 {
16 public string propertyPath;
17 public bool isSceneReference;
18 public System.Type type;
19
20 private readonly static ObjectReferenceField[] None = new ObjectReferenceField[0];
21 private readonly static Dictionary<System.Type, ObjectReferenceField[]> s_Cache = new Dictionary<System.Type, ObjectReferenceField[]>();
22
23 public static ObjectReferenceField[] FindObjectReferences(System.Type type)
24 {
25 if (type == null)
26 return None;
27
28 if (type.IsAbstract || type.IsInterface)
29 return None;
30
31 if (!typeof(ScriptableObject).IsAssignableFrom(type))
32 return None;
33
34 ObjectReferenceField[] result = null;
35 if (s_Cache.TryGetValue(type, out result))
36 return result;
37
38 result = SearchForFields(type);
39 s_Cache[type] = result;
40 return result;
41 }
42
43 public static ObjectReferenceField[] FindObjectReferences<T>() where T : ScriptableObject, new()
44 {
45 return FindObjectReferences(typeof(T));
46 }
47
48 private static ObjectReferenceField[] SearchForFields(System.Type t)
49 {
50 Object instance = ScriptableObject.CreateInstance(t);
51 var list = new List<ObjectReferenceField>();
52
53 var serializableObject = new SerializedObject(instance);
54 var prop = serializableObject.GetIterator();
55 bool enterChildren = true;
56 while (prop.NextVisible(enterChildren))
57 {
58 enterChildren = true;
59 var ppath = prop.propertyPath;
60 if (ppath == "m_Script")
61 {
62 enterChildren = false;
63 }
64 else if (prop.propertyType == SerializedPropertyType.ObjectReference || prop.propertyType == SerializedPropertyType.ExposedReference)
65 {
66 enterChildren = false;
67 var exposedType = GetTypeFromPath(t, prop.propertyPath);
68 if (exposedType != null && typeof(Object).IsAssignableFrom(exposedType))
69 {
70 bool isSceneRef = prop.propertyType == SerializedPropertyType.ExposedReference;
71 list.Add(
72 new ObjectReferenceField() { propertyPath = prop.propertyPath, isSceneReference = isSceneRef, type = exposedType }
73 );
74 }
75 }
76 }
77
78 Object.DestroyImmediate(instance);
79 if (list.Count == 0)
80 return None;
81 return list.ToArray();
82 }
83
84 private static System.Type GetTypeFromPath(System.Type baseType, string path)
85 {
86 if (string.IsNullOrEmpty(path))
87 return null;
88
89 System.Type parentType = baseType;
90 FieldInfo field = null;
91 var pathTo = path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
92 var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
93 BindingFlags.Instance;
94 foreach (string s in pathTo)
95 {
96 field = parentType.GetField(s, flags);
97 while (field == null)
98 {
99 if (parentType.BaseType == null)
100 return null; // Should not happen really. Means SerializedObject got the property, but the reflection missed it
101 parentType = parentType.BaseType;
102 field = parentType.GetField(s, flags);
103 }
104
105 parentType = field.FieldType;
106 }
107
108 // dig out exposed reference types
109 if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<Object>).GetGenericTypeDefinition())
110 {
111 return field.FieldType.GetGenericArguments()[0];
112 }
113
114 return field.FieldType;
115 }
116
117 public Object Find(ScriptableObject sourceObject, Object context = null)
118 {
119 if (sourceObject == null)
120 return null;
121
122 SerializedObject obj = new SerializedObject(sourceObject, context);
123 var prop = obj.FindProperty(propertyPath);
124 if (prop == null)
125 throw new InvalidOperationException("sourceObject is not of the proper type. It does not contain a path to " + propertyPath);
126
127 Object result = null;
128 if (isSceneReference)
129 {
130 if (prop.propertyType != SerializedPropertyType.ExposedReference)
131 throw new InvalidOperationException(propertyPath + " is marked as a Scene Reference, but is not an exposed reference type");
132 if (context == null)
133 Debug.LogWarning("ObjectReferenceField.Find " + " is called on a scene reference without a context, will always be null");
134
135 result = prop.exposedReferenceValue;
136 }
137 else
138 {
139 if (prop.propertyType != SerializedPropertyType.ObjectReference)
140 throw new InvalidOperationException(propertyPath + "is marked as an asset reference, but is not an object reference type");
141 result = prop.objectReferenceValue;
142 }
143
144 return result;
145 }
146
147 /// <summary>
148 /// Check if an Object satisfies this field, including components
149 /// </summary>
150 public bool IsAssignable(Object obj)
151 {
152 if (obj == null)
153 return false;
154
155 // types match
156 bool potentialMatch = type.IsAssignableFrom(obj.GetType());
157
158 // field is component, and it exists on the gameObject
159 if (!potentialMatch && typeof(Component).IsAssignableFrom(type) && obj is GameObject)
160 potentialMatch = ((GameObject)obj).GetComponent(type) != null;
161
162 return potentialMatch && isSceneReference == obj.IsSceneObject();
163 }
164
165 /// <summary>
166 /// Assigns a value to the field
167 /// </summary>
168 public bool Assign(ScriptableObject scriptableObject, Object value, IExposedPropertyTable exposedTable = null)
169 {
170 var serializedObject = new SerializedObject(scriptableObject, exposedTable as Object);
171 var property = serializedObject.FindProperty(propertyPath);
172 if (property == null)
173 return false;
174
175 // if the value is a game object, but the field is a component
176 if (value is GameObject && typeof(Component).IsAssignableFrom(type))
177 value = ((GameObject)value).GetComponent(type);
178
179 if (isSceneReference)
180 {
181 property.exposedReferenceValue = value;
182
183 // the object gets dirtied, but not the scene which is where the reference is stored
184 var component = exposedTable as Component;
185 if (component != null && !EditorApplication.isPlaying)
186 EditorSceneManager.MarkSceneDirty(component.gameObject.scene);
187 }
188 else
189 property.objectReferenceValue = value;
190
191 serializedObject.ApplyModifiedProperties();
192 return true;
193 }
194 }
195}