Restarting unity clear list in scriptable object

Hi!

As topic states: Restarting unity clear list in scriptable object. Last 8 hours I have spent learning everything about serialization in unity. I am aware of SetDirty(), how serialization works on top level, even on C++ level and still I dont understand what is happening :C

Here I have my beatiful behaviour tree which is scriptable object. It can add node to list or add child to node’s children.

    [CreateAssetMenu(menuName = "New AI behaviour tree")]
    public class BehaviourTreeSO : ScriptableObject
    {
        [SerializeField] private List<ScriptableObject> nodes = new();
      
        public IReadOnlyList<NodeSO> Nodes => nodes.Cast<NodeSO>().ToList();
        
        public void AddNode(NodeSO node)
        {
            node.Guid = Guid.NewGuid().ToString();
            node.name = node.Name;
            nodes.Add(node);
            
            AssetDatabase.AddObjectToAsset(node, this);
            AssetDatabase.SaveAssets();
        }

        public void AddChild(NodeSO parent, NodeSO child)
        {
            parent.Children.Add(child); 
            child.Parent = parent;
            EditorUtility.SetDirty(parent);
            EditorUtility.SetDirty(child);
        }
    }

Here is my node class:

    [Serializable]
    public class NodeSO : ScriptableObject
    {
        [field:SerializeField] public NodeSO Parent { get; set; }
        [field:SerializeField] public List<NodeSO> Children { get; protected set; }
    }

And here as a bonus serialized Behaviour tree:

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-8780635226977283878
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 44b41a5880c3418fa31ef5d67850d25f, type: 3}
  m_Name: Output Port Node
  m_EditorClassIdentifier: 
  <Parent>k__BackingField: {fileID: 0}
  <Children>k__BackingField:
  - {fileID: 8310260071823163259}
--- !u!114 &11400000
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 4d8d32fee06540eca76f96d062291480, type: 3}
  m_Name: New Behaviour Tree SO
  m_EditorClassIdentifier: 
  nodes:
  - {fileID: -8780635226977283878}
  - {fileID: 8310260071823163259}
--- !u!114 &8310260071823163259
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 44b41a5880c3418fa31ef5d67850d25f, type: 3}
  m_Name: Output Port Node
  m_EditorClassIdentifier: 
  <Parent>k__BackingField: {fileID: -8780635226977283878}
  <Children>k__BackingField: []

The question is, why when i restart unity list of children is empty? As we can see on serialized behaviour tree file there is a children with id that matches id of one of created nodes! And why it’s marked as monobehaviour as it is a scriptable object?!

This happened to me a few times when writing a similar system using nested Scriptable Objects.

TL;DR, I ended up needing to ensure that the object itself was considered dirty after adding the nested Scriptable Objects. For your setup there, it doesn’t look like the SetDirty and SaveAssets calls are synced up, so Unity might not be seeing your changes even though a new object was added as a nested child?

Relevant bit from my own setup, where I had to mark the object as dirty after adding its children:

foreach (var spellNode in spellNodes)
{
    // Adding objects and stuff like: 
    //
    AssetDatabase.AddObjectToAsset(payload, spellGraph);
    AssetDatabase.SaveAssets();
}

// Final prep stuff, then finally:
//
AssetDatabase.SaveAssets();
EditorUtility.SetDirty(spellGraph);