Search Unity

Official SerializeReference improvements in Unity 2021 LTS

Discussion in 'Scripting' started by Unity_Joseph, Jun 30, 2022.

  1. Unity_Joseph

    Unity_Joseph

    Unity Technologies

    Joined:
    Nov 3, 2016
    Posts:
    16
    In the 2021 LTS release, polymorphic serialization offers improved user collaboration and API access, plus more granular handling of missing types.

    Learn more in our SerializeReference blog post

    We welcome your comments, questions, and feedback!
     
  2. david-wtf

    david-wtf

    Joined:
    Sep 30, 2021
    Posts:
    25
    The mentioned improvements are really great and well received. Good job everyone! Do I assume correctly that the improvements are also available in 2022 and newer streams (so not only the 2021 LTS)?
     
  3. reuno

    reuno

    Joined:
    Sep 22, 2014
    Posts:
    4,929
    That's good stuff, thanks for the update!
     
  4. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,314
    It took a while, but I am glad we finally received quite stable version of SerializeReference :)
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    I couldn't really determine it from the post, is inspector support for SerializeReference now a thing too? Or were the examples using a custom editor?
     
    reuno likes this.
  6. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Technically there's always been inspector support for this; the inspector draws all the managed references that are assigned. It doesn't really work with UIToolkit, though. Before 2022, it doesn't detect when a field changes type, and in 2022 it's a bit buggy in some edge cases, like when two managed references have fields with different types but same name. I've ended up using my own workaround for UIToolkit, even in 2022 because of those edge cases, but I think I'll be able to stop using it sooner or later.

    If you are wondering whether the inspector draws some kind of popup for users to choose the type of a managed reference, it doesn't. You need to take care of creating the object instances, but that doesn't need to be done by inspector. In the first code example of the blog post, you can see that the food list is filled when creating a LunchBox. There are also some nice packages from the community to easily add that popup to your fields. IMO, that's good because there are lots of times where that kind of popup wouldn't fit well the use case.
     
    Last edited: Jul 12, 2022
    Bunny83 likes this.
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Okay I didn't know it could already draw already assigned references. When I poked around it in Unity 2019 it wouldn't even draw the empty collection.

    Nowadays I have Odin Inspector and my own Editor extensions to handle polymorphic lists in a nice streamlined manner. But Unity should really support this out of the box, especially for such a powerful bit of it's own engine, otherwise it'll just languish and be under appreciated.

    I can't be the only one using SerializeReference almost as much as I use SerializeField.
     
  8. DiacPaulAlexandru

    DiacPaulAlexandru

    Joined:
    Oct 20, 2020
    Posts:
    14
    Hello, the only thing holding us from using SerializeReference in our projects is the fact that type info is serialized by type name, we do a lot refactoring on our project, moving classes to other assembly definitions or changing the owner namespaces require us marking the types with the MovedFrom attribute and in case we miss that a lot of things break. Is there any way you could provide us with a mechanism of assigning custom type ids or something like a guid based attribute like [TypeID("my-generated-guid")], this would allow us to refactor code in any way we want and not worry about losing data.
     
    Ghat-Smith, Flavelius, rauiz and 3 others like this.
  9. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    This is awesome. I've been familiar with the SerializeReference improvements for a while, they are very nice, but it's so good to see a blog post and an official thread about this. It's nice to see some acknowledgement of the importance of this kind of development; things like SerializeReference and generics support were world-changing, they deserved more public attention. It's also nice to read in the last paragraph of the blog that the team is continuing to enhance capabilities. The tooling power is my favorite part of Unity, and what this team does is vital to it.

    I have some questions and feedback about handling missing types:

    1. Are there plans to support renaming types, namespaces and assemblies in a more official capacity?
    Currently, it can be done by using the MovedFromAttribute. It seems to be relatively known; I have seen it suggested for this specific case multiple times in the forum and in the Issue Tracker. Yet, there doesn't seem to be official support for this attribute, as there is no mention of it in the docs or in your own blog post about this.

    It'd be nice to know that there's an officially sanctioned way of solving these problems. It's uncomfortable to feel like I'm doing something tricky every time I need to move classes to an assembly definition, or a namespace. It makes one live a little in fear of the attribute becoming unsupported for this case. And there's no good alternative for it; fixing each reference manually can be too much work sometimes.

    2. It'd be nice to have an attribute equivalent to MovedFrom, but for assemblies and maybe namespaces.
    It's relatively common to move code to an assembly definition, or to rename one. Namespace changes are a bit rarer for me, but they do happen from time to time. Something like this could save a lot of time and reduce the chance of mistakes:
    Code (CSharp):
    1. [assembly:ContainsTypesPreviouslyIn("OldAssembly")]
    2. // And maybe even something like this:
    3. [assembly:TypesMovedFromNamespace("OldNamespace", "NewNamespace")]
    3. Are there any updates on the bug that makes components in Prefab Instances loose all their data when a type goes missing?
    This bug was shared by @reuno in this thread. It's a lot more dangerous and uglier than it might seem as first glance, as it sets all the fields in the affected components to their initial values. It doesn't even require using the MovedFromAttribute; it happens whenever prefab assets propagate their data to instances with missing reference types in their overrides.

    This bug is currently making me avoid using SerializedReference in MonoBehaviours, as there's a very real risk of losing a lot of work if someone makes a couple of mistakes. The worst part is that users may not even notice that their data is lost until much later after triggering the bug. Here's an easy way of reproducing it:
    1. Copy this script to a project:
      Code (CSharp):
      1. using UnityEngine;
      2. using UnityEditor;
      3.  
      4. [System.Serializable]
      5. public class ObjectContainer
      6. {
      7.     public Object myObject;
      8. }
      9.  
      10. public class SRComponent : MonoBehaviour
      11. {
      12.     [SerializeReference] public object myReference;
      13.     public string myText = "initial text";
      14.     public int myNumber;
      15. }
      16.  
      17. [CustomEditor(typeof(SRComponent))]
      18. public class SRComponentEditor : Editor
      19. {
      20.     public override void OnInspectorGUI()
      21.     {
      22.         base.OnInspectorGUI();
      23.  
      24.         if (GUILayout.Button("Assign Reference"))
      25.         {
      26.             serializedObject.FindProperty("myReference").managedReferenceValue = new ObjectContainer();
      27.             serializedObject.ApplyModifiedProperties();
      28.         }
      29.     }
      30. }
    2. Create a prefab asset and a prefab instance with the SRComponent.
    3. Click the Assign Reference button in the instance, change the rest of the fields in the instance too.
    4. Rename the ObjectContainer class.
    5. Make a change to the prefab asset's component.
    6. Notice some unexpected errors in the console. If you select the instance, you'll notice all the fields in the component are back to their initial values; even if you restore the name of the ObjectContainer class, the component's data is lost.
     
    Last edited: Jul 12, 2022
    NotaNaN and Bunny83 like this.
  10. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Doesn't the current MovedFrom attribute already cover this? I've moved types around in assemblies before, used this attribute, and all is honky-dory.

    Or do you mean only for namespaces/assemblies? That could probably be solved with a few more constructors in the MovedFrom attribute, rather than more attributes.
     
  11. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    MovedFrom can't be applied to whole assemblies; if you try to apply it to an assembly, you'll get an error. Yes, technically MovedFrom covers all these cases, but it's so impractical in some cases; for a big project, renaming an assembly can mean that you need to put that attribute in a huge amount of places. An attribute that's specific for assemblies would allow you to write a single line of code when you move classes to an asmdef, instead of having to remember all the different classes where you need to put the MovedFrom attribute. It's the same for moving classes from one namespace to another.

    The reason I suggested a name different from MovedFrom for these attributes is that the name wouldn't fit what the attributes would do in some cases. Imagine you move some classes from the default Assembly-Csharp to an asmdef; you didn't move all the code in Assembly-Csharp to that assembly, just some classes. The same case is possible with namespaces; for example, sometimes I move some classes from "MyNamespace" to "MyNamespace.ChildNamespace", but I still keep some other classes in "MyNamespace".

    EDIT:
    I'm thinking that there might be a communication problem on my part. So, it's not very well known, but you can put attributes that apply to an assembly, you just need to put
    assembly:
    before the attribute. The attribute doesn't need to be written before a class, you just put it at the beggining of a file. A common use case is the InternalsVisibleToAttribute, which allows members and types declared internal in one assembly to be accessible from another assembly.

    What I'm suggesting is to be able to declare namespace and assembly changes in the whole assembly.
     
    Last edited: Jul 12, 2022
  12. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    331
    Kirsche and Maisey like this.
  13. AndrewSkow

    AndrewSkow

    Unity Technologies

    Joined:
    Nov 17, 2020
    Posts:
    91
    Thanks for all the feedback, its definitely helpful to hear which areas or improvement are most interesting to our users, e.g. easier UI support for instantiating SerializeReference instances and better support (and documentation) for loading existing data when the types have been renamed or moved.

    We have IN-3730 (internally: UUM-6733) tracking the MovedFrom problem with Prefabs which @reuno reported. We have been able to reproduce it. Thanks Oscar for the additional helpful information you've added.
     
  14. AndrewSkow

    AndrewSkow

    Unity Technologies

    Joined:
    Nov 17, 2020
    Posts:
    91
    BTW here is the code example from that post in text format, to save some typing in case anyone wants to try it out:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public interface Food
    6. {
    7. }
    8.  
    9. [System.Serializable]
    10. public class Sandwich : Food
    11. {
    12.     public string m_Bread;
    13.     public string m_Contents;
    14. }
    15.  
    16. [System.Serializable]
    17. public class Fruit : Food
    18. {
    19.     public string m_Type;
    20.     public int m_Quanity;
    21. }
    22.  
    23. public class LunchBox : ScriptableObject
    24. {
    25.     [SerializeReference]
    26.     public List<Food> m_Contents;
    27. }
    28.  
    29. public class LunchBoxCreator
    30. {
    31.     [MenuItem("Examples/SerializeReference")]
    32.     static void CreateExampleLunchBox()
    33.     {
    34.         LunchBox obj = ScriptableObject.CreateInstance<LunchBox>();
    35.  
    36.         obj.m_Contents = new List<Food>
    37.         {
    38.             new Sandwich() { m_Bread = "Croissant", m_Contents = "Jam" },
    39.             new Fruit() { m_Type = "Banana", m_Quanity = 1 },
    40.             new Sandwich() { m_Bread = "Bagel", m_Contents = "Cream Cheese" },
    41.             new Fruit() { m_Type = "Grape", m_Quanity = 20 }
    42.         };
    43.  
    44.         AssetDatabase.CreateAsset(obj, "Assets/LunchBox1.asset");
    45.     }
    46. }
    47.  
    48.  
     
  15. AndrewSkow

    AndrewSkow

    Unity Technologies

    Joined:
    Nov 17, 2020
    Posts:
    91
    @moonshinebot, regarding: "Do I assume correctly that the improvements are also available in 2022 and newer streams (so not only the 2021 LTS)?"

    Yes we introduced the described changes during 2021 development but the same code is also included in 2022 and beyond.

    By the way: one change in 2022 is that some functionality initially exposed on UnityEditor.SerializationUtility has moved to a new class UnityEngine.Serialization.ManagedReferenceUtility. This was done so that stable ID-related calls can be used at runtime. The actual functionality is identical and code written based on 2021 SerializationUtility can be updated by the API updater when upgrading from 2021 to 2022.
     
    r9shackleford, david-wtf and reuno like this.
  16. Maisey

    Maisey

    Joined:
    Feb 17, 2014
    Posts:
    302
    I can't say this enough, but the ONLY thing I've wanted from SerializedReference for as long as I can remember:
    1. Built-in editor support, e.g. let us choose classes from interfaces etc. (Right now using https://github.com/TextusGames/UnitySerializedReferenceUI)
    2. Fix the issue of class renaming causing loss of data. It's redonkelous we have to depend on MovedFrom attribute. Which if forgotten = bad. (Maybe something like DiacPaulAlexandru suggested? Or add .meta information to custom classes (this requires that the classes are put into their own files though)).
     
    Last edited: Jul 15, 2022
    eses and Kirsche like this.
  17. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    +1 for custom TypeID/alternative
     
  18. yu_yang

    yu_yang

    Joined:
    May 3, 2015
    Posts:
    85
    Will 2021 LTS include UnityEngine.Serialization.ManagedReferenceUtility? We really need this!
     
  19. AndrewSkow

    AndrewSkow

    Unity Technologies

    Joined:
    Nov 17, 2020
    Posts:
    91
    Hi @yu_yang - In 2021 LTS the same methods are exposed on UnityEditor.SerializationUtility.

    So the only difference is:
    -in 2021 the calls are available only in the Editor
    -in 2022 they move to be available for both Editor + Runtime
     
  20. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hello @AndrewSkow. I've been looking for this issue in the issuetracker, but I can't find it. I also tried making the url manually from the ID you shared, like this: https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-6733, but it doesn't work.

    Is there a way to access it? My main objective is to be able to know the status of this bug and which Unity versions are being considered for a fix; although I'd also like to vote for it in case it helps to prioritize it.

    Thank you.
     
  21. NoTuxNoBux

    NoTuxNoBux

    Joined:
    Oct 2, 2020
    Posts:
    34
    This is nice, and brings us a step closer to proper support for dependency injection (such as the likes of Zenject do). It would be nice if Unity supported something similar natively at some point, though I understand it's not that easy as, even if it is made possible to assign a
    Foo
    to a
    SerializableReference
    field
    IFoo
    through the editor, you still have to get that
    Foo
    recognized by the editor somehow, and said
    Foo
    often won't even be a
    MonoBehaviour
    at all.

    It'd require some sort of central repository of services (the service container), which can consist of things such as aliases, class instances that are not necessarily
    MonoBehaviour
    s, where you can centrally determine what gets injected when; you don't want to be specifying all links for every
    MonoBehaviour
    separately, or even for separate scriptable objects, since that curbs the flexibility severely.

    (You also can't set these fields from code, currently, as that requires making them fully
    public
    , whilst they should be initialize-once, preferably passed through the constructor, but a set-once field that can only be read from the class, and not externally, is probably the next best thing.)
     
    CodeRonnie likes this.
  22. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    26
    Hi @oscarAbraham ,
    Your link should now work, the ticket was not public which I think might have been an issue in its particular treatment(not actually 100% certain why). UUM-6733 will be considered for fixes in 2021.x and up. We hope to start work on it later this month.
    Hope this helps.
    Regards,
    Martin
     
    oscarAbraham likes this.
  23. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Thank you very much!

    In case someone else wants to check out this in the issue tracker, here's a link: https://issuetracker.unity3d.com/issues/movedfrom-attribute-doesnt-work-on-prefab-overrides

    I want to encourage anyone that's using SerializeReference to vote for this issue. I don't know how much voting matters in this case, but it's very easy to do. This issue could affect you if you ever use prefabs in combination with SerializeReference, even if you don't use the MovedFromAttribute; you could lose a lot of work by doing common stuff like moving a script to an asmdef or renaming a class/namespace.
     
    Ghat-Smith and reuno like this.
  24. reuno

    reuno

    Joined:
    Sep 22, 2014
    Posts:
    4,929
    Yes let's please all vote on that one :)
     
  25. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    331
    I'd like to suggest a new feature that I think would add a lot of possibilities.
    If a editor support is added, please add a dropdown menu with the possible implementations but add also a Object field in case we'd like to use a Unity.Object that implements that interface

    Code (CSharp):
    1. public class MonoBar : MonoBehaviour
    2. {
    3. [SerializeReference]
    4. public IFoo foo;
    5. }
    6.  
    7. [System.Serializable]
    8. public class Foo : IFoo {}
    9.  
    10. public class FooScriptable : ScriptableObject, IFoo{}
    Thus, the SerializeReference could contains a new serialized instance or a reference to an UnityEngine.Object
     
  26. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    You know that SerializeReference doesn't support UnityEngine.Object, right?
     
  27. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    331
  28. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    It would be nice, but serialising references to assets is completely different to how SerializeReference works.

    Would just add more performance overhead. A common base class works well enough.
     
  29. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53

    Having a built-in solution like that may be interesting yes, but you can also emulate that result yourself quite simply.

    Code (CSharp):
    1. public interface IFoo
    2. {
    3.     int Example { get; }
    4. }
    5.  
    6. public class MyMonoBehaviour : MonoBehaviour, IFoo
    7. {
    8.     [SerializeField] private int example;
    9.     public int Example => example;
    10. }
    11.  
    12. public class FooObject : IFoo
    13. {
    14.     [SerializeField] Object unityObject;
    15.     private IFoo target = null;
    16.     private IFoo Target => target ??= (unityObject as IFoo);
    17.     int IFoo.Example => Target.Example;
    18. }
    19.  
    20. // If the interface is not implemented by many Object types, may be easier to use the proper type directly
    21. // No cast needed, and easier to assign the field through asset/object picker
    22. public class FooScript : IFoo
    23. {
    24.     [SerializeField] MyMonoBehaviour script;
    25.     int IFoo.Example => script.Example;
    26. }
     
    Maeslezo likes this.
  30. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
  31. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    542
    I agree that it shouldn't be the default, but I think a built-in attribute which allows you to add a type selection popup would still be nice instead of using community packages for such a common feature.
    There are many other optional built-in attributes to change the inspector (like number range), so it wouldn't be a exotic request.

    Im not up-to-date but I tried this feature a while ago and when you use the list inspector to add a new entry it would fill the new entry with the same reference as the previous last element. Which is quite annoying for references. Because if you are not careful you will change the properties of both entries (since they are the same reference). It would make more sense to create a new instance or even a null entry instead of using the same reference multiple times.

    A "normal" SerializedField List doesn't behave this way, they create a new instance when you press the add button in the list inspector. Which is the expected behavior imo.

    Was this ever "fixed" or are there any plans to change this behavior for Lists with SerializeReference?
     
    Last edited: Sep 15, 2022
  32. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Well, no. Anything serialised is serialised as values. Don't mistake the 'Reference' part of SerializeReference to mean it's serialising as a reference. It refers to serialising a reference - or better put, a pointer - to the serialised object's class by using its name, namespace and assembly address.

    This is of course not counting shoddy custom inspectors that may produce this unwanted behaviour.
     
    Bunny83 likes this.
  33. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    542
    I was using the default inspector and this issue was excactly what happend, both list entries contained the same reference, even the object.ReferenceEquals method returned true for entry at index 0 and 1 (after using the + button in the list inspector).

    I remember some old post where people also reported the same "issue" but can't find it anymore.
    Which is why I consider it as unexpected behavior and wanted to talk about it again.

    But as I said this was a while ago and I didn't try it again since then.
    So if this issue is fixed by now then Im happy.
     
    Last edited: Sep 15, 2022
  34. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    Not sure if I misunderstand you but SerializeReference does serialize references. The documentation gives the example of serializing a graph, where it'll preserve the references between the nodes in the graph. The limitation is that the references are limited to other SerializeReference objects on the same host object (i.e. MonoBehaviour, ScriptableObject), you cannot serialize arbitrary references.

    I tried on 2022.1 and the default inspector will add a null object instead of duplicating the last one. Not very useful since you cannot assign anything but at least ending up with an array/list containing the same object multiple times isn't possible anymore.
     
  35. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    It serialises reference types, but they are not serialised as references. It still follows the rest of Unity serialisation which is as values, and the Unity serialiser has no means of restoring plain C# references.

    So for a node graph example, imagine you have plain C# object nodes A, B, and C each serialised in a collection with (either with SerializeField, or SerializeReference, it doesn't matter) and each of those had a SerializeReference field that takes another node. If you point nodes A and B to node C, once the data is re-serialised (usually with inspector repainting) or you write the data to disk, you just end up with a 'copy' of C serialised in into the field of A and B that have no connection to the original node C.

    You'd still need to use an GUID system to identify connections between nodes.

    The node graph example is mostly to show that this:
    Code (CSharp):
    1. [System.Serializable]
    2. public class Node
    3. {
    4.     [SerializeReference]
    5.     public Node ConnectedNode;
    6. }
    Won't throw Unity's serialisation depth limit warning, while using SerializeField will.

    Edit: Admittedly I did look at the docs and it sorta does imply it can serialise by reference, but I have not experienced this behaviour. Some testing is required I think.
     
    Last edited: Sep 16, 2022
  36. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    It's explicitly the point of SerializeReference that it will preserve references in this case. You can have A and B point to the same C node and that will be preserved through serialization, you'll end up with only one instance of C.

    You should really read the documentation, it goes into detail how the managed references are assigned IDs and only serialized once inside of a host object. SerializeReference references will then just point to those IDs, not contain an inline copy of the object.

    The limitation of those IDs vs GUIDs is that they are local to the host object. So references to data contained in other MonoBehaviour or SerializedObject instances will be serialized by value. You also need to use SerializeReference on all relevant fields, if a regular serialized field and a SerializeReference field share a managed reference, the regular field will still serialize its own value copy.
     
  37. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    EDIT: The below is wrong and not true so keep on reading for the real answer.

    I understand that, but that's not the behaviour I've seen. I'm not saying that without some experience behind me.

    "Simple" code example using Odin's magic:
    Code (CSharp):
    1. namespace LizardBrainGames.Testing
    2. {
    3.     using System.Collections;
    4.     using System.Collections.Generic;
    5.     using UnityEngine;
    6.     using Sirenix.OdinInspector;
    7.     using Sirenix.OdinInspector.Editor;
    8.  
    9.     [CreateAssetMenu(menuName = "LBG-Testing/Node Container Object")]
    10.     public class NodeContainerObject : ScriptableObject
    11.     {
    12.         [SerializeField]
    13.         private List<Node> nodes = new();
    14.  
    15.         public IEnumerable<Node> AllNodes => nodes;
    16.     }
    17.  
    18.     [System.Serializable]
    19.     public class Node
    20.     {
    21.         [SerializeField]
    22.         private string nodeName = "";
    23.  
    24.         [SerializeReference, ValueDropdown(nameof(SelectConnectedNode))]
    25.         private Node connectedNode;
    26.  
    27.         private IEnumerable<ValueDropdownItem<Node>> SelectConnectedNode(InspectorProperty property)
    28.         {
    29.             List<ValueDropdownItem<Node>> nodes = new() { new ValueDropdownItem<Node>("null", null) };
    30.  
    31.             int index = 1;
    32.  
    33.             foreach (Node node in ((NodeContainerObject)property.SerializationRoot.ValueEntry.WeakSmartValue).AllNodes)
    34.             {
    35.                 nodes.Add(new ValueDropdownItem<Node>($"Node {index}", node));
    36.                 index++;
    37.             }
    38.             return nodes;
    39.         }
    40.     }
    41. }
    Which looks like this:
    upload_2022-9-16_20-5-25.png

    Right now there's a node referencing itself... referencing itself... and I could open the drop downs ad infinitum.

    Now the references do persist... right up until you save. At that point, every 'node' regardless of the same reference or not is written out individually into the assets yaml:
    Code (csharp):
    1.   nodes:
    2.   - nodeName: Test
    3.     connectedNode:
    4.       rid: 1230398963196624969
    5.   references:
    6.     version: 2
    7.     RefIds:
    8.     - rid: -2
    9.       type: {class: , ns: , asm: }
    10.     - rid: 1230398963196624969
    11.       type: {class: Node, ns: LizardBrainGames.Testing, asm: LizardBrainGames-Testing}
    12.       data:
    13.         nodeName:
    14.         connectedNode:
    15.           rid: 1230398963196624970
    16.     - rid: 1230398963196624970
    17.       type: {class: Node, ns: LizardBrainGames.Testing, asm: LizardBrainGames-Testing}
    18.       data:
    19.         nodeName:
    20.         connectedNode:
    21.           rid: -2
    22.  
    You can see above there's two nodes. One pointing to another, and one pointing to nothing. I can keep setting the nodes to point at themselves, but upon serialisation they all become distinct individuals serialised out. And naturally on deserialisation they are all separate instances too.

    I tried different configurations of course. Such as three nodes with two of them pointing to one node, and again, everything got serialised out as separate instances.

    NOW, I totally get that Odin is doing the heavy lifting and drawing all this, and this may be down to how this drawer handles things... BUT, up until I save the references are maintained. If I have one node pointing at itself, and type something in
    nodeName
    , they both update. As soon as I save, ergo, as soon as Unity serialises everything, the connections are broken.

    So on that I'm confident and saying this is down to Unity's serialisation. If Unity is meant to be preserving these references... then we have a bug. Otherwise, I don't think Unity is quite capable (yet) of restoring references like this between serialisation and deserialisation.

    Happy to be proven wrong. I'd love to be wrong here.
     
    Last edited: Sep 17, 2022
    Ghat-Smith likes this.
  38. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    I thought the same! But it seems it's no longer the case, as stated in the documentation:
    I tried, and it seems to work! Except sometimes Odin doesn't display the field as a reference (but values are still shared so that's fine).

    Here is my quick test code (put in a MonoBehaviour script) :
    Code (CSharp):
    1.     public interface ISharingTest { }
    2.  
    3.     [System.Serializable]
    4.     public class SharingTest : ISharingTest
    5.     {
    6.         [SerializeReference] public ISharingTest NestedSharingTest = null;
    7.     }
    8.     [System.Serializable]
    9.     public class NestedClassTest
    10.     {
    11.         [SerializeReference] public ISharingTest SharingTest = null;
    12.     }
    13.  
    14.     [SerializeReference] private ISharingTest sharingTestA = new SharingTest();
    15.     [SerializeReference] private ISharingTest sharingTestB = new SharingTest();
    16.     [SerializeReference] private ISharingTest sharingTestC = new SharingTest();
    17.     [SerializeReference] private ISharingTest sharingTestD = new SharingTest();
    18.     [SerializeReference] private ISharingTest sharingTestE = new SharingTest();
    19.     [SerializeReference] private ISharingTest sharingTestF = new SharingTest();
    20.     [SerializeField] private NestedClassTest nestedClass = new NestedClassTest();
    21.  
    22.     void Test()
    23.     {
    24.         sharingTestB = sharingTestA; // Works
    25.         (sharingTestC as SharingTest).NestedSharingTest = sharingTestB; // Works (it's referencing sharingTestA)
    26.         (sharingTestD as SharingTest).NestedSharingTest = new SharingTest(); // Initialized nested reference field
    27.         (sharingTestE as SharingTest).NestedSharingTest = (sharingTestD as SharingTest).NestedSharingTest; // Works but Odin doesn't show expose it as shared reference if sharingTestD is folded
    28.         (sharingTestF as SharingTest).NestedSharingTest = sharingTestF; // Works
    29.         nestedClass.SharingTest = sharingTestA; // Works
    30.     }
    Which Unity version are you using?
    I'm using Unity 2020.3.38f1, and Odin v2.1.13.0

    EDIT: it's interesting because it's working on Unity 2020, prior SerializeReferences improvements introduced with Unity 2021 LTS, including stable IDs. Maybe a regression somewhere?
     
    Last edited: Sep 16, 2022
  39. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    This was in Unity version 2021.3.7f1 and the latest Odin at the time of writing, 3.1.4.0. That's a bloody old version of Odin you're on.

    It could come down to how these references are managed via code, and my primitive example could easily be erroneous.
     
  40. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    I've tested with the most recent Unity version I have installed (Unity 2021.3.5f1) and it's working too.

    I'm stuck with Oin v2 due to license changes introduced with Odin v3. But I'm gonna download the latest Odin version temporarily and reproduce the test in an empty project to see if the issue is coming from Odin or not. Will edit this message with the results.

    EDIT : my test is working with Unity 2021.3.5f1 and Odin 3.1.4.0. I also made the same test inside a ScriptableObject instead of a MonoBehaviour, it works too.
    So it could be a regression introduced in 2021.3.7f, unlikely in my opinion (but who knows...). Or something wrong in your code example.
     
    Last edited: Sep 16, 2022
  41. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    I've tested your code (Unity 2021.3.5f1 and Odin 3.1.4.0) and got an issue too. I think it's coming from Odin ValueDropdown, which seems to assign a copy of the selected value instead of a reference. Changing value through dropdown never displayed a "reference to" field in my case.
    You can confirm this by assigning a shared reference directly from code. Just add something like that in your Node class and see if you get the expected result:
    Code (CSharp):
    1.         [Button]
    2.         void Test(NodeContainerObjectSpiney nodeContainerObject)
    3.         {
    4.             connectedNode = nodeContainerObject.nodes[0];
    5.         }

    EIDT: one more detail, I'm using Odin Inspector Editor Only Mode, without Odin Serialization. Odin Serialization could also potentially be the culprit here.
     
  42. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    It's not returning a copy, as it doesn't have any means to produce a copy. It's grabbing direct references through Odin's property tree. I confirmed this here:
    There is no Odin serialisation. This is a regular scriptable object using Unity's SerializeReference attribute. If I was using Odin serialisation I'd be deriving from SerializedScriptableObject and not using SerializeReference.

    Odin is only handling the drawing here.

    Really I think it's down to how it's handled through code.
     
  43. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    After a quick look at Odin source code, I confirm that ValueDropdownAttribute is creating a copy of the value selected in the dropdown menu (call to Odin SerializationUtility.CreateCopy which I guess is using reflection or maybe Odin own serialization system to produce a copy).

    Hopefully you can override this default behaviour :
    Code (CSharp):
    1.         [SerializeReference, ValueDropdown(nameof(SelectConnectedNode), CopyValues =false)]
    2.         private Node connectedNode;
    One more thing. In your code, your list of nodes must use SerializeReference too.

    Here is your code with the two mentioned fixes :
    Code (CSharp):
    1. namespace LizardBrainGames.Testing
    2. {
    3.     using System.Collections;
    4.     using System.Collections.Generic;
    5.     using UnityEngine;
    6.     using Sirenix.OdinInspector;
    7.     using Sirenix.OdinInspector.Editor;
    8.     [CreateAssetMenu(menuName = "LBG-Testing/Node Container Object")]
    9.     public class NodeContainerObject : ScriptableObject
    10.     {
    11.         [SerializeReference]
    12.         private List<Node> nodes = new();
    13.         public IEnumerable<Node> AllNodes => nodes;
    14.     }
    15.     [System.Serializable]
    16.     public class Node
    17.     {
    18.         [SerializeField]
    19.         private string nodeName = "";
    20.         [SerializeReference, ValueDropdown(nameof(SelectConnectedNode), CopyValues = false)]
    21.         private Node connectedNode;
    22.         private IEnumerable<ValueDropdownItem<Node>> SelectConnectedNode(InspectorProperty property)
    23.         {
    24.             List<ValueDropdownItem<Node>> nodes = new() { new ValueDropdownItem<Node>("null", null) };
    25.             int index = 1;
    26.             foreach (Node node in ((NodeContainerObject)property.SerializationRoot.ValueEntry.WeakSmartValue).AllNodes)
    27.             {
    28.                 nodes.Add(new ValueDropdownItem<Node>($"Node {index}", node));
    29.                 index++;
    30.             }
    31.             return nodes;
    32.         }
    33.     }
    34. }

    Conclusion: for me everything seems to work as expected.
     
  44. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Yup, I'm happy to be proven wrong here!

    This is good to know in the future as I do love my node basic logic. Before I was using Scriptable Object sub-objects, which is a PITA.

    It also means I owe someone in a Discord group an apology for sending them down a rabbit hole they never needed to...
     
  45. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    I'm happy too!

    I know your feeling, I was using scriptable object for polymorphism before SerializeReference being introduced. That was terrirble.
    Then I upgraded my code to use SerializeReference. But I thought sharing references was not possible at all, so my graph has a list of nodes serialized as references. Each node has an ID, and I used this ID to make references between the nodes themselves.
    Now it seems I could totally rely on Unity serialization system (supposing sharing references is really robust and support multiple level of nesting etc etc). That's really great, although too late for me to change my code.

    EDIT: in fact my nodes IDs are still useful, as it allows me to reference a node in another graph (ie another Unity.Object). It would be cool if Unity could expose the serialize references IDs, and would allow at runtime to retrieve object instance using (UnityEngine.Object, Stable ID) couple.
     
  46. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    From 2021.2+ you have SerializationUtility that contains a couple of methods to work with managed objects and SerializeReference, notably getting/setting the ID of an object, getting an object by ID or getting all IDs used.

    But it's editor only until 2022.2+, where some methods were moved to ManagedReferenceUtility.
     
    spiney199 likes this.
  47. Ghat-Smith

    Ghat-Smith

    Joined:
    Aug 16, 2016
    Posts:
    53
    Oh yes you're right I forgot it already existed!
    I didn't know runtime support had been added in 2022.2. That's great thanks!
     
  48. yu_yang

    yu_yang

    Joined:
    May 3, 2015
    Posts:
    85
    We use 2022 now and find a terrible bug, using SerializeReference in custom class cause prefab error. I had report this bug (CASE IN-16752) already, but no one reply me for a long time.
     
  49. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hi, did you get a response? Could you tell us what happens so we can be aware?
    Thanks.
     
  50. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Last edited: Oct 28, 2022