Search Unity

Weird freezing issue with nested SerializeReference.

Discussion in 'Editor & General Support' started by RecursiveEclipse, Feb 23, 2021.

  1. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Editor.Log crash log attached on bottom, taken from allowing Unity to crash from running out of memory.

    I have this (simplified for understanding) hierarchy for a utility AI system:

    Code (CSharp):
    1. Intelligence
    2.     List<ActionSet>
    3.         List<Action>
    4.             Evaluator
    5.                 List<Consideration>
    The problem I'm having is that when setting things up in the editor, once I get to the list of considerations and try adding one to the list, the editor freezes, stuck in an apparent recursive loop. Memory will eventually be consumed until Unity crashes.

    The SelectType attribute works for a SerializeReferenced Action, and if I add a List<Consideration> at the Intelligence level that works as well. I assume SelectType is not the issue.

    I can remove [SerializeReference] in ConsiderationAuthoringProxy and it doesn't freeze but [SerializeReference] is required. Oddly I can add it back after the list element was added so SelectType works, modify the element, save and restart Unity and the data persists. It seems like whatever is extending the array can't handle this situation, but I can't pinpoint the cause.

    Relevant portion of the serialized file, up to the list of considerations:
    Code (CSharp):
    1.  
    2. MonoBehaviour:
    3.   m_ObjectHideFlags: 0
    4.   m_CorrespondingSourceObject: {fileID: 0}
    5.   m_PrefabInstance: {fileID: 0}
    6.   m_PrefabAsset: {fileID: 0}
    7.   m_GameObject: {fileID: 1803438630}
    8.   m_Enabled: 1
    9.   m_EditorHideFlags: 0
    10.   m_Script: {fileID: 11500000, guid: 19a0239faa0a95f49b460bec5f2ff682, type: 3}
    11.   m_Name:
    12.   m_EditorClassIdentifier:
    13.   ActionSets:
    14.   - Name:
    15.     Value:
    16.       Actions:
    17.       - Name:
    18.         ValueAsAsset: {fileID: 0}
    19.         Value:
    20.           id: 0
    21.     ValueAsAsset: {fileID: 0}
    22.   references:
    23.     version: 1
    24.     00000000:
    25.       type: {class: ActionTest, ns: WOM.IAUS, asm: WOM.IAUS}
    26.       data:
    27.         Evaluator:
    28.           Name:
    29.           Value:
    30.             Weight: 0
    31.             Considerations: []
    32.           ValueAsAsset: {fileID: 0}
    33.  

    Code dump of anything potentially relevant(hopefully in order of importance, sorry it's a lot):

    Code (CSharp):
    1. [Serializable]
    2. public abstract class GenericAsset<T> : ScriptableObject {
    3.     public T Value;
    4. }
    5.  
    6. [Serializable]
    7. public abstract class ScriptableObjectProxy<T, U> where U : GenericAsset<T> {
    8.     public string Name;
    9.     public T Value;
    10.     public U ValueAsAsset;
    11. }
    Code (CSharp):
    1. /// <summary>
    2. /// Contains all action sets an ai agent can perform.
    3. /// </summary>
    4. public class Intelligence : MonoBehaviour, IConvertGameObjectToEntity {
    5.     public List<ActionSetProxy> ActionSets;
    6.  
    7.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {}
    8. }
    Code (CSharp):
    1. public class ActionSetAsset : GenericAsset<ActionSet> {}
    2.  
    3. [Serializable]
    4. public class ActionSet {
    5.     public List<ActionProxy> Actions;
    6. }
    7.  
    8. [Serializable]
    9. public class ActionSetProxy : ScriptableObjectProxy<ActionSet, ActionSetAsset> {}
    Code (CSharp):
    1. /// <summary>
    2. /// Editor authoring for an action, contains all considerations for performing the given action type.
    3. /// </summary>
    4. [Serializable]
    5. public abstract class ActionAuthoring : IConvertGameObjectToEntity {
    6.     public EvaluatorProxy Evaluator;
    7.  
    8.     public abstract void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem);
    9. }
    10.  
    11. [Serializable]
    12. public class ActionAuthoringAsset : GenericAsset<ActionAuthoring> {}
    13.  
    14. [Serializable]
    15. public class ActionProxy : ScriptableObjectProxy<ActionAuthoring, ActionAuthoringAsset> {
    16.     [SerializeReference, SelectType(typeof(ActionAuthoring))]
    17.     public new ActionAuthoring Value;
    18. }
    19.  
    20. //TODO, remove, used for testing
    21. public class ActionTest : ActionAuthoring {
    22.     public override void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {}
    23. }
    Code (CSharp):
    1.     public class ConsiderationAuthoringAsset : GenericAsset<ConsiderationAuthoring> {}
    2.  
    3.     /// <summary>
    4.     /// Base for authoring consideration components in editor. This is necessary as long as
    5.     /// there are missing inspectors for some types, ie: dots AnimationCurve. It's also
    6.     ///  convenient for adding other components if needed.
    7.     /// </summary>
    8.     [Serializable]
    9.     public abstract class ConsiderationAuthoring : IConvertGameObjectToEntity {
    10.         public abstract void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem);
    11.     }
    12.  
    13.     [Serializable]
    14.     public class ConsiderationAuthoringProxy : ScriptableObjectProxy<ConsiderationAuthoring, ConsiderationAuthoringAsset> {
    15.         [SerializeReference, SelectType(typeof(ConsiderationAuthoring))]
    16.         public new ConsiderationAuthoring Value;
    17.     }
    18.  
    19.     //TODO, remove, for testing
    20.     public class ConsiderationTest : ConsiderationAuthoring {
    21.         public float ConsiderationFloat;
    22.  
    23.         public override void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {}
    24.     }
    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(SelectTypeAttribute))]
    2. public class SelectTypeDrawer : PropertyDrawer {
    3.     Type[] implementationTypes;
    4.  
    5.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    6.         return EditorGUI.GetPropertyHeight(property);
    7.     }
    8.  
    9.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    10.         if(implementationTypes is null) {
    11.             implementationTypes = GetImplementations(((SelectTypeAttribute)attribute).FieldType)
    12.             .Where(impl => !impl.IsSubclassOf(typeof(Object)))
    13.             .OrderBy(impl => impl.Name)
    14.             .ToArray();
    15.         }
    16.  
    17.         EditorGUI.BeginProperty(position, label, property);
    18.         if(implementationTypes.Length > 0) {
    19.             string[] implementationNames = implementationTypes.Select(type => $"{type.FullName}").ToArray();
    20.             string propertyFullName = property.managedReferenceFullTypename.Split(' ').ElementAtOrDefault(1);
    21.             int selectedTypeIndex = Array.IndexOf(implementationNames, propertyFullName);
    22.  
    23.             EditorGUI.BeginChangeCheck();
    24.             Rect selectTypePopupRect = position;
    25.             selectTypePopupRect.xMin += EditorGUIUtility.labelWidth;
    26.             selectTypePopupRect.height = EditorGUIUtility.singleLineHeight;
    27.             selectedTypeIndex = EditorGUI.Popup(selectTypePopupRect, selectedTypeIndex, implementationNames);
    28.             if(EditorGUI.EndChangeCheck()) {
    29.                 property.managedReferenceValue = Activator.CreateInstance(implementationTypes[selectedTypeIndex]);
    30.             }
    31.         }
    32.         EditorGUI.PropertyField(position, property, label, includeChildren:true);
    33.         EditorGUI.EndProperty();
    34.     }
    35.  
    36.     Type[] GetImplementations(Type interfaceType) {
    37.         return AppDomain.CurrentDomain.GetAssemblies()
    38.         .SelectMany(s => s.GetTypes())
    39.         .Where(x => interfaceType.IsAssignableFrom(x) && x != interfaceType && !x.IsAbstract)
    40.         .OrderBy(x => x.Name)
    41.         .ToArray();
    42.     }
    43. }
     

    Attached Files:

    Last edited: Feb 23, 2021
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Looks like you have some potentially-massive Linq queries going over all the assemblies, etc.... make sure what you think is a "freeze" isn't you trying to process just a massive list of items. Remember Linq can get extremely piggish memory-wise, and what might seem like a simple query to you could result in a massive use of memory.

    If you refactor to use loops you can start to get some idea of what the counts are, if they are in the dozens or the trillions of numbers range, and how many times you are calling each query.
     
  3. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    I'm not sure that's the issue here, I had a small performance issue prior to this. But I cached the implementation names and added a null check(shown in posted code). LINQ shouldn't freeze from heavyness AND eventually consume all 48 GB of my memory, unless maybe there is recursion in the LINQ, or endless threads for GUI.

    The attribute works in other situations, just not in this specific place in this heirarchy.
     
    Last edited: Feb 23, 2021
  4. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Minimum failing example:

    Code (CSharp):
    1. //Testing interfaces with SerializeReference
    2. public interface IA {}
    3. public interface IB {}
    4.  
    5. //Concrete implementations
    6. [Serializable]
    7. public class AImpl : IA {}
    8.  
    9. [Serializable]
    10. public class BImpl : IB {}
    11.  
    12. //Wrappers
    13. [Serializable]
    14. public class AWrapper { // hierarchy starts with an instance of this
    15.     [SerializeReference, SelectType(typeof(IA))]
    16.     public IA IAImpl;
    17. }
    18.  
    19. [Serializable]
    20. public class BWrapper {
    21.     [SerializeReference, SelectType(typeof(IB))]
    22.     public IB IBImpl;
    23. }
    24.  
    25. [Serializable]
    26. public class BWrapperList {
    27.     public List<BWrapper> BImplWrappers;
    28. }

    AImpl can contain:
    • IB with [SerializeReference]
    • List<IB> with [SerializeReference]
    • BWrapper

    AImpl can NOT contain:
    • List<BWrapper>
    • BWrapperList
    • Either of the above even if [SelectType] is removed from BWrapper.

    BWrapperList is I think most similar to my issue, I'm using that wrapper ("ScriptableObjectProxy") to have an optional ScriptableObject workflow where I can load/save my classes if desired.

    The reorderable list inspector doesn't seem to like having lists of serialized references inside a serialized reference, but only if they're wrapped in another class. And only if the wrapped reference(s) were not marked as [SerializeReference] when the list was first added to, but it can be marked and appended to without issue once the length >= 1.(WUT?)
     
    Last edited: Feb 24, 2021
  5. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Apparently this hack works without causing a freeze(but will freeze if the list length goes back to 0 and another element attempted to be added in the inspector), which looks to me like more evidence of an issue with the creation of the list from the inspector.

    Code (CSharp):
    1. [Serializable]
    2. public class AImpl : IA {
    3.     public List<BWrapper> Value;
    4.  
    5.     public AImpl() {
    6.         Value = new List<BWrapper> {new BWrapper {}};
    7.     }
    8. }
     
    nicolascparreiras likes this.