Search Unity

Question how to use ISerializationCallbackReceiver interface properly on ScriptableObject-derived class?

Discussion in 'Scripting' started by Solihin100, Aug 6, 2022.

  1. Solihin100

    Solihin100

    Joined:
    Mar 7, 2021
    Posts:
    8
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4.  
    5. public class LineDrawerData : ScriptableObject, ISerializationCallbackReceiver
    6. {
    7.     public List<List<Vector3>> Points;
    8.  
    9.     public List<Vector3> Points_value;
    10.     public List<int> Points_1Dsizes;
    11.  
    12.     // Other fields ....
    13.  
    14.     public LineDrawerData()
    15.     {
    16.         Points = new List<List<Vector3>>(new List<Vector3>[] { new List<Vector3>() });
    17.  
    18.         // Initialization of oher fields ....
    19.     }
    20.  
    21.     public void OnBeforeSerialize()
    22.     {
    23.         if (Points == null)
    24.         {
    25.             Debug.Log("OnBeforeSeralize() is skipped");
    26.             return;
    27.         }
    28.  
    29.         Debug.Log("OnBeforeSeralize() is started");
    30.  
    31.         Points_value = new List<Vector3>();
    32.         Points_1Dsizes = new List<int>();
    33.  
    34.         int TotalSize = 0;
    35.  
    36.         for (int i = 1; i <= Points.Count; i++)
    37.             TotalSize += Points[i - 1].Count;
    38.  
    39.         for (int i = 1; i <= TotalSize; i++)
    40.         {
    41.             Points_1Dsizes.Add(Points[i - 1].Count);
    42.  
    43.             for (int j = 1; j <= Points_1Dsizes[i - 1]; j++)
    44.                 Points_value.Add(Points[i - 1][j - 1]);
    45.         }
    46.     }
    47.  
    48.     public void OnAfterDeserialize()
    49.     {
    50.         Debug.Log("OnAfterDeseralize() is started");
    51.  
    52.         Points = new List<List<Vector3>>();
    53.  
    54.         for (int i = 1; i <= Points_1Dsizes.Count; i++)
    55.             Points.Add(new List<Vector3>());
    56.  
    57.         int j = 1;
    58.         int k = 1;
    59.         for (int i = 1; i <= Points_value.Count; i++)
    60.         {
    61.             if (j <= Points_1Dsizes[k - 1])
    62.             {
    63.                 Points[k - 1].Add(Points_value[i - 1]);
    64.                 j++;
    65.             }
    66.  
    67.             else
    68.             {
    69.                 k++;
    70.                 j = 1;
    71.             }
    72.         }
    73.     }
    74. }
    75.  
    Hello there, I got a problem about ISerializationCallbackReceiver inteface. In my case, I have multi-dimension array field that must be "flatten" into 1D-array for it to be serializable. This class is for editor-purpose only. Here's what I found:
    > Data is perserved (saved) when: editing in custom window, importing scripts, and playing the game (not build)
    > Data is lost when: Stop playing the game and restarting Unity

    Many many working example in other threads as well in documentation is if it used in Monobehavior-derived class. Is there any logic error in my coding? do I use ISerializationCallbackReceiver wrongly? How to properly use ISerializationCallbackReceiver for ScriptableObject-derived class?
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,005
    The ISerializationCallbackReceiver is only responsible for converting non serializable data to serializable data. It does not change how or when data is serialized. So your issue has to be in the way you actually change your data. You most likely do not use the SerializedObject / SerializedProperty to edit the data as those would directly edit the serialized fields (so your one dimensional array). If you manually edit your nested List, it would not trigger a serialisation to disk unless you use Undo.RecordObject before you apply your changes. You have to use RecordObject to tell Unity this is the old state. After the current GUI callback has finished, Unity will then check the recorded object(s) if there are any changes and apply the changes. You probably do not do that at the moment.

    ps: Is the data you want to store an actual 2d array? So are the two dimesions actually uniform? Currently you have jagged / nested Lists. So technically each nested list could have a different element count. Do you really need that?

    Note that you can get rid of the whole conversion by using a single intermediate class that is serializable.

    Code (CSharp):
    1. [System.Serializable]
    2. public class Row
    3. {
    4.     public List<Vector3> points;
    5. }
    6.  
    7. public List<Row> points;
    Unity can serialize this list out of the box. However direct manipulations still need to be communicated through either the Undo class or the SerializedObject / SerializedProperty mechanic.

    Note: Unity will "serialize" your class for various reasons. Serializing it does not mean that it is actually stored on disk. This only happens when the object is marked as dirty.
     
  3. Solihin100

    Solihin100

    Joined:
    Mar 7, 2021
    Posts:
    8
    Ahh... now that you mention it, how can I forget the old friend EditorUtility.SetDirty() (I don't need it to be undoable).

    Regarding nesting class, I wonder if this technique can be implemented in 3D (or more) jagged array. I'll just try that myself, I guess.

    Thanks for pointing what I miss, man!

    ... okay, now how to close a thread ...
     
    Last edited: Aug 7, 2022