Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Discussion Non-serialized prefab variables demystified

Discussion in 'Scripting' started by RakNet, Jun 28, 2023.

  1. RakNet

    RakNet

    Joined:
    Oct 9, 2013
    Posts:
    313
    It's never been clear to me, nor explained anywhere that I found, how Unity treats non-serialized variables that are a member of a non-instantiated prefab.

    For example:
    Code (CSharp):
    1. class MyPrefab : ScriptableObject
    2. {
    3. public string myString {get;set;} = "Hello World";
    4. }
    5.  
    6. public MyPrefab myPrefabInTheProjectTab;
    7. myPrefabInTheProjectTab.myString = "A new world";
    Does "myString" keep its value past the immediate function call, and if not when is it lost?

    I did some experimentation to try to get more information about this
    1. If all you do is load another scene, the data persists. This is true for both Single and Additive
    2. Resources.UnloadUnusedAssets causes the data to be reset to default as long as that prefab is not referenced anywhere at the time of the call.
    3. Data changes persists between entering and leaving play mode, and even code changes.
    4. [RuntimeInitializeOnLoadMethod] does not work to reset data to defaults, because it only works with static methods.
    In summary, while you can change non-serialized data at runtime on a prefab, the data can be lost at unpredictable times and shouldn't be relied on past the current scope.
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    First, this is not a prefab, this is a ScriptableObject.

    That myString is a property, backed by a string field that lives in the context of MyPrefab's object instance. This is saved on a heap (again as a reference because string is a reference type) and the reference to this instance is kept.

    Given that this object is ScriptableObject (or UnityEngine.Object derivation) the underlying engine owns the actual C++ object and keeps the reference to the C# as well. And as long as this is actively held onto, the instance will live and its data won't change.

    If you switch the environment to one where the underlying engine drops this object (actively marks the object for deletion), the garbage collector will wipe the C# part off at some undisclosed time later.

    There are three general types of data-holding in Unity of which a developer should be aware of: serialized data, engine state, and pure C#.

    Engine state and pure C# represent the most volatile state of data, fed to RAM only, however given that the engine is opaque and its data subjected to internal mechanisms and implementation details which make things more complicated than what is normally the case with standard C#, you're not supposed to even know what happens, you just drive the engine toward optimal behavior, either through instantiation, destruction, or forced persistence.

    This is certainly the case for all UnityEngine.Object instances, where the instances could even survive for longer, but you are reported a fake null object regardless, if it makes sense.

    Other engine state data is completely off the limits and represents the state of the engine itself.

    Serialized data on the other hand is guaranteed to transpire between separate runtimes, and that's the whole point of serialization.

    You also forgot to mention how and when did you exactly instantiate this ScriptableObject. This is the most important piece of information. Most typically you either load it through Resources or you dragndrop it on a component. In both cases it'll come preloaded / pre-instantiated and thus its C# counterpart will be intialized and will never expire unless you move onto a scene that doesn't actively refer to it. Like you've discovered in your point (2) with Resources.UnloadUnusedAssets.
     
    Bunny83 likes this.
  3. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    449
    Just to clarify the concept, serialization means that your object, whatever it is, is transformed from its runtime-internal memory representation into a binary representation that can be stored on a disk or sent over a communication channel. Whenever something needs to be serialized (it is serializable), it means there needs to be a function that transforms it into a binary form, and back again. Anything you ever need to store on disk or send over a pipeline, needs to be serializable.

    Often when we say something "is serialized", we actually mean it is persisted. Persistence means storing something on a disk so that it survives, say, the game restart or a system restart.

    If something is not serializable, you can be sure that it is lost on restart, because it cannot exist anywhere apart from memory even in theory. If it both serializable and persisted, then it can be recovered (from disk).

    This might not answer to the specific question you had in mind and I'm sorry if all this was already clear to you, but it is at least a basic conceptual clarification.
     
    Bunny83 likes this.
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    @Jaakk0S Thanks for that, I kind of glossed over it, for brevity. We've been writing about it to death here and it's a staple thing in Unity, so it should be really easy to find more about the concept, but I guess it's worth reiterating just in case.
     
    KillDashNine likes this.
  5. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    449
    ScriptableObjects are like instantiatable data containers. Your code there that extends ScriptableObject defines the form of the data. You can then create instances of this class from the editor, from eg the Create menu. You can also instantiate them in code with ScriptableObject.CreateInstance, but this is a marginal use case, because their main purpose is to store persistent application data rather than runtime data, in a way that it is easy to use in the editor. For runtime purposes you can use whatever other classes.
     
    spiney199 likes this.