Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

The WeakReference into a broken state when UnloadUnusedAssets.

Discussion in 'Scripting' started by watsonsong, May 24, 2020.

  1. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I am using WeakReference to tracking the UnityEngine.Object alive.
    But when I load a prefab from AssetBundle and save it in a WeakReference, and invoke Resources.UnloadUnusedAssets, or LoadScene(which invoke the UnloadUnusedAssets internally), it always report some error:
    ···
    The referenced script (Unknown) on this Behaviour is missing!
    The referenced script on this Behaviour (Game Object 'Canvas') is missing!
    A scripted object (script unknown or not yet loaded) has a different serialization layout when loading. (Read 32 bytes but expected 76 bytes)
    Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?
    ···
     

    Attached Files:

  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,519
    I'm curious what you're actually trying to do here.

    Keep in mind you can keep C# references to anything you like.

    But if it was in the scene and the scene changes, Unity is still going to Destroy() it if it was not marked as DontDestroyOnLoad().

    You've still got the C# reference but it is a dead object to Unity.
     
  3. unity_nDI54UEnEZerWg

    unity_nDI54UEnEZerWg

    Joined:
    Mar 2, 2020
    Posts:
    3
    But what I get it not a dead object. If the object is dead, I can check it with a simple 'if'. In fact I get a borken object, which mead it pass the 'if (obj != null)' check but missing all serialized field and script code.
     
  4. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    "This class doesn't support the null-conditional operator (?.) and the null-coalescing operator (??)." - this is from the Scripting API, UnityEngine.Object.

    Unity handles its own objects in a different way. To be sure an UnityEngine.Object (GameObject, MonoBehaviour, Collider, ...) is not NULL, you have to write
    if(myUnityObject != null && !myUnityObject.Equals(null))
    or the way shorter version:
    if(myUnityObject)
    .

    A bit confusing at first and even more complicated when you realize, you can't call this from a second thread, but there are always solutions. ^^

    Also, you should avoid using weakReference at all ... whatever you are doing, this will just lead to bugs. Tried it twice - not recommended - atleast not with unity.
     
  5. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I am not using the null-conditional operator(?.) in the test project, which I uploaded. And everything is happend on the main thread. I am not excepted the WeakReference will keep alive after the UnloadUnusedAssets, but if the object is destroyed, I should check it.

    If the unity not support use WeakReference at all, I think it is a bug, the unity should fix it.
     
  6. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    the null-conditional operator(?.) is the same as writing
    != null
    which is just the inverted version of
    == null
    - both don't work with UnityEngine.Object classes such as MonoBehavior or GameObject.

    The reason why it does not work, is because Unity handles its own objects from the C++ side, while all C# are managed inside C#.
    WeakReference checks on the C# side - so it does not work as expected with UnityEngine.Object derived classes.
    If you set the breakpoint properly (calling a method inside a monobehavior right after calling Destroy()), VisualStudio will tell you that " this == 'null' ", so it is in a state of "not really null" so you have to use the
    if(myUnityObject != null && !myUnityObject.Equals(null))
    check to see if it is really null (or as mentioned above, the shorter version:
    if(myUnityObject)
    ).

    This happens because of performance optimisation afaik. But you can't use anything related to common C# null checks such as WeakReference with UnityEngine.Object derived classes.

    Thats not a bug, this is just how it works - otherwise we probably would have constant stuttering and lags in the runtime because of the garbage collection.
     
  7. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I upload two project in attachment. It has no relationship with the 'null check' trick in UnityEngine.Object.
    It is a test for a bug when unity work with WeakReference and Resources.UnloadUnusedAssets.
    Because the WeakReference is broken, is not alive or dead. It report it alive but the target is already dead. It can get the c# alive part, but the c# UnityEngine.Object tell me the Native part is existed but it seems already destroyed.

    If you open the project the run the Test scene, you know what I said.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,519
    I'll be blunt: most people here are not going to bother downloading and opening some random project.

    Again I recommend that you take a step back, figure out what you're actually trying to accomplish, and then do it The Unity Way(tm). As the saying goes, "When in Rome..."

    Remember, games with clever and unusual internal memory tricks don't help the customer at all.
     
    unity_IpxdANggCs1roQ likes this.
  9. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    WeakReference is actually a normal thing and a lot of code use it. I provide the test project will let the unity guy easy to reproduce the bug. I also submit to the issue tracker.
    I paste the core test code blew, hope you can help me find the problem:
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6. #endif
    7. using UnityEngine;
    8.  
    9. public sealed class Test : MonoBehaviour
    10. {
    11.     private WeakReference wf;
    12.  
    13.     private void Awake()
    14.     {
    15.         GameObject.DontDestroyOnLoad(this.gameObject);
    16.     }
    17.  
    18.     public void OnClick()
    19.     {
    20.         var bundle = AssetBundle.LoadFromFile(
    21.             "Assets/StreamingAssets/canvas");
    22.         var prefab = bundle.LoadAsset<GameObject>("Canvas");
    23.         this.wf = new WeakReference(prefab);
    24.         Resources.UnloadUnusedAssets();
    25.         this.StartCoroutine(this.TestWeak());
    26.     }
    27.  
    28.     private IEnumerator TestWeak()
    29.     {
    30.         yield return null;
    31.         if (!this.wf.IsAlive)
    32.         {
    33.             Debug.Log("Weak reference is not alive.");
    34.             yield break;
    35.         }
    36.  
    37.         var prefab = (GameObject)this.wf.Target;
    38.         if (prefab == null)
    39.         {
    40.             Debug.Log("Prefab is destroyed.");
    41.             yield break;
    42.         }
    43.  
    44.         GameObject.Instantiate(prefab);
    45.         this.enabled = false;
    46.     }
    47.  
    48. #if UNITY_EDITOR
    49.     [MenuItem("Test/BuildAB")]
    50.     private static void BuildAB()
    51.     {
    52.         var options =
    53.             BuildAssetBundleOptions.DeterministicAssetBundle |
    54.             BuildAssetBundleOptions.ChunkBasedCompression;
    55.         BuildPipeline.BuildAssetBundles(
    56.             "Assets/StreamingAssets",
    57.             options,
    58.             EditorUserBuildSettings.activeBuildTarget);
    59.     }
    60. #endif
    61. }
    62.  
     
    david-wtf likes this.
  10. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    So you think that it counts the weakReference as Reference and does not unload the asset?
    But when you call UnloadUnusedAssets - the test results in the "not really null" state. Thats exactly the problem I explained - WeakReference will return that the object is alive, when it is "not really null", no matter what you call - thats why you can't use WeakReference for this check, UnloadUnusedAssets will not force it to be null, the magic happens on the C++ side.
    The only reason for this test as far as I can tell, is to check if the object is not null in order to call methods on this object?
    And if you want to use the object, you have to make the UnityCheck.

    If this all has nothing to do with calling the object, but only with the Memory - then you should use the Memory-Profiler for your checks and no C# code, as C# code will return invalid results.

    I thnik I don't understand what you are trying to do ...

    "I am using WeakReference to tracking the UnityEngine.Object alive." - you can't do that, it won't work, you cannot keep track of the alive status of a UnityEngine.Object with WeakReference.
    The big question is: Why? Why do you want to keep track of it that way?

    (99% of the time in programming, the solution is not solving a specific problem, but chaning the perspective and solving the bigger problem in an entirely different way.)
     
  11. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I know the WeakReference will not hold the both the c# part and native part of a UnityEngine.Object. If there is only WeakReference hold the object, Resources.UnloadUnusedAssets will destroy the Native part of the UnityEngine.Object. The c# part of the UnityEngine.Object is rely on GC.

    If there is no GC, then the WeakReference will get a UnityEngine.Object with only the c# part but it's native part is destroyed. So then I check the UnityEngine.Object inside a 'if' statement, it will invoke the '
    IsNativeObjectAlive' method. But the 'IsNativeObjectAlive' report true.

    So in the code, both the two log: "Weak reference is not alive." and "Prefab is destroyed." is not printed. The code run into the GameObject.Instantiate. But when instantiate the object, unity report the following error:
    Code (CSharp):
    1. The referenced script (Unknown) on this Behaviour is missing!
    2. The referenced script on this Behaviour (Game Object 'Canvas') is missing!
    3. A scripted object (script unknown or not yet loaded) has a different serialization layout when loading. (Read 32 bytes but expected 76 bytes)
    4. Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?
     
  12. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    The test result say if there has a WeakReference to a UnityEnging.Object without any strong reference(And this what WeakReference work for), invoke the Resources.UnloadUnusedAssets(or indirectly invoke it when loading scene), will make the WeakReference turn into an unusable state: The UnityEnging.Object is destroyed, but every existed API tell me it alive.

    So this result means that WeakReference CAN NOT used in unity.

    But trust me, why I stick to talking about this is WeakReference is really widely used in unity world. For example almost every lua bridge to unity library use WeakReference:
    https://github.com/Tencent/xLua/search?q=WeakReference&unscoped_q=WeakReference
    https://github.com/topameng/tolua/search?q=WeakReference&unscoped_q=WeakReference
    https://github.com/pangweiwei/slua/search?q=WeakReference&unscoped_q=WeakReference

    And if you search the keywords: "WeakReference" with "GameObject" or "Unity", there has a huge code use it.
    https://github.com/search?l=C#&p=7&q=WeakReference+GameObject&type=Code
     
  13. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    Afaik weak reference is used, so the object can get destroyed by the Garbage Collection, even when there is still a reference - as long as this reference is a weak reference.

    Garbage Collection frees the memory, without it, we would run out of memory real quick. When does the garbage collection free memory? There is a counter on every object, counting the references to this object - if the counter hits Zero, no objects refrence the object and it can be freed from memory.

    Unity objects don't get destroyed by the GC when there is no reference to them. They exist on their own, they can only be destroyed by calling Destroy(anyUnityObject).
    So there is absolutely no need and no use for WeakReference for UnityObjects at all - it just doesn't make any sense.

    Maybe I am missing the point here?
     
  14. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    ‘So there is absolutely no need and no use for WeakReference for UnityObjects at all’
    That is the test result, but first thing is WeakReference to a UnityEngine.Object is already widely used. May be some code use WeakReference directly to a UnityEngine.Object, or maybe indirectly hold a WeakReference to a UnityEngine.Object, for example maybe they hold a System.Action, which capture some UnityEngine.Object. As I mentioned in the GitHub urls. In this conclusion, all these code is wrong.

    The second thing is, if the Garbage Collection free the memory, the WeakReference will not alive, so the first check in my test code will not pass, it will report "Weak reference is not alive.".
    If there is no Garbage Collection, so the native part MUST BE destroyed. If I gets a C# object without the native part, UnityEngine.Object should report by it implicit bool cast, which internal invoke the IsNativeObjectAlive.

    What I say is, there is no way to check this state: The c# part is alive from a WeakReference, but the native part is destroyed.
    In correctly way, if I hold a strong reference to a GameObject, and Destroy the native part by Object.Destroy, the IsNativeObjectAlive will get false.

    You can see this method in https://github.com/Unity-Technologi...xport/Scripting/UnityEngineObject.bindings.cs.

    As unity support C#, and WeakReference is part of the C#, and there is no error check and document talk about do not use WeakReference, and the WeakReference is already widely used. I think this behavior is a BUG for unity. They not consider this situation.
     
  15. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    "widely used" I've looked over the samples, from about 50 entries, one was using WeakReference with GameObject, all others used the WeakReference for C# classes.
    If it is used with GameObject, there has to be a specific scenario why it is used because the core mechanic does not work. I can't go through the whole codebase just to find out why they used it and if it is just a backup savety method along others or if it fullfills a special case.

    "If there is no Garbage Collection, so the native part MUST BE destroyed."
    Why? It could be pooled on the C++ side, we don't know what happens there.
    "IsNativeObjectAlive" does not specify if it is just pooled or completely cleared from memory.

    "In correctly way, if I hold a strong reference to a GameObject, and Destroy the native part by Object.Destroy, the IsNativeObjectAlive will get false."
    I think the native part has to survive somehow, to clear "strong references" even when they are set on the C# side - because after some time, the C# will also tell you that the reference is NULL even when there was a strong reference first and the Object got destroyed.

    At the end, I don't think it's a bug, I think this is how things are optimised by unity - WeakReference does not work, == null does not work, some things just don't work for the sake of performance - but actually, they do work in a specific case, I just have no idea when, so it's best to avoid these checks completely and use the provided workarounds
    if(myUnityObject) // do cool stuff


    I am just a game programmer - whatever happens in their engine, I've really no idea. My suggestion: avoid things that don't work. In the end we all want to make games (or whatever software with Unity) and no one cares how it is done at the end. ^^
    I don't think that I can help you any further, if you really want to dig deeper down this rabbithole, I'm afraid you'll have a hard time finding any further information. But if you are lucky and find something, maybe share it here on the forums. ^^
     
  16. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    "If there is no Garbage Collection, so the native part MUST BE destroyed."
    Because there is an invoke: AssetDatabase.UnloadUnusedAssets.

    Code (CSharp):
    1. if(myUnityObject) // do cool stuff
    The problem is, in this situation, this check is not work. This if tell my 'myUnityObject' is alive but it not.

    "widely used" I've looked over the samples, from about 50 entries, one was using WeakReference with GameObject, all others used the WeakReference for C# classes.

    Some weak reference it not that directly, for example:
    Code (CSharp):
    1. Dictionary<int, WeakReference> delegate_bridges = new Dictionary<int, WeakReference>();
    Although this WeakReference is to 'delegate', but 'delegate' can hold anything, include a GameObject.
     
  17. pyphehe

    pyphehe

    Joined:
    Feb 17, 2018
    Posts:
    10
    oscarAbraham likes this.
  18. AsaEx

    AsaEx

    Joined:
    May 19, 2022
    Posts:
    2
    Because Resources.UnloadUnusedAssets is an asynchronous call and only resources and objects on the Cpp side should be release, you need to wait for the call to complete. Then call System.GC.Collect to release the object on the CSharp side, so that WeakReference.target becomes null. Just like the picture I uploaded.
     

    Attached Files:

    kdchabuk likes this.