/// <summary> /// Registers the supplied hashable property's initial backing field value and setter callback. /// </summary> /// <param name="trigger">Hashable property whose value changes will trigger the callback.</param> /// <param name="propertyToInvoke"> /// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property /// that triggers the setter in the array property. /// </param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> private static void RegisterPropertyIfNeeded( HashableSerializedProperty trigger, HashableSerializedProperty propertyToInvoke, System.Func <object, object> getter, System.Action <object, object> setter, System.Type propertyType ) { // initialize value cache if (!valueCache.ContainsKey(trigger)) { valueCache.Add(trigger, trigger.SerializedProperty.GetValue()); // if it is an array element, ensure the array and the size properties are registered SerializedProperty sp = trigger.SerializedProperty; if (sp.IsArrayElement()) { RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType); } } if (!valueCache.ContainsKey(propertyToInvoke)) { valueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue()); } // add callbacks associated with the trigger if (!propertySetterCallbacks.ContainsKey(trigger)) { propertySetterCallbacks.Add( trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType) ); } }
/// <summary> /// Gets any properties upstream of the supplied property. This method is used to determine when a parent setter /// might need to be invoked. /// </summary> /// <returns>The upstream properties.</returns> /// <param name="property">Property.</param> private static List <HashableSerializedProperty> GetUpstreamProperties(HashableSerializedProperty property) { List <HashableSerializedProperty> upstreamParents = new List <HashableSerializedProperty>(); SerializedProperty sp = property.SerializedProperty; if (sp != null) { while (sp.GetParentProperty() != null) { sp = sp.GetParentProperty(); upstreamParents.Add(new HashableSerializedProperty(sp.propertyPath, property.TargetObject)); } } return(upstreamParents); }
/// <summary> /// Registers the supplied hashable property's initial backing field value and setter callback. /// </summary> /// <param name="trigger">Hashable property whose value changes will trigger the callback.</param> /// <param name="propertyToInvoke"> /// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property /// that triggers the setter in the array property. /// </param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> private static void RegisterPropertyIfNeeded( HashableSerializedProperty trigger, HashableSerializedProperty propertyToInvoke, System.Func <object, object> getter, System.Action <object, object> setter, System.Type propertyType ) { if (propertyType == null) { return; } // ensure concrete property type is registered if (propertyType.IsGenericType) { FieldInfo field; propertyToInvoke.SerializedProperty.GetProvider(out field); propertyType = field.FieldType; } // initialize value cache if (!s_ValueCache.ContainsKey(trigger)) { s_ValueCache.Add(trigger, trigger.SerializedProperty.GetValue()); // if it is an array element, ensure the array and the size properties are registered SerializedProperty sp = trigger.SerializedProperty; if (sp.IsArrayElement()) { RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType); } } if (!s_ValueCache.ContainsKey(propertyToInvoke)) { s_ValueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue()); } // add callbacks associated with the trigger if (!s_PropertySetterCallbacks.ContainsKey(trigger)) { s_PropertySetterCallbacks.Add( trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType) ); } }
/// <summary> /// Raises the GUI event. /// </summary> /// <param name="position">Position.</param> /// <param name="property">Property.</param> /// <param name="label">Label.</param> public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { // TODO: this can be removed when Unity fixes bug 601339 if (property.isArray && property.propertyType != SerializedPropertyType.String) { return; } // clear all callbacks and cached values if the selection has changed if (!currentSelection.SetEquals(Selection.objects)) { propertySetterCallbacks.Clear(); valueCache.Clear(); currentSelection = new HashSet <Object>(Selection.objects); } // ensure all properties are registered foreach (Object target in property.serializedObject.targetObjects) { HashableSerializedProperty hashableProperty = new HashableSerializedProperty(property.propertyPath, target); // register property if needed RegisterPropertyIfNeeded( hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType ); } // display field EditorGUI.BeginDisabledGroup(Attribute.Setter == null); { if (DrawerToUse == null) { EditorGUI.PropertyField(position, property, true); } else { DrawerToUse.OnGUI(position, property, label); } } EditorGUI.EndDisabledGroup(); }
/// <summary> /// Registers the array property and its size property. /// </summary> /// <param name="arrayProperty">Array property.</param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> private static void RegisterArrayProperty( SerializedProperty arrayProperty, System.Func <object, object> getter, System.Action <object, object> setter, System.Type propertyType ) { HashableSerializedProperty hashableArrayProperty = new HashableSerializedProperty(arrayProperty.propertyPath, arrayProperty.serializedObject.targetObject); HashableSerializedProperty hashableArraySizeProperty = new HashableSerializedProperty( arrayProperty.propertyPath + ".Array.size", arrayProperty.serializedObject.targetObject ); RegisterPropertyIfNeeded(hashableArrayProperty, hashableArrayProperty, getter, setter, propertyType); RegisterPropertyIfNeeded(hashableArraySizeProperty, hashableArrayProperty, getter, setter, propertyType); FieldInfo field; arrayProperty.GetProvider(out field); s_ValueCache[hashableArrayProperty] = field.FieldType.IsArray ? System.Array.CreateInstance(field.FieldType.GetElementType(), 0) : System.Activator.CreateInstance(field.FieldType); s_ValueCache[hashableArraySizeProperty] = 0; }
/// <summary> /// Raises the GUI event. /// </summary> /// <param name="position">Position.</param> /// <param name="property">Property.</param> /// <param name="label">Label.</param> public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { #if UNITY_4_6 // bug 601339 if (property.isArray && property.propertyType != SerializedPropertyType.String) { return; } #endif // ensure property's decorator height is registered if (!decoratorHeights.ContainsKey(property.propertyPath)) { decoratorHeights.Add(property.propertyPath, 0f); } // clear all callbacks and cached values if the selection has changed if (!s_CurrentSelection.SetEquals(Selection.objects)) { s_PropertySetterCallbacks.Clear(); s_ValueCache.Clear(); s_CurrentSelection = new HashSet<Object>(Selection.objects); } // ensure all properties are registered foreach (Object target in property.serializedObject.targetObjects) { HashableSerializedProperty hashableProperty = new HashableSerializedProperty(property.propertyPath, target); // register property if needed if (hashableProperty.SerializedProperty != null) // newly added array elements might not yet exist { RegisterPropertyIfNeeded( hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType ); } } // display field bool hasSetter = Attribute.Setter != null; EditorGUI.BeginDisabledGroup(!hasSetter); { if (!hasSetter) { position.width -= EditorGUIUtility.singleLineHeight; } if (DrawerToUse == null) { // for generic types, just back up and draw the decorator again if ( property.propertyType == SerializedPropertyType.Generic || property.propertyType == SerializedPropertyType.ObjectReference ) { position.y -= decoratorHeights[property.propertyPath]; EditorGUI.PropertyField(position, property, label, true); // TODO: did Unity 5.1.0b3 fix this? } // for other types, use the default property field else { s_DefaultPropertyField.Invoke(null, new object[] { position, property, label }); } } else { DrawerToUse.OnGUI(position, property, label); } } EditorGUI.EndDisabledGroup(); if (!hasSetter) { position.x += position.width; position.height = EditorGUIUtility.singleLineHeight; position.width = EditorGUIUtility.singleLineHeight; s_NoSetterStatusIcon.tooltip = string.Format("No setter found for {0}.", Attribute.PropertyName); GUI.Box(position, s_NoSetterStatusIcon, EditorStylesX.StatusIconStyle); } }
/// <summary> /// Registers the supplied hashable property's initial backing field value and setter callback. /// </summary> /// <param name="trigger">Hashable property whose value changes will trigger the callback.</param> /// <param name="propertyToInvoke"> /// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property /// that triggers the setter in the array property. /// </param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> private static void RegisterPropertyIfNeeded( HashableSerializedProperty trigger, HashableSerializedProperty propertyToInvoke, System.Func<object, object> getter, System.Action<object, object> setter, System.Type propertyType ) { if (propertyType == null) { return; } // ensure concrete property type is registered if (propertyType.IsGenericType) { FieldInfo field; propertyToInvoke.SerializedProperty.GetProvider(out field); propertyType = field.FieldType; } // initialize value cache if (!s_ValueCache.ContainsKey(trigger)) { s_ValueCache.Add(trigger, trigger.SerializedProperty.GetValue()); // if it is an array element, ensure the array and the size properties are registered SerializedProperty sp = trigger.SerializedProperty; if (sp.IsArrayElement()) { RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType); } } if (!s_ValueCache.ContainsKey(propertyToInvoke)) { s_ValueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue()); } // add callbacks associated with the trigger if (!s_PropertySetterCallbacks.ContainsKey(trigger)) { s_PropertySetterCallbacks.Add( trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType) ); } }
/// <summary> /// Registers the array property and its size property. /// </summary> /// <param name="arrayProperty">Array property.</param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> private static void RegisterArrayProperty( SerializedProperty arrayProperty, System.Func<object, object> getter, System.Action<object, object> setter, System.Type propertyType ) { HashableSerializedProperty hashableArrayProperty = new HashableSerializedProperty(arrayProperty.propertyPath, arrayProperty.serializedObject.targetObject); HashableSerializedProperty hashableArraySizeProperty = new HashableSerializedProperty( arrayProperty.propertyPath + ".Array.size", arrayProperty.serializedObject.targetObject ); RegisterPropertyIfNeeded(hashableArrayProperty, hashableArrayProperty, getter, setter, propertyType); RegisterPropertyIfNeeded(hashableArraySizeProperty, hashableArrayProperty, getter, setter, propertyType); FieldInfo field; arrayProperty.GetProvider(out field); s_ValueCache[hashableArrayProperty] = field.FieldType.IsArray ? System.Array.CreateInstance(field.FieldType.GetElementType(), 0) : System.Activator.CreateInstance(field.FieldType); s_ValueCache[hashableArraySizeProperty] = 0; }
/// <summary> /// Raises the trigger property setter event. /// </summary> /// <param name="hashableProperty">Hashable property.</param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> /// <param name="oldValue">Old value.</param> private static void OnTriggerPropertySetter( HashableSerializedProperty hashableProperty, System.Func<object, object> getter, System.Action<object, object> setter, System.Type propertyType ) { // clean up lookup tables and early out if property is dead SerializedProperty sp = hashableProperty.SerializedProperty; if (sp == null) { s_PropertySetterCallbacks.Remove(hashableProperty); if (s_ValueCache.ContainsKey(hashableProperty)) { s_ValueCache.Remove(hashableProperty); } return; } // invoke the setter InvokeSetter(hashableProperty, getter, setter, propertyType); // when any part of an array changes, ensure array, elements, and size are all up to date bool doElementsNeedUpdate = false; bool doesArrayNeedUpdate = false; bool doesSizeNeedUpdate = false; if (sp.isArray && sp.propertyType != SerializedPropertyType.String) { doElementsNeedUpdate = true; doesSizeNeedUpdate = true; } else if (sp.IsArrayElement()) { doesArrayNeedUpdate = true; doesSizeNeedUpdate = true; sp = sp.GetParentProperty(); } else if (sp.IsArraySize()) { doesArrayNeedUpdate = true; doElementsNeedUpdate = true; sp = sp.GetParentProperty(); } if (doElementsNeedUpdate) { for (int elementIndex = 0; elementIndex < sp.arraySize; ++elementIndex) { HashableSerializedProperty hashableElement = new HashableSerializedProperty( string.Format("{0}.Array.data[{1}]", sp.propertyPath, elementIndex), hashableProperty.TargetObject ); if (s_ValueCache.ContainsKey(hashableElement)) { s_ValueCache[hashableElement] = hashableElement.SerializedProperty.GetValue(); } else // for added elements when list grows { s_ValueCache.Add(hashableElement, hashableElement.SerializedProperty.GetValue()); } } } if (doesArrayNeedUpdate) { HashableSerializedProperty hashableArray = new HashableSerializedProperty(sp.propertyPath, hashableProperty.TargetObject); s_ValueCache[hashableArray] = hashableArray.SerializedProperty.GetValue(); } if (doesSizeNeedUpdate) { HashableSerializedProperty hashableSize = new HashableSerializedProperty( string.Format("{0}.Array.size", sp.propertyPath), hashableProperty.TargetObject ); s_ValueCache[hashableSize] = hashableSize.SerializedProperty.GetValue(); } }
/// <summary> /// Invokes the supplied property setter for the supplied hashable serialized property and update its value /// cache. /// </summary> /// <param name="hashableProperty">A hashable representation of the property's serialized backing field.</param> /// <param name="getter">A getter method that takes the provider as its argument.</param> /// <param name="setter">A setter method that takes the provider and new value as its arguments.</param> /// <param name="propertyType">The type returned by the getter and specified in the setter signature.</param> private static void InvokeSetter( HashableSerializedProperty hashableProperty, System.Func<object, object> getter, System.Action<object, object> setter, System.Type propertyType ) { SerializedProperty sp = hashableProperty.SerializedProperty; // mark for undo HashSet<Object> objectsToUndo = new HashSet<Object>(); objectsToUndo.Add(hashableProperty.TargetObject); string undoName = string.Format("Change {0}", sp.GetDisplayName()); // if it's on a monobehaviour, it may affect other objects in the hierarchy if (hashableProperty.TargetObject is MonoBehaviour) { MonoBehaviour monoBehaviour = hashableProperty.TargetObject as MonoBehaviour; foreach (Transform t in monoBehaviour.transform.root.GetComponentsInChildren<Transform>(true)) { objectsToUndo.Add(t.gameObject); foreach (Component c in t.GetComponents<Component>()) { if (c != null) // skip MonoBehaviours with unassigned script reference { objectsToUndo.Add(c); } } } } Undo.RecordObjects(objectsToUndo.ToArray(), undoName); // get the providers FieldInfo fieldInfo; object provider = sp.GetProvider(out fieldInfo); // get the element index of the property being set, if any int elementIndex = hashableProperty.SerializedProperty.GetElementIndex(); // flush inspector changes and store pending values sp.serializedObject.ApplyModifiedProperties(); object pendingValue = null; // ensure IList backing field values are converted to the type expected by the setter if (elementIndex < 0 && typeof(IList).IsAssignableFrom(propertyType) && propertyType != fieldInfo.FieldType) { pendingValue = sp.GetConvertedIListValues(propertyType); } else { pendingValue = sp.GetValue(); } // reset backing field to old values if (elementIndex >= 0) { (fieldInfo.GetValue(provider) as IList)[elementIndex] = s_ValueCache[hashableProperty]; } else { fieldInfo.SetValue(provider, s_ValueCache[hashableProperty]); } // invoke the setter if (elementIndex >= 0) { // clone the result of the getter IList arrayValues = (IList)getter.Invoke(provider); if (typeof(System.Array).IsAssignableFrom(propertyType)) { arrayValues = (IList)((System.ICloneable)arrayValues).Clone(); } else { IList srcValues = arrayValues; arrayValues = (IList)System.Activator.CreateInstance(propertyType); for (int idx = 0; idx < srcValues.Count; ++idx) { arrayValues.Add(srcValues[idx]); } } // assign the pending element value arrayValues[elementIndex] = pendingValue; // invoke setter setter.Invoke(provider, arrayValues); } else { setter.Invoke(provider, pendingValue); // if the provider is a struct, copy its changes back up to the next reference type if (provider.GetType().IsValueType) { object newValue = provider; SerializedProperty parent = sp.GetParentProperty(); while (parent != null && !parent.isArray) { provider = parent.GetProvider(out fieldInfo); if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType)) { elementIndex = parent.GetElementIndex(); (fieldInfo.GetValue(provider) as IList)[elementIndex] = newValue; } else { fieldInfo.SetValue(provider, newValue); } if (provider.GetType().IsValueType) { newValue = provider; parent = parent.GetParentProperty(); } else { parent = null; } } } } // set dirty EditorUtilityX.SetDirty(objectsToUndo.ToArray()); // update cache s_ValueCache[hashableProperty] = sp.GetValue(); }
/// <summary> /// Gets any properties upstream of the supplied property. This method is used to determine when a parent setter /// might need to be invoked. /// </summary> /// <returns>The upstream properties.</returns> /// <param name="property">Property.</param> private static List<HashableSerializedProperty> GetUpstreamProperties(HashableSerializedProperty property) { List<HashableSerializedProperty> upstreamParents = new List<HashableSerializedProperty>(); SerializedProperty sp = property.SerializedProperty; if (sp != null) { while (sp.GetParentProperty() != null) { sp = sp.GetParentProperty(); upstreamParents.Add(new HashableSerializedProperty(sp.propertyPath, property.TargetObject)); } } return upstreamParents; }
/// <summary> /// Raises the GUI event. /// </summary> /// <param name="position">Position.</param> /// <param name="property">Property.</param> /// <param name="label">Label.</param> public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { #if UNITY_4_6 // bug 601339 if (property.isArray && property.propertyType != SerializedPropertyType.String) { return; } #endif // ensure property's decorator height is registered if (!decoratorHeights.ContainsKey(property.propertyPath)) { decoratorHeights.Add(property.propertyPath, 0f); } // clear all callbacks and cached values if the selection has changed if (!s_CurrentSelection.SetEquals(Selection.objects)) { s_PropertySetterCallbacks.Clear(); s_ValueCache.Clear(); s_CurrentSelection = new HashSet <Object>(Selection.objects); } // ensure all properties are registered foreach (Object target in property.serializedObject.targetObjects) { HashableSerializedProperty hashableProperty = new HashableSerializedProperty(property.propertyPath, target); // register property if needed if (hashableProperty.SerializedProperty != null) // newly added array elements might not yet exist { RegisterPropertyIfNeeded( hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType ); } } // display field bool hasSetter = Attribute.Setter != null; EditorGUI.BeginDisabledGroup(!hasSetter); { if (!hasSetter) { position.width -= EditorGUIUtility.singleLineHeight; } if (DrawerToUse == null) { // for generic types, just back up and draw the decorator again if ( property.propertyType == SerializedPropertyType.Generic || property.propertyType == SerializedPropertyType.ObjectReference ) { position.y -= decoratorHeights[property.propertyPath]; EditorGUI.PropertyField(position, property, label, true); // TODO: did Unity 5.1.0b3 fix this? } // for other types, use the default property field else { s_DefaultPropertyField.Invoke(null, new object[] { position, property, label }); } } else { DrawerToUse.OnGUI(position, property, label); } } EditorGUI.EndDisabledGroup(); if (!hasSetter) { position.x += position.width; position.height = EditorGUIUtility.singleLineHeight; position.width = EditorGUIUtility.singleLineHeight; s_NoSetterStatusIcon.tooltip = string.Format("No setter found for {0}.", Attribute.PropertyName); GUI.Box(position, s_NoSetterStatusIcon, EditorStylesX.StatusIconStyle); } }
/// <summary> /// Raises the trigger property setter event. /// </summary> /// <param name="hashableProperty">Hashable property.</param> /// <param name="getter">Getter.</param> /// <param name="setter">Setter.</param> /// <param name="propertyType">Property type.</param> /// <param name="oldValue">Old value.</param> private static void OnTriggerPropertySetter( HashableSerializedProperty hashableProperty, System.Func <object, object> getter, System.Action <object, object> setter, System.Type propertyType ) { // clean up lookup tables and early out if property is dead SerializedProperty sp = hashableProperty.SerializedProperty; if (sp == null) { s_PropertySetterCallbacks.Remove(hashableProperty); if (s_ValueCache.ContainsKey(hashableProperty)) { s_ValueCache.Remove(hashableProperty); } return; } // invoke the setter InvokeSetter(hashableProperty, getter, setter, propertyType); // when any part of an array changes, ensure array, elements, and size are all up to date bool doElementsNeedUpdate = false; bool doesArrayNeedUpdate = false; bool doesSizeNeedUpdate = false; if (sp.isArray && sp.propertyType != SerializedPropertyType.String) { doElementsNeedUpdate = true; doesSizeNeedUpdate = true; } else if (sp.IsArrayElement()) { doesArrayNeedUpdate = true; doesSizeNeedUpdate = true; sp = sp.GetParentProperty(); } else if (sp.IsArraySize()) { doesArrayNeedUpdate = true; doElementsNeedUpdate = true; sp = sp.GetParentProperty(); } if (doElementsNeedUpdate) { for (int elementIndex = 0; elementIndex < sp.arraySize; ++elementIndex) { HashableSerializedProperty hashableElement = new HashableSerializedProperty( string.Format("{0}.Array.data[{1}]", sp.propertyPath, elementIndex), hashableProperty.TargetObject ); if (s_ValueCache.ContainsKey(hashableElement)) { s_ValueCache[hashableElement] = hashableElement.SerializedProperty.GetValue(); } else // for added elements when list grows { s_ValueCache.Add(hashableElement, hashableElement.SerializedProperty.GetValue()); } } } if (doesArrayNeedUpdate) { HashableSerializedProperty hashableArray = new HashableSerializedProperty(sp.propertyPath, hashableProperty.TargetObject); s_ValueCache[hashableArray] = hashableArray.SerializedProperty.GetValue(); } if (doesSizeNeedUpdate) { HashableSerializedProperty hashableSize = new HashableSerializedProperty( string.Format("{0}.Array.size", sp.propertyPath), hashableProperty.TargetObject ); s_ValueCache[hashableSize] = hashableSize.SerializedProperty.GetValue(); } }
/// <summary> /// Invokes the supplied property setter for the supplied hashable serialized property and update its value /// cache. /// </summary> /// <param name="hashableProperty">A hashable representation of the property's serialized backing field.</param> /// <param name="getter">A getter method that takes the provider as its argument.</param> /// <param name="setter">A setter method that takes the provider and new value as its arguments.</param> /// <param name="propertyType">The type returned by the getter and specified in the setter signature.</param> private static void InvokeSetter( HashableSerializedProperty hashableProperty, System.Func <object, object> getter, System.Action <object, object> setter, System.Type propertyType ) { SerializedProperty sp = hashableProperty.SerializedProperty; // mark for undo HashSet <Object> objectsToUndo = new HashSet <Object>(); objectsToUndo.Add(hashableProperty.TargetObject); string undoName = string.Format("Change {0}", sp.GetDisplayName()); // if it's on a monobehaviour, it may affect other objects in the hierarchy if (hashableProperty.TargetObject is MonoBehaviour) { MonoBehaviour monoBehaviour = hashableProperty.TargetObject as MonoBehaviour; foreach (Transform t in monoBehaviour.transform.root.GetComponentsInChildren <Transform>(true)) { objectsToUndo.Add(t.gameObject); foreach (Component c in t.GetComponents <Component>()) { if (c != null) // skip MonoBehaviours with unassigned script reference { objectsToUndo.Add(c); } } } } Undo.RecordObjects(objectsToUndo.ToArray(), undoName); // get the providers FieldInfo fieldInfo; object provider = sp.GetProvider(out fieldInfo); // get the element index of the property being set, if any int elementIndex = hashableProperty.SerializedProperty.GetElementIndex(); // flush inspector changes and store pending values sp.serializedObject.ApplyModifiedProperties(); object pendingValue = null; // ensure IList backing field values are converted to the type expected by the setter if (elementIndex < 0 && typeof(IList).IsAssignableFrom(propertyType) && propertyType != fieldInfo.FieldType) { pendingValue = sp.GetConvertedIListValues(propertyType); } else { pendingValue = sp.GetValue(); } // reset backing field to old values if (elementIndex >= 0) { (fieldInfo.GetValue(provider) as IList)[elementIndex] = s_ValueCache[hashableProperty]; } else { fieldInfo.SetValue(provider, s_ValueCache[hashableProperty]); } // invoke the setter if (elementIndex >= 0) { // clone the result of the getter IList arrayValues = (IList)getter.Invoke(provider); if (typeof(System.Array).IsAssignableFrom(propertyType)) { arrayValues = (IList)((System.ICloneable)arrayValues).Clone(); } else { IList srcValues = arrayValues; arrayValues = (IList)System.Activator.CreateInstance(propertyType); for (int idx = 0; idx < srcValues.Count; ++idx) { arrayValues.Add(srcValues[idx]); } } // assign the pending element value arrayValues[elementIndex] = pendingValue; // invoke setter setter.Invoke(provider, arrayValues); } else { setter.Invoke(provider, pendingValue); // if the provider is a struct, copy its changes back up to the next reference type if (provider.GetType().IsValueType) { object newValue = provider; SerializedProperty parent = sp.GetParentProperty(); while (parent != null && !parent.isArray) { provider = parent.GetProvider(out fieldInfo); if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType)) { elementIndex = parent.GetElementIndex(); (fieldInfo.GetValue(provider) as IList)[elementIndex] = newValue; } else { fieldInfo.SetValue(provider, newValue); } if (provider.GetType().IsValueType) { newValue = provider; parent = parent.GetParentProperty(); } else { parent = null; } } } } // set dirty EditorUtilityX.SetDirty(objectsToUndo.ToArray()); // update cache s_ValueCache[hashableProperty] = sp.GetValue(); }