Search Unity

Bug Serialize reference doesn't seem to work.

Discussion in 'Editor & General Support' started by illinar, Nov 2, 2020.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    EDIT: Updated the code

    I think the test here conclusively shows that it doesn't work in my case. This is also my first time using it, so I'm not sure if it's supposed to work or how. Nontheless:

    The data:

    Code (CSharp):
    1. [CreateAssetMenu()]
    2. public class CityData : ScriptableObject
    3. {
    4.     [HideInInspector] public List<PathPoint> pathPoints = new List<PathPoint>();
    5. }
    6.  
    7. [System.Serializable]
    8. public class PathPoint
    9. {
    10.     public Vector3 position;
    11.     [SerializeReference] public List<PathPoint> outgoingPathPoints = new List<PathPoint>();
    12. }

    First test:

    Code (CSharp):
    1.  
    2.         Debug.Log("PP Test");
    3.         cityData.pathPoints = new List<PathPoint>(2) { new PathPoint(), new PathPoint() };
    4.         cityData.pathPoints[0].outgoingPathPoints = new List<PathPoint>(1) { cityData.pathPoints[1] };
    5.         cityData.pathPoints[0].position = new Vector3(1, 2, 3);
    6.  
    7.         EditorUtility.SetDirty(cityData);
    8.         AssetDatabase.SaveAssets();
    9.  
    10.         Debug.Log(cityData.pathPoints[0].outgoingPathPoints[0] == cityData.pathPoints[1]);
    11.         Debug.Log(cityData.pathPoints[0].position);
    12.         Debug.Log("The End");
    13.  
    upload_2020-11-3_1-36-27.png

    This was ran in play mode. Shows how it should work. But if we actually test how it was serialized we need to restart the play mode.

    The position in PathPoint class is serialized correctly when assign, but the PathPointReference contains nonsense. Let's restart and try this.

    Second test. Ran INSTEAD of the first, and after the play mode restart.

    Code (CSharp):
    1.  
    2.         Debug.Log("PP Test 2");
    3.         Debug.Log(cityData.pathPoints[0].outgoingPathPoints[0] == cityData.pathPoints[1]);
    4.         Debug.Log(cityData.pathPoints[0].position);
    5.         Debug.Log("The End");
    6.  
    upload_2020-11-3_1-35-12.png

    Suddently pp1 doesn't contain the reference to pp2, it contains some nonsense PathPoint instance that doesn't exist anywhere else.

    Do I misunderstand something about Serialize reference, or is play mode a problem? What is going on?

    Other data like "position" in PathPoint is serialized correctly and persists.
     
    Last edited: Nov 2, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Line 11 of your first script you're making a list with 2 empty entries

    Then later you're adding onto that... added stuff will be added AFTER those 2 blank entries

    Is that what you intend? Also, what has been serialized could overwrite stuff you put in code... traditionally you use one way or the other (code or the editor inspector), not both (at least for the same fields).
     
  3. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Actually, no, I'm creating an empty list with capacity of 2 (micro optimization on my part).

    The first test works, so the setup is correct.

    I'm not using inspector. I'm making a runtime level editor that serializes to scriptable objects. I prefer this approach to any other possible approaches. The same editor in custom EditorWindow is at least twice as much code, a lot of hustle, and fewer features.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    If you serialize a reference to something in an asset, that thing must be an asset. It doesn't make sense for an asset to refer to something in a scene, as what is in a scene is transient.

    Now at runtime, that reference will still point to the asset, not the instantiated object(s) you may have created from those original asset references. Is that where you're getting the "nonsense reference" perhaps? It's hard for me to judge from the limited slice of what I can see above.

    But ... but ... but you gotta have hustle!

    I agree though: I like to keep things simple. Code I didn't write is code I don't have to maintain.
     
  5. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    To prove that serialization works (except the references) I assign and log position:

     pp1.position = new Vector3(1, 2, 3);


    upload_2020-11-3_0-47-57.png
     
  6. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I do not serialize anything in the scene. I serialize something in the scriptable object itself.

    This is basically the scenario [SerializeReference] was intended for. For data like graphs. This is graph. It's a collection of points with references to each other.

    I can pretend that [SerializeReference] feature was never added and go back to referring by indexes, but the references are better.

    "that thing must be an asset" No. Again, you can serialize references to assets without any attributes. [SerializeReference] is for saving references to instances of classes within the same serialized Obect. Infact it doesn't work for Object (itself) references at all.
     
    Last edited: Nov 2, 2020
  7. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    But also I'm reading the docs and it seems I'm using it wrong. I made a wrapper "PathPointReference" to have serialized references in a list, but I could have just used the attribute on the list field and it would apply to the contents.

    "- Referenced values cannot be shared between UnityEngine.Object instances. For example, two MonoBehaviours cannot share an object that is serialized by reference. Use ScriptableObjects instead to share data."

    This might be the root of my problems.. Checking.
     
  8. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Removed the wrapper, that didn't help of course. (should have worked anyway)

    What does it mean exactly? Serialized instances of UnityEngine.Object or any instances? In runtime (play mode) I'm keeping refences to the same PathPoint instances in monobehaviours, so I can edit them... But surely this shouldn't interfere with serialization because they are never serialized anywhere except the CityData ScriptableObject (single instance).

    I think it just means not serializing them twice in two different Objects. So that's not it.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    For me to add any more useful guidance here, I think I would have to both understand your exact use case and then construct an experiment to isolate what Unity is doing... and it seems like you're hot on that trail.

    One thing I can suggest is to simplify it even more, to just the minimum number of types of objects and counts of instances that shows the issue.

    Another way to gain insight into Unity's serialization is to use source control, then when you change something you can see in the diff tool what exactly was changed and reason about where your data is actually being serialized. Caveat for this: Unity does NOT actually do a flush-to-disk until you hit Save-Project, which can be very confusing.
     
  10. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I did just simplify it. This is the entire use case. This is the data. The only additional info is that this code runs in play mode. I don't think there is anything else to add or simplify there.

    Not sure what you mean by flush-to-disk, because surely it saves dirty assets to disc right away whenever you call AssetDatabase.SaveAssets(), which I do, or whenever you edit them in inspector.

    But it keeps using the data that is in memory and to flush that I'm exiting and entering the play mode again. And when it's done the second test fails.
     
  11. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    If this is clearer:
    Code (CSharp):
    1.         Debug.Log("PP Test");
    2.         cityData.pathPoints = new List<PathPoint>(2) { new PathPoint(), new PathPoint() };
    3.         cityData.pathPoints[0].outgoingPathPoints = new List<PathPoint>(1) { cityData.pathPoints[1] };
    4.         cityData.pathPoints[0].position = new Vector3(1, 2, 3);
    5.  
    6.         EditorUtility.SetDirty(cityData);
    7.         AssetDatabase.SaveAssets();
    8.  
    9.         Debug.Log(cityData.pathPoints[0].outgoingPathPoints[0] == cityData.pathPoints[1]);
    10.         Debug.Log(cityData.pathPoints[0].position);
    11.         Debug.Log("The End");
    12.  
    13.         Debug.Log("PP Test 2");
    14.         Debug.Log(cityData.pathPoints[0].outgoingPathPoints[0] == cityData.pathPoints[1]);
    15.         Debug.Log(cityData.pathPoints[0].position);
    16.         Debug.Log("The End");
     
  12. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I'm labeling it as a bug.

    A workaround without references was easy, but it now causes me massive issues with Undo. I would really like serialize references to work.