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

How to keep in-memory ScriptableObject alive after BuildPipeline.BuildPlayer?

Discussion in 'Editor & General Support' started by Peter77, Mar 17, 2020.

  1. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,438
    How can I make a ScriptableObject in-memory instance survive a domain reload?

    I've a ScriptableObject that contains a bunch of build related settings and code. Before the build starts, I create an in-memory representation of the ScriptableObject, because that way I don't need to care if the ScriptableObject code caches data in itself. It always starts fresh, it's always used only once.

    However, BuildPipeline.BuildPlayer triggers one or multiple domain reloads and seems to throw away my ScriptableObject instance.

    Running this code:
    Code (CSharp):
    1. var scriptableObject = ScriptableObject.CreateInstance<ScriptableObject>();
    2. Debug.LogFormat("Before: scriptableObject == null? {0}", scriptableObject == null);
    3. BuildPipeline.BuildPlayer(...);
    4. Debug.LogFormat("After: scriptableObject == null? {0}", scriptableObject == null);
    ... outputs:
    Code (CSharp):
    1. Before: scriptableObject == null? False
    2. After: scriptableObject == null? True
    I want to keep the scriptableObject alive even after BuildPipeline.BuildPlayer, in this case it should display "null? False" in both cases.

    How would I do this?

    I'm using Unity 2018.4.4f1.
     
  2. MechEthan

    MechEthan

    Joined:
    Mar 23, 2016
    Posts:
    166
    Heya @Peter77,

    My first thought is to force Unity to serialize & save your ScriptableObject instance to the asset file right before invoking BuildPlayer, and then load it again right after.

    UnityEditor.EditorUtility.SetDirty(yourInstance);
    UnityEditor.AssetDatabase.SaveAssets();


    There may be a better answer -- (like, is there a way to put your ScriptableObject instance in the appdomain used for building?) -- but this is all I can think of to help unblock you right now.
     
    Peter77 likes this.
  3. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,438
    Thanks for answer!

    I was experimenting with your idea and it seems to work. I also needed to store the ScriptableObject reference in a static field. This made the non-serialized fields in the ScriptableObject to keep alive/intact.

    Loading the asset though seems to load another state of the asset, where the non-serialized fields have their default values.

    It's still a big question-mark for me though :)
     
    Last edited: Mar 20, 2020
  4. MechEthan

    MechEthan

    Joined:
    Mar 23, 2016
    Posts:
    166
    Yeah, I would expect the SO instance from loading the asset to have default values in the non-serialized fields.
    e.g.
    var scriptableObject2 = Resources.Load<ScriptableObject>(assetPath);

    Or are you doing something different to somehow load an asset into an existing instance?

    But, I guess I'm a bit confused now: You're saying that keeping an external static ref to the SO instance makes its entire state survives domain reload? That's... not expected at all. I have no idea what's going on then. :eek:
     
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,438
    After more trial&error, my final solution is to have a static variable to that ScriptableObject and to apply some HideFlags.

    It boils down to pretty much this:
    Code (CSharp):
    1. static BuildConfig s_ActiveConfig;
    2.  
    3. static void Build(BuildConfig config)
    4. {
    5.     s_ActiveConfig = ScriptableObject.Instantiate(config);
    6.     s_ActiveConfig.hideFlags |= (HideFlags.DontUnloadUnusedAsset | HideFlags.DontSave);
    7.  
    8.     s_ActiveConfig.PreBuildPlayer();
    9.     BuildPipeline.BuildPlayer();
    10.     s_ActiveConfig.PostBuildPlayer();
    11.  
    12.     ScriptableObject.DestroyImmediate(s_ActiveConfig);
    13.     s_ActiveConfig = null;
    14. }
    This seems to work so far. s_ActiveConfig is still there after BuildPlayer and it also holds the same data as before BuildPlayer, so it seems to survive just how I wanted it.
     
    MechEthan likes this.
  6. MechEthan

    MechEthan

    Joined:
    Mar 23, 2016
    Posts:
    166
    Weird, but helpful! Thank you for the followup, I'm sure I will need this one day =)
     
    Peter77 likes this.