Search Unity

Possible bug: losing references inside prefabs in Windows Store Apps

Discussion in 'Editor & General Support' started by CanisLupus, Dec 4, 2013.

  1. CanisLupus

    CanisLupus

    Joined:
    Jul 29, 2013
    Posts:
    427
    We are facing a bug that only occurs in Windows Store App builds (as far as we tested). I'll first and briefly describe our test project.

    We created 2 scenes in Unity: Scene1 and Scene2. Scene1 contains two objects:
    1. the default camera with a Scene1.cs script attached
    2. another object with a PrefabHolder.cs script

    Scene2 contains only:
    1. the default camera with a Scene2.cs script

    Besides these, we also have a "TestPrefab" asset that has a TextureHolder.cs script (it also has a material and renderer for debug purposes). Lastly, we have a Texture for tests.

    That is everything.

    ---------------------------------------------------------------------------------------------

    Now onto the scripts. The TextureHolder allows us to add the test texture to TestPrefab in the inspector. Consequently, the prefab is saved as an asset with the TextureHolder script attached, and the script already has our test texture set.

    Code (csharp):
    1. public class TextureHolder : MonoBehaviour
    2. {
    3.     public Texture texture;
    4. }
    Next, PrefabHolder is a singleton, which means that it has a static variable referencing the current PrefabHolder instance (set on Awake). This seems to be relevant to our problem. This script is attached to object 2 of Scene1, as I've said before. In the inspector, we set the TestPrefab asset described above in the "public GameObject Prefab", seen below.

    Code (csharp):
    1. public class PrefabHolder : MonoBehaviour
    2. {
    3.     public static PrefabHolder Instance;
    4.     public GameObject Prefab;
    5.  
    6.     void Awake() {
    7.         Instance = this;
    8.     }
    9. }
    The Scene1 script, below, essentially waits for the user to press Enter - to load Scene2 - or W (the key is irrelevant) to Instantiate a copy of the prefab saved in the PrefabHolder instance. It then gets the texture from the instantiated object and sets it in the object for us to see (this is just for debugging, really).

    Code (csharp):
    1. public class Scene1 : MonoBehaviour
    2. {
    3.     void Update () {
    4.         if (Input.GetKeyDown(KeyCode.Return)) {
    5.             Application.LoadLevel("Scene2");
    6.         }
    7.  
    8.         if (Input.GetKeyDown(KeyCode.W)) {
    9.             GameObject go = GameObject.Instantiate(PrefabHolder.Instance.Prefab) as GameObject;
    10.             TextureHolder holder = go.GetComponent<TextureHolder>();
    11.             go.renderer.material.mainTexture = holder.texture;  // visual aid for debugging
    12.         }
    13.     }
    14. }
    The Scene2 script simply loads Scene1 when enter is pressed (similar to the first "if" above).

    ---------------------------------------------------------------------------------------------

    This setup works in the Editor, in a Windows standalone build and in an Android build. We start, press W (touch in the case of Android), the texture appears; we change the scene with Enter, change it again (to Scene1), press W, the texture appears again. Everything is fine.

    HOWEVER, in a Windows Store App (Metro) build, when we return to Scene1 after Scene2, the "holder.texture" is null, which can be seen in our debug prints and also in the fact that the texture displayed is blank. If we go to Scene2 again and back to Scene1, everything is fine again. If we repeat afterwards, it is blank. Always correct, blank, correct, blank, etc.

    - If, instead of switching between two scenes, we make the first scene load itself again on Enter, the problem never occurs.
    - If, instead of using 2 scenes we use 3 (scene 1 calls 2, 2 calls 3, 3 calls 1), the problem also never occurs.
    - The weirder part: if we use 4 scenes (following the pattern above), the problem returns. Basically, if Scene1 is an even load (2nd, 4th, etc): the problem occurs; then it doesn't; then it does again, etc. This behavior is consistent.

    ---------------------------------------------------------------------------------------------

    Some added information:

    - I attached a package with the test project files.
    - Doing "Instance = null" inside the PrefabHolder's OnDestroy method seems to solve the problem. Might this be related to the garbage collector?
    - Our Unity version is 4.3.0f4. I am aware of 4.3.1, but the change log does not appear to solve anything vaguely related.
    - We are targeting Windows 8.0 and using Visual Studio Express 2012 (for Windows 8) with the Windows Kit 8.0.


    - The Awake of the PrefabHolder (which sets the instance each time the scene is loaded) is being called before the Awake of the Scene1 script, but this doesn't seem to be a problem with script ordering anyway.
    - The instance is always set correctly and different from the previous value (it's always a new object).
    - If we use 'GameObject.Find("Prefab Holder").GetComponent<PrefabHolder>().Prefab' in the Instantiate, instead of 'PrefabHolder.Instance.Prefab' (hence not using the static Instance of the PrefabHolder variable AT ALL, except setting it on Awake), the problem seems to persist.
    - If we do the above but remove the setting of the static Instance, the problem disappears.
    - Other static variables types (such as an int) being set on the PrefabHolder's Awake (instead of the Instance reference) do not cause problems.
    - When we discovered the problem in our application (not this test project), we actually had several prefabs in the PrefabHolder, some of them having arrays of items set in the inspector, such as an array of textures. Every texture in the array became null unless it was already in the object's material by default, which I found strange.
    - Not only textures become null. We observed other types of references to objects becoming null, inside the prefabs set in the PrefabHolder.


    I'm guessing this has something to do with the PrefabHolder object being held in memory (referenced by the static variable) after destruction, even when in another scene; but the Awake method of the new PrefabHolder should overwrite the previous value when loading the scene. And it does not explain the on-off behavior of the problem... or why it happens in Metro apps only.

    Do any of you have any suggestion on why this happens? Is it a bug? Should I always do "Instance = null" on destroy anyway, to help the garbage collector do its job? (or perhaps avoid Singletons altogether...)

    Thanks!

    - CanisLupus
     

    Attached Files:

  2. Tomas1856

    Tomas1856

    Unity Technologies

    Joined:
    Sep 21, 2012
    Posts:
    3,919
    Try marking your object with DontDestroyOnLoad(this); that way you'll tell Unity not to unload PrefabHolder and any references it holds

    Code (csharp):
    1.  
    2. public class PrefabHolder : MonoBehaviour
    3. {
    4.     public static PrefabHolder Instance { get; private set; }
    5.  
    6.     public GameObject Prefab;
    7.    
    8.     void Awake()
    9.     {
    10.         if (Instance == null)
    11.         {
    12.             DontDestroyOnLoad(this);
    13.             Instance = this;
    14.         }      
    15.     }
    16.    
    17.     void OnDestroy()
    18.     {
    19.         //Instance = null;
    20.     }
    21. }
    22.  
     
  3. CanisLupus

    CanisLupus

    Joined:
    Jul 29, 2013
    Posts:
    427
    Thank you for your answer. I am aware of the existence and usage of that method (we use it in our project), but in this case it didn't make sense to retain our prefab holder in the hierarchy when in other scenes.

    Either way, I am inclined to believe that this is a Unity bug, as it only shows in Metro builds and has a rather weird behavior. Should I submit a bug report or is it somehow expected?
     
  4. Sudhir-Singh2k2

    Sudhir-Singh2k2

    Joined:
    Dec 16, 2012
    Posts:
    9
    +1. I bet this is a serious bug. For a similar bug on windows phone I had to hold on all audio sources for all scenes which was a significant design change. Now only for windows store public textures on cars are becoming null when I return to menu scene from a race scene and then go back to the race scene. For many games like mine holding on to prefabs between different scenes may not make sense.
    e.g. User Chose CarModel1 for first race. Then he chose Car Model2 for second race. Now if he chooses car Model1 for third race, and here the problem occurs. But for fixing this keeping all car models in memory for all races will eat RAM and kill performance.
     
  5. CogumeloSoft

    CogumeloSoft

    Joined:
    Dec 28, 2012
    Posts:
    88
    +1. This bug is pissing me off
     
  6. Sudhir-Singh2k2

    Sudhir-Singh2k2

    Joined:
    Dec 16, 2012
    Posts:
    9
    Significant windows store app update blocked by this bug :(
     
  7. m4a44

    m4a44

    Joined:
    Mar 13, 2013
    Posts:
    45
    +1 We are experiencing this with a customization system that uses 3D prefabs

    Edit: Turns out that by using
    Code (CSharp):
    1. [System.Serializable]
    in inheriting classes and not the base class it would mess up on our Windows Store App...
     
    Last edited: Jun 9, 2014
  8. TwisterK

    TwisterK

    Joined:
    Oct 26, 2010
    Posts:
    110
    Encountered the same bug in Unity 4.5.5 ... Any workaround?
     
  9. Ignas83

    Ignas83

    Unity Technologies

    Joined:
    Mar 26, 2013
    Posts:
    28
    One way to workaround this is to pass -enableWarningsIncorrectlyUnloadedAssets command line option to WSA app (won't work on WP8) and fix all the warnings you get. They'll look something like:

    "Incorrectly unloaded MonoBehaviour class '%s' in prefab '%s' during GarbageCollectUnusedAssets.\nThe Object will be reloaded from disk, to fix this properly keep a reference to the MonoBehaviour directly instead of the game object or other component."

    "Incorrectly unloaded ScriptableObject class '%s' with name '%s' during GarbageCollectUnusedAssets.\nThe Object will be reloaded from disk. To fix this properly keep a reference directly to the ScriptableObject."
     
    chechoggomez likes this.
  10. CanisLupus

    CanisLupus

    Joined:
    Jul 29, 2013
    Posts:
    427
    This issue was marked "By Design" by the Unity team, as seen here. This means that it's supposed to happen, though I don't think the given explanation does a very good job at letting us know why this happens. Anyway, Ignas83's answer should help. :)