Tanoda
PropertyAccessor.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System;
10using System.Reflection;
11using System.Linq.Expressions;
12using UnityEngine;
13#if UNITY_EDITOR
14using UnityEditor;
15#endif
16
17namespace Leap.Unity.Recording {
18 using Query;
19
20#if UNITY_EDITOR
21
27 public struct PropertyAccessor {
28 private static MethodInfo GetFloatMethod;
29 private static MethodInfo GetColorMethod;
30 private static MethodInfo GetVectorMethod;
31
32 static PropertyAccessor() {
33 Type[] intArr = new Type[] { typeof(int) };
34 GetFloatMethod = typeof(Material).GetMethod("GetFloat", intArr);
35 GetColorMethod = typeof(Material).GetMethod("GetColor", intArr);
36 GetVectorMethod = typeof(Material).GetMethod("GetVector", intArr);
37 }
38
39 private Func<float> _accessor;
40
48 public PropertyAccessor(GameObject root, EditorCurveBinding binding, bool failureIsZero = false) {
49 try {
50 //First get the target object the binding points to
51 GameObject target = getTargetObject(root, binding);
52 if (target == null) {
53 throw new InvalidOperationException("Target object cannot be null");
54 }
55
56 //Get the specific component the binding points to
57 Component component = target.GetComponent(binding.type);
58 if (component == null) {
59 throw new InvalidOperationException("Could not find a component of type " + binding.type + " on object " + target.name);
60 }
61
62 //Then get an Expression that represents accessing the bound value
63 Expression propertyExpr;
64 {
65 string[] propertyPath = binding.propertyName.Split('.');
66
67 //Material properties require special logic
68 if (propertyPath[0] == "material" && component is Renderer) {
69 propertyExpr = buildMaterialExpression(component, propertyPath);
70 } else {
71 propertyExpr = buildFieldExpression(component, propertyPath, binding);
72 }
73 }
74
75 //Compile the expression into a lambda we can execute
76 _accessor = Expression.Lambda<Func<float>>(propertyExpr).Compile();
77 } catch (Exception e) {
78 if (failureIsZero) {
79 //If we get any errors, catch them so that recording can continue
80 //But error loudly and default to a curve of zero
81 Debug.LogError("Exception when trying to construct PropertyAccessor, curves will be incorrect.");
82 Debug.LogException(e);
83 _accessor = () => 0;
84 } else {
85 throw e;
86 }
87 }
88 }
89
93 public float Access() {
94 return _accessor();
95 }
96
101 private static GameObject getTargetObject(GameObject root, EditorCurveBinding binding) {
102 string[] names = binding.path.Split('/');
103 GameObject target = root;
104
105 foreach (var name in names) {
106 for (int i = 0; i < target.transform.childCount; i++) {
107 var child = target.transform.GetChild(i);
108 if (child.gameObject.name == name) {
109 target = child.gameObject;
110 break;
111 }
112 }
113 }
114
115 return target;
116 }
117
122 private static Expression buildMaterialExpression(Component component, string[] propertyPath) {
123 //We are only _reading_ values so no need to instantiate a new material
124 Material material = (component as Renderer).sharedMaterial;
125 if (material == null) {
126 throw new InvalidOperationException("Could not record property because material was null");
127 }
128
129 Shader shader = material.shader;
130 if (shader == null) {
131 throw new InvalidOperationException("Could not record property because shader was null");
132 }
133
134 //material is the first element in the property path,
135 //the second (index 1) element is the name of the shader property we are accessing
136 string propertyName = propertyPath[1];
137
138 //Search for the type of the property in question
139 ShaderUtil.ShaderPropertyType? propertyType = null;
140 int shaderPropCount = ShaderUtil.GetPropertyCount(shader);
141 for (int i = 0; i < shaderPropCount; i++) {
142 if (ShaderUtil.GetPropertyName(shader, i) == propertyName) {
143 propertyType = ShaderUtil.GetPropertyType(shader, i);
144 break;
145 }
146 }
147
148 if (!propertyType.HasValue) {
149 throw new InvalidOperationException("Could not find property " + propertyName + " in shader " + shader);
150 }
151
152 //We convert the property name to id for faster access
153 var idExpr = Expression.Constant(Shader.PropertyToID(propertyName));
154 var matExpr = Expression.Property(Expression.Constant(component), "sharedMaterial");
155
156 //Based on the type of the property, we build an expression that invokes the correct
157 //method GetFloat for float types, GetColor for color types, ect....
158 Expression propertyExpr;
159 switch (propertyType.Value) {
160 case ShaderUtil.ShaderPropertyType.Float:
161 case ShaderUtil.ShaderPropertyType.Range:
162 propertyExpr = Expression.Call(matExpr, GetFloatMethod, idExpr);
163 break;
164 case ShaderUtil.ShaderPropertyType.Color:
165 propertyExpr = Expression.Call(matExpr, GetColorMethod, idExpr);
166 break;
167 case ShaderUtil.ShaderPropertyType.Vector:
168 propertyExpr = Expression.Call(matExpr, GetVectorMethod, idExpr);
169 break;
170 default:
171 throw new NotImplementedException("Can not handle property type " + propertyType.Value);
172 }
173
174 //The value we accessed might be a struct with additional fields we need to dive into
175 //0 = 'material'
176 //1 = name of shader property
177 //2... = fields of the accessed property
178 for (int i = 2; i < propertyPath.Length; i++) {
179 propertyExpr = Expression.PropertyOrField(propertyExpr, propertyPath[i]);
180 }
181
182 return propertyExpr;
183 }
184
189 private static Expression buildFieldExpression(Component component, string[] propertyPath, EditorCurveBinding binding) {
190 //First build an expression that accesses the base field value
191 //We get an aproximate field/property name because many engine types don't have property names
192 //that actually correspond to real c# properties we can access
193 Expression fieldExpr = Expression.PropertyOrField(Expression.Constant(component),
194 getClosestPropertyName(binding.type, propertyPath[0]));
195
196 //For the remainder of the property path, we access additional fields located inside the value
197 for (int i = 1; i < propertyPath.Length; i++) {
198 fieldExpr = Expression.PropertyOrField(fieldExpr, propertyPath[i]);
199 }
200
201 return fieldExpr;
202 }
203
211 private static string getClosestPropertyName(Type type, string bindingProperty) {
212 //We only care about instance members, but they can be public or non-public
213 BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
214
215 //Properties and fields can both be categorized as MemberInfo
216 var members = type.GetFields(flags).
217 Query().
218 Cast<MemberInfo>().
219 Concat(type.GetProperties(flags).
220 Query().
221 Cast<MemberInfo>()).
222 ToList();
223
224 {
225 MemberInfo exactMatch = members.Query().FirstOrDefault(f => f.Name == bindingProperty);
226 if (exactMatch != null) {
227 return exactMatch.Name;
228 }
229 }
230
231 {
232 MemberInfo minusPrefix = members.Query().FirstOrDefault(f => bindingProperty.Substring(2).ToLower() == f.Name.ToLower());
233 if (minusPrefix != null) {
234 return minusPrefix.Name;
235 }
236 }
237
238 {
239 MemberInfo containsMatch = members.Query().FirstOrDefault(f => bindingProperty.ToLower().Contains(f.Name.ToLower()));
240 if (containsMatch != null) {
241 return containsMatch.Name;
242 }
243 }
244
245 throw new InvalidOperationException("Cannot find a field or property with a name close to " + bindingProperty + " for type " + type);
246 }
247 }
248#endif
249}
UnityEngine.Component Component
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19