Hello,
I am trying to implement a property drawer that allows me to select an option between the different derived types of a base class. The problem I am facing is an assertion called IsInSyncWithParentSerializedObject that appears when I add a new array element in the editor as you can see in the pictures below:
I have just realized that the number of times this assertion is logged is proportional to the number of serialized properties of all the already existent array elements. In this example I initially had 2 array elements (4 properties), so when adding a new array element, the assertion is logged 4 times.
I leave below the current code I am using, just in case it helps where the root cause is:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEngine;
using Scripts;
namespace Editor
{
[CustomPropertyDrawer(typeof(SubclassSelectorAttribute))]
public class SubclassPropertyDrawer : PropertyDrawer
{
private Dictionary<string, Type> _typeNameToType;
//private List<VisualElement> changeableElements;
private List<string> _elements = new List<string>();
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
_typeNameToType = new Dictionary<string, Type>();
var baseType = fieldInfo.FieldType.IsArray ? fieldInfo.FieldType.GetElementType() : fieldInfo.FieldType;
var derivedTypesNames = new List<string>();
var derivedTypes = TypeCache.GetTypesDerivedFrom(baseType);
foreach (var derivedType in derivedTypes)
{
derivedTypesNames.Add(derivedType.Name);
_typeNameToType[derivedType.Name] = derivedType;
}
var rootElement = new VisualElement();
var changeableElements = new List<VisualElement>();
BuildUI(rootElement, property, derivedTypesNames, changeableElements);
return rootElement;
}
private void BuildUI(VisualElement rootElement, SerializedProperty property, List<string> derivedTypes, List<VisualElement> changeableElements)
{
int dropDownInitValue = GetMananagedReferenceValueNameIdx(property, derivedTypes);
var dropdown = new DropdownField("Derived Classes", derivedTypes, dropDownInitValue);
dropdown.RegisterValueChangedCallback((evt) => OnDropDownValueChanges(rootElement, property, evt, changeableElements));
rootElement.Add(dropdown);
DisplayDerivedClassProperties(rootElement, property, derivedTypes[dropDownInitValue], changeableElements);
}
private int GetMananagedReferenceValueNameIdx(SerializedProperty property, List<string> derivedTypes)
{
if (!string.IsNullOrEmpty(property.managedReferenceFullTypename))
{
var name = property.managedReferenceFullTypename.Split('.').Last();
return derivedTypes.IndexOf(name);
}
return 0;
}
private void DisplayDerivedClassProperties(VisualElement rootElement, SerializedProperty property, string value, List<VisualElement> changeableElements)
{
if (property.managedReferenceValue == null || property.managedReferenceValue.GetType() != _typeNameToType[value])
{
var derivedClassInstance = Activator.CreateInstance(_typeNameToType[value]);
property.managedReferenceValue = derivedClassInstance;
property.serializedObject.ApplyModifiedProperties();
}
var foldout = new Foldout();
foldout.text = property.displayName;
_elements.Add(property.displayName);
if (property.hasChildren)
{
var enumerator = property.Copy().GetEnumerator();
while (enumerator.MoveNext())
{
var childProperty = enumerator.Current as SerializedProperty;
var propertyField = new PropertyField(childProperty, childProperty.displayName);
propertyField.BindProperty(childProperty);
foldout.Add(propertyField);
}
}
rootElement.Add(foldout);
changeableElements.Add(foldout);
}
private void OnDropDownValueChanges(VisualElement rootElement, SerializedProperty property, ChangeEvent<string> evt, List<VisualElement> changeableElements)
{
ResetDynamicVisualElements(rootElement, changeableElements);
DisplayDerivedClassProperties(rootElement, property, evt.newValue, changeableElements);
string text = string.Empty;
foreach (var element in _elements)
{
text += $"{element}, ";
}
Debug.Log(text);
}
private void ResetDynamicVisualElements(VisualElement rootElement, List<VisualElement> changeableElements)
{
foreach (var element in changeableElements)
{
rootElement.Remove(element);
}
changeableElements.Clear();
}
}
}
Additionally, I have to more questions:
-
I am commenting this line since all the array elements were sharing it:
Why is this happening? Is this supposed to be the expected behavior? Due to this variable, I was facing problems in the ResetDynamicVisualElements method since it was trying to remove visual elements from a different array element property… finally I had to share this list across all the methods as an argument to avoid this problem. -
Modifying a string property and then adding an element to the array changes the array element label:
Another mystery I cannot understand. I am binding the PropertyField to the Name SerializedProperty… how is this changing the element label?
I hope anyway can help me with this because I spent a lot of time into this and I cannot even realize where the root cause is.
Thank you very much in advance!