Search Unity

Setting Object Dirty & Serializing

Discussion in 'Scripting' started by Zullar, Oct 25, 2017.

  1. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    tldr; In the editor how do you mark a script/scene as dirty so it's serialized & saved?

    I have a serialized ushort ID part of a MonoBehaviour

    Code (csharp):
    1.  
    2. [SerializeField]
    3. private ushort ID = 0;
    4.  
    The ID is used as a unique identifier for scene objects used for networking. So if there are 3 objects in a scene they would have ID's 1, 2, and 3. I wrote an OnValidate script that does this and works fine.
    Code (csharp):
    1.  
    2. private void OnValidate()
    3. {
    4.   if(ID == 0) //has not been set yet
    5.   {
    6.     ID = GetOpenID(); //search through all scripts in the scene and find the first open ID number
    7.   }
    8. }
    9.  
    However, when I set the ID value in this way it does not mark the script/scene as dirty. So if I change scenes or click Play then the values are lost. Interestingly if I make the scene dirty through some other means (i.e. moving different object's transform) then the values for all objects in the scene (even unmoved ones) will be saved.

    To attempt to make things dirty I've tried...
    -Undeo.RecordObject(gameObject, "SetID");
    -EditorUtility.SetDirty(this);
    -EditorUtility.SetDirty(gameObject);
    -SerializedObject serializedObject = new SerializedObject(this); + serializedObject.ApplyModifiedProperties();
    -UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();

    I also have the same issue with prefab asset that are not part of a scene. If the scripts ushort value is changed, but if you close the Unity Editor and re-open it then the prefab asset ushort value is reverted.

    I feel like I'm missing something. There has to be a simple way to mark an object as dirty or force an object to be serialized/saved!

    Thanks in advance.
     
    Last edited: Oct 25, 2017
  2. ymiroshnyk

    ymiroshnyk

    Joined:
    Jun 22, 2017
    Posts:
    2
    Have you found the fix for this problem?
     
  3. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    No. The only work-around I've found is to completely avoid doing thins during OnValidate(Dirty Scenes does not seem to work within OnValidate).

    I bug reported and Unity repro'd the bug.
    https://fogbugz.unity3d.com/default.asp?962617_sqmfhb1a1gfauj1d
     
  4. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    So I guess the 'resolution' was that you can't Dirty stuff in OnValidate because once OnValidate has completed executing, the thing is marked as not dirty?

    I am using OnValidate on some scriptable objects to do some 'ID Management' similar to you (if ID == 0, ask database for next available ID and set self as dirty), but I am finding that the objects are being marked as dirty correctly. The changes are not showing up on git. If I edit another property of the scriptable object, it does appear in git. I can see the mock change that I made and the ID assignment comes up as a change as well.

    So it effectively means that any changes performed in OnValidate should be deterministic in nature, because those changes aren't saved to disk (unless the asset gets edited some other way after that point, in which case they will get written to disk)?
     
    Last edited: Sep 13, 2019
  5. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,323
    The problem here is that Unity's serialization simply does not support the ushort type. So the SerializeField attribute does not have any effect in this case.

    The way around the issue is to use an int instead of ushort for the ID, at least when serializing the value. You can always cast the value to ushort from the int after deserialization has finished. Here is one possible solution:

    Code (CSharp):
    1.  
    2.     [SerializeField]
    3.     private int id;
    4.  
    5.     public ushort ID
    6.     {
    7.         get
    8.         {
    9.             return (ushort)id;
    10.         }
    11.         private set
    12.         {
    13.             id = value;
    14.         }
    15.     }
    16.  
     
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,323
    If you want to avoid casting the int to ushort every time ID is accessed, another option is to only cast it once after deserialization has finished. This can be done by implementing the ISerializationCallbackReceiver interface.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Example : MonoBehaviour, ISerializationCallbackReceiver
    4. {
    5.     private uint ID;
    6.  
    7.     [SerializeField]
    8.     private int idSerialized;
    9.    
    10.     public void OnBeforeSerialize()
    11.     {
    12.         idSerialized = (int)ID;
    13.     }
    14.  
    15.     public void OnAfterDeserialize()
    16.     {
    17.         ID = (ushort)idSerialized;
    18.     }
    19. }
     
    alex_roboto likes this.
  7. wang37921

    wang37921

    Joined:
    Aug 1, 2014
    Posts:
    102
    Code (CSharp):
    1. private void OnValidate()
    2. {
    3.   if(ID == 0) //has not been set yet
    4.   {
    5.     #if UNITY_EDITOR
    6.     UnityEditor.Undo.RecordObject(this, "Assign OpenID");
    7.     #endif
    8.     ID = GetOpenID(); //search through all scripts in the scene and find the first open ID number
    9.   }
    10. }
    11.  
     
    CliffCawley and Zullar like this.