Search Unity

I... I think SceneManager.UnloadScene is destroying *prefabs* (not instances). What? O_o

Discussion in 'Scripting' started by invicticide, Dec 31, 2015.

  1. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    In our game we stream visual background props past the camera to create a sense of continuous forward motion. We choose each prop from a graph at random; the graph is basically a flowchart of "if the last-spawned prop is *this*, then pick the next prop from *these*". We have prefabs for each prop, and we also have prefabs for each graph. The graph prefabs contain references to prop prefabs. We never instantiate graphs, only props.

    This system has worked well for over two years. The other day I hooked up load screens and for the first time made use of additive async loads and the new SceneManager API. The load screen is itself a scene (it has some nice load screen graphics and a simple typical "spinner" animation) which contains a script which fires an additive async load of the target scene, with AsyncOperation.allowSceneActivation disabled. Once the load is complete and a couple other conditions are met, we activate the new scene, run some initialization in that scene, then fade out the load screen to reveal the now-live target scene. Once the load screen is done fading out, we unload it with SceneManager.UnloadScene.

    While the load screen is still fading out, both scenes are live and visible. That means you can see our props streaming past the camera in the target scene. This works fine.

    After the fade out completes and we've unloaded the load screen scene, the next time we try to get a prop reference from the prop graph we get back a null. Remember that graphs store references to *prefabs*, not instances in the scene. It seems that our prefab reference magically disappeared the moment we unloaded our previous scene... but I would not expect non-instanced prefabs to be considered part of any scene, and I certainly wouldn't expect them to *ever* disappear during execution under any circumstances.

    Incidentally, none of this behavior occurs in the editor; it only occurs in standalone builds. I dev on Mac OS X and so far that's the only platform I've tested. The same behavior occurs in both 32- and 64-bit standalone builds.

    I'm posting this here instead of filing a bug report because I don't feel like I have a great sense of what the bug actually is, yet. It *seems* like our prefab references are disappearing, but recall that graphs are themselves non-instanced prefabs, so why aren't we getting a NullReferenceException when calling into the graph to ask for our next prop? Our reference to the graph prefab seems to still be valid; only our references to prop prefabs have disappeared. I don't know if this is some random selection of prefabs disappearing, or if it's all of them but there's a quirk I'm missing in our prop graph code that makes it still able to work under those circumstances (I don't think so, but I'm not infallible), and I haven't yet figured out a good way to concretely prove or disprove any of that.

    So I guess I'm looking for two things:
    • Has anyone else seen/reproduced similar behavior?
    • Any ideas about tests that can prove/disprove any of the components of this behavior, without the aid of editor inspection? (Since the problem does not repro in the editor.)
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Prefabs still take up memory. They contain all the parts of the GameObjects that would ever have to be instantiated. This includes mesh data and all that sort. Just none of it has been activated. Copies of them are made when you call 'Instantiate'. So they too must be loaded/unloaded with scenes.

    The prefabs that are loaded into memory are determined by if the scene has references to them. You can also force the inclusion of them by loading them through AssetBundles or from Resources.

    Unloading a scene will remove any prefabs that were in the scene unloaded. Any new scene when loaded would have the prefabs it needs loaded as well. Unloading all scenes (caused by loading a whole new scene not additively) will purge everything from memory with the assumption that the new scene will load what is needed for itself (this may cause some issues if you load things from Resources outside of a Unity Object).

    I am unsure how you have your scenes set up. But if say for instance you have it where SceneA contains prefabs for X and SceneB also contains prefabs for X. And you load SceneA, then load additively SceneB, then unload SceneA... it might remove X... I haven't tested, but this is a possibility. SceneManager is so new that it might not include a check that a currently loaded scene may also need that resource...

    but like I said, I don't know how you have your scenes set up, and I don't exactly have the time to test this. But this might be it, which would be a bug.

    Or it might be something.

    But at least this informs you as to how prefabs work when it comes to loading and unloading.


    I'd suggest breaking this down into the simplest code you can to recreate the issue to attempt to figure out the issue. And if it results in what is a unity bug, submit a bug report, will that simple example project.
     
  3. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    Yeah, this is why this is so weird. Scene A (load screen) doesn't refer to any prop prefabs. Scene B (the game level) does refer to prop prefabs. The sequence of events seems to be like this:
    1. Scene A is loaded and begins to execute
    2. Scene B is loaded additively and asynchronously
    3. Scene B finishes loading; prop prefabs should now be loaded (among other things)
    4. Scene B begins instantiating props (successfully)
    5. Scene A is unloaded
    6. Scene B can no longer instantiate props (references to those prefabs have become null)
     
    Xrank and felixmann like this.
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    How are these prefabs loaded?

    Do you have a GameObject with a script that references them? Or are you loading them from Resources?

    Also, you mentioned a prefab that references prefabs. How does this work in the structure? Is the prefab that is referencing the others still available? Could it maybe be since those sub prefabs have no true owner they get purged (the only thing referencing them is itself a prefab, or non-scene object, so Unload sees it as being unused by anything)?

    Can you recreate this in a simple example project?
     
  5. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    There are three relevant classes:
    • BackgroundProp: A single prop that will appear in the background and scroll past the camera. We have prefabs for lots of different BackgroundProps which differ only in what visuals are attached to them.
    • PropGraph: Defines the graph of which props are allowed to follow which. For each prop, there is a list of props that are allowed to spawn immediately after it. All of these are references to BackgroundProp prefabs. PropGraphs themselves are also prefabs.
    • BackgroundLayer: Defines a parallax scrolling layer for the 2D background. Multiple BackgroundLayers exist in the scene, and each one references a PropGraph prefab which defines which props are allowed to spawn on that layer.
    So, our loading screen scene (A) doesn't reference any of this stuff; it's got its own assets for the presentation of the loading screen, alone. Our gameplay scene (B) contains BackgroundLayer instances, which refer to PropGraph prefabs (non-instanced), which in turn refer to BackgroundProp prefabs (non-instanced).

    A loads B additively and asynchronously, at which point B starts updating its BackgroundLayers, which query PropGraphs for which BackgroundProp prefabs to spawn, and then instance those BackgroundProp prefabs. This works fine until A is unloaded, at which point the PropGraphs start returning null instead of any BackgroundProp prefab references.

    Concerning the dependency graph, I'd think it would look like:
    • Scene directly references (in fact, contains) BackgroundLayer instances
    • BackgroundLayers directly reference PropGraph prefabs (so those should stay loaded - and they seem to)
    • PropGraph prefabs directly reference BackgroundProp prefabs (so those should also stay loaded - but they actually seem to disappear)
    So maybe when UnloadScene is called and the dependency graph is checked to determine which assets can be unloaded, it's not walking through prefab->prefab references? That could be a valid hypothesis, and I could probably construct a stripped-down test for that.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Yeah, I'm betting BackgroundProp, since the only thing that references them is PropGraph which isn't a 'live' object, they're getting removed.

    Not sure if unity considers this to be expected behaviour, or if they would consider it to be a bug.

    Like I said before, create an example project of this, bare-bone, and submit it as a bug. Unity will decide if it actually is, or if it's considered expected behaviour.


    As a work around. Instantantiate PropGraph. (this could actually act to confirm our hypothesis... if BackgroundProp doesn't get destroyed if PropGraph is instantiated, then that's what's happening).
     
  7. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    I was able to put together a tiny example project that reproduces the issue. It's actually surprisingly simple:

    If you have a scene A, then additively load a second scene B, then call SceneManager.UnloadScene on A, indirect references in B to non-instantiated prefabs in the project will become invalid, even if A did not contain any references or dependencies on those same prefabs. This behavior does not occur in the editor; only in standalone.

    By "indirect references" I mean that an object in scene B contains a reference to a non-instantiated prefab X, and X in turn contains a reference to a non-instantiated prefab Y. After unloading scene A, X remains valid, but Y disappears.

    It seems unexpected that non-instantiated prefabs would *ever* be destroyed; moreso that they would be destroyed by unloading a scene which contains no references to them; and even moreso when a still-loaded scene *does* contain references to them.

    I've submitted a bug report (758692) with that example project. Also, I had previously submitted a bug which was essentially just a copy of this thread (on the advice of the @Unity3D Twitter account) which is now deprecated; that bug was 758191.
     
    Malkyne and lordofduct like this.
  8. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    Hi, is this been fixed/patched....Some help anywhere?

    I am receving this behavior as well. Additively load a new scene and all your references of prefabs are destroyed in your other scenes.
    I have also tried to Resource.Load() around this, this fails as well as soon as it's loaded one it fails to load another after a sceneload. I am testing on Android for the build.
    Everything works perfect in Editor then build it and failing to load.


    Code to Instantiate:
    Code (csharp):
    1.  public GameObject m_Prefab;
    2.  
    3. void  Start()
    4. {
    5.      Instantiate(m_Prefab);
    6. }
    7.  
     
  9. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    Further Investigations, I have now removed all SceneManager.UnloadScene (causing the app to behave oddly of course) and as if by magic all Resource.load and prefab references instantiates are now working perfectly.

    I have tested both parameter types for unloadscene (name and build Idx) Unity v5.3.1f1.

    This is serious bug, that needs at least a workaround and fixing soon.
     
  10. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    Further Investigations and workaround:

    I had a hunch that the as the new scenemanager create a new scene called DontDestroyOnLoad that anything in there would do just that. So I created a gameobject with a singleton like monobehaviour this is placed in the first loading scene. The Script has all the prefabs you want to keep for the app and then instead of resource.load or using the prefab reference you call the instance of the sciprit and loading then occurs and the assets are never destroyed.

    This is a bit of a quick hack to test the hunch and get something working for my own progress.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class AssetSceneLoader : MonoBehaviour {
    6.  
    7.  
    8.     public List<GameObject> m_AssetsPrefabs = new List<GameObject>();
    9.  
    10.     private static AssetSceneLoader instance;
    11.  
    12.     public static AssetSceneLoader Instance
    13.     {
    14.         get
    15.         {
    16.             if (instance == null)
    17.             {
    18.                 GameObject go = new GameObject();
    19.                 instance = go.AddComponent<AssetSceneLoader>();
    20.                 go.name = "AssetSceneLoader";
    21.             }
    22.             return instance;
    23.         }
    24.     }
    25.  
    26.     void Awake () {
    27.  
    28.         if (instance == null)
    29.         {
    30.             instance = this;
    31.             DontDestroyOnLoad(gameObject);
    32.         }
    33.         else
    34.         {
    35.             Destroy(gameObject);
    36.             gameObject.SetActive(false);
    37.         }
    38.     }
    39.  
    40.     public GameObject GetGameObjectPrefab(string PrefabName)
    41.     {
    42.         m_AssetsPrefabs.RemoveAll(obj => obj == null);
    43.  
    44.         Debug.Log("Getting GameObject Prefab " + PrefabName + "Count : " + m_AssetsPrefabs.Count);
    45.  
    46.         GameObject go =  m_AssetsPrefabs.Find(obj => obj.name == PrefabName);
    47.  
    48.         if(go == null)
    49.         {
    50.             Debug.LogWarning("Cannot Find Gameobject Prefab");
    51.         }
    52.         else
    53.         {
    54.             return go;
    55.         }
    56.  
    57.         return null;
    58.     }
    59. }
    60.  
    Place this on a gameobject and then access with:
    Code (csharp):
    1.  AssetSceneLoader.Instance.GetGameObjectPrefab("Prefab_Name");
    This needs a lot more work and a fix from Unity.
     
  11. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    FWIW Unity recently reproduced and confirmed the bug (758692). No word yet on an ETA for a fix, but at least it's not just languishing unnoticed in a forum thread. \o/
     
    lordofduct likes this.
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
  13. FFGSoftware

    FFGSoftware

    Joined:
    Aug 22, 2013
    Posts:
    14
    Great to hear! We ran into this very issue last week. Thank you for submitting the report.
     
  14. llutman

    llutman

    Joined:
    Jul 29, 2013
    Posts:
    4
    Ran into this problem today using 5.3.1f1.

    1) Load SceneA
    2) GameObject prefab = Resources.Load("MyPrefab", typeof(GameObject)) as GameObject;
    3) yield return SceneManager.LoadAsync("SceneB", LoadSceneMode.Additive);
    4) SceneManager.UnloadScene("SceneA");
    5) GameObject prefab = Resources.Load("MyPrefab", typeof(GameObject)) as GameObject;

    After (2), the prefab variable holds a valid reference.
    After (5), the prefab variable is null.

    It appears that calling UnloadScene somehow destroys the prefab, and it can't be loaded again. This only happens on a device, it works fine in the editor.

    I'd like to look at (and vote on) issue 758692 in the issue tracker, but the issue page doesn't seem to exist (it just redirects to the main issue tracker page). If I search for the issue number, it finds the issue, but again the link in the search results just links back to the main issue tracker page.
     
  15. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    Can confirm. I Ran in the same issue in 5.3.1p3 for our game Kôna. We load/unload scenes for houses and such and use prefabs that we load/unload (using Resources.Load) for terrain tiles. As you can expect, the terrain chunk prefabs get deleted, and we got big holes where we should have a terrain tile because the prefab asset has vanished. So yes, that's a BIG issue for us.
     
  16. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
  17. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    Meanwhile, this ugly hack works for me:

    var prefab = (GameObject) prefab;
    int correctIndex = "whatever scene index that isn't unloaded or whatever"
    if (assetGameObject.scene.buildIndex != correctIndex) { SceneManager.MoveGameObjectToScene(assetGameObject, correctIndex); }
     
  18. nngafook

    nngafook

    Joined:
    Sep 7, 2013
    Posts:
    5
    After days of turning every link on google purple, I finally find this thread that reassures me that this isn't an isolated case in my codebase. Thank God!
    I really hope Unity pushes the actual fix out soon.
     
  19. SuperUltraHyper

    SuperUltraHyper

    Joined:
    Sep 7, 2015
    Posts:
    112