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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more..
    Dismiss Notice
  3. Dismiss Notice

AsyncOperationHandle.completed not firing until a frame late

Discussion in 'Addressables' started by therobby3, Apr 22, 2021.

  1. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    I'm not sure if this would be considered a bug, or if it is an unfortunate side effect of asynchronous code, but it definitely seems less than ideal.

    So the issue is that when instantiating an object via InstantiateAsync(), the Completed callback is often fired a frame late. Not always, but probably 50% of the time or so. Take the following for example:

    Code (CSharp):
    1. AsyncOperationHandle<GameObject> handle = myAsset.InstantiateAsync();
    2. handle.Completed += delegate {
    3.      print("Async completed, time to set up initial values!" + Time.frameCount);
    4.      handle.Result.transform.position = ....setting position here.
    5.      handle.Result.GetComponent<StatValues>().SetMyHealthAndStuff();
    6. };
    7.  
    8.  
    9. public class StatValues: MonoBehavior {
    10.      private void Start(){
    11.           print("Start called!  " + Time.frameCount);
    12.      }
    13.      private SetMyHealthAndStuff(){
    14.            //Set initial values, that ideally should be set at or before Start() is called.
    15.      }
    16. }
    Can often display the following:
    Start called! 10
    Async completed, time to set up initial values! 11


    With the above example, the handle.Completed callback can very often get called AFTER Start. Not only that, but it will get called a full frame after Start. That means if you set position inside the Completed callback, or any other important required values that need passed to the object, they will not actually get set until a frame late. For position, that means that your object will appear at coordinate 0, 0, 0 (or whatever it's default is) for a single frame before blinking to the correct position, or also have the incorrect values for a single frame. That also kinda' makes the Start() method a little bit useless for the object in some cases. I realize this could all be worked around, but it'd seem a bit sloppy for me to do so and I'll have to put a bunch of ugly checks in place to make sure to the Completed callback was called before accessing certain values for the object. Ideally, I believe the Completed callback should always get called before Start().

    I've also had a similar issue with this before here: https://forum.unity.com/threads/loadsceneasync-callback-not-firing-until-a-frame-late-bug.998722/
     
  2. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Ok I did some more tests, and when you pass an object's position and rotation into the InstantiateAsync() method, it always gets set immediately, like it should. So the position problem I mentioned can be solved by passing it into the InstantiateAsync() method instead of setting it inside the Completed callback, but obviously setting anything else then the issue still applies. I feel like this is a pretty common thing that would be an issue for alot of people.

    Again, take this scenario for example. A player shoots a gun and spawns a bullet using bulletAsset.InstantiateAsync(). That bullet needs a reference to the player that shot it, so you set the reference to the player inside the Completed callback. That means that very often, that bullet will NOT have a reference to the player that spawned it for 1 frame. Any collision detection code or such that may fire within that 1 frame, will then error because the reference to the player is missing for that frame. Of course you could add checks to make sure it's been assigned, but that's a little sloppy and ideally unneeded.

    Since the position and rotation of the object are set dang near immediately as it should be (from my tests like I stated) I would think this should be able to be solved somehow internally. I hope that information helped and made sense! Would love to see some kind of fix if possible.
     
  3. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Has noone else has this issue? Surely it seems like something that would effect almost anyone using it at one point or another.
     
  4. LilMako17

    LilMako17

    Joined:
    Apr 10, 2019
    Posts:
    43
    I've avoided uses InstantiateAsync() at all in my projects because of the issue you described above. By the nature of the function being async, as a developer, i have to assume the callback will be completed at some unknown, uncontrollable time in the future. might be next frame, might be next week. If it matters to me that I have consistency of when the callback will be called, don't use async. Instead I would preload my bullet prefabs before combat starts using LoadAssetAsync() and hold onto a reference of them in a manager class, and then when i want to spawn a bullet, i use regular old GameObject.Instantiate() and pass in the prefab reference I have stored in the manager. then I guarantee that my bullet spawned immediately, and I can do any necessary setup code in-line. (After combat ends, or I know I no longer need bullets, then I go back to the manager class and release the bullet prefab to clear up memory).
     
  5. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Thanks for the response. Using LoadAssetAsync and then instantiating the traditional way like you mentioned does seem it would solve the problem. I suppose I could use that way, but I'd have to change a whole lot of how I do things in places. It'd be nice if they could change the firing order like I mentioned or something, so as to not render the InstantiateAsync unreliable in these cases.
     
  6. renegadegd

    renegadegd

    Joined:
    Mar 23, 2014
    Posts:
    10
    Looks like I posted the same sort of question after yours - https://forum.unity.com/threads/awa...s-a-frame-after-the-prefab-is-created.1099027

    But I'm still none the wiser.

    For now I am calling...
    Code (CSharp):
    1. await Addressables.LoadAssetsAsync<GameObject>(assetName, null).Task;
    ...before using the addressable instantiation which I know is not the right way to handle this as addressables is probably keeping an extra count for that load behind the scenes but it's allowing me to at least get on with development and I will come back and fix it once I get more information. At the moment I'm also thinking LoadAssetsAsync and the traditional instantiation might be the best way unless anything else comes to light.
     
  7. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Ah, looks like you are indeed having the exact same problem as me then. Ideally, I'm hoping some Unity staff in charge of the Addressables can see this topic and hopefully a change could be made internally to make it fire in the correct order. If not, the InstantiateAsync method seems to be fairly useless in quite a bit of cases. =/
     
  8. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,796
    Howdy all! I'll flag this with the team so they can have a look. Which version of Addressables are you using?
     
  9. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Great, thanks! This is with version 1.17.17. But I'm imagining it exists in nearly all versions.
     
    TreyK-47 likes this.
  10. renegadegd

    renegadegd

    Joined:
    Mar 23, 2014
    Posts:
    10
    I'm seeing this on 1.16.17 and 1.17.6-preview.

    Would be great to get some feedback on this to know if it's something I'm fundamentally misunderstanding with the system and need to work around or if it's a case of waiting for a future fix.
     
  11. renegadegd

    renegadegd

    Joined:
    Mar 23, 2014
    Posts:
    10
    I have just tried updating addressables to 1.18.4 and it is showing promise. I'm now seeing...

    Frame 1: call to InstantiateAsync made
    Frame 4: call to InstantiateAsync finished awaiting and i can manually call a set up method
    Frame 5: update call on the game object being created called

    However i am also seeing a new error
    Code (CSharp):
    1. Assertion failed on expression: 'ShouldRunBehaviour()
    Could be a completely unrelated error but seems relatable to addressables. So therobby3, you might want to give this a go and see if you get something similar
     
  12. davidla_unity

    davidla_unity

    Unity Technologies

    Joined:
    Nov 17, 2016
    Posts:
    736
    So, I circulated this with some other team members because this sounded very familiar to me. We've looked into this before a bit and even tried to implement a more general solution for all async opreations but due to our APIs that have an auto release handle parameter it caused complications.

    If I'm correct, what's happening is that somehow your bundle is already loaded or is cached and quite small, something is causing your instantiate to be completed synchronously. Then you're assigning to the completed, which gets added to a list of deferred callbacks internally. These deferred callbacks get flushed once per frame so it's so happens the assignment is coming after the deferred callbacks are flushed for a given frame which is why you have to wait until the next frame.

    If you wanted you could do something like:
    Code (CSharp):
    1. var opHandle = InstantiateAsync(...);
    2. if(opHandle.isDone)
    3.     CompletedCallback(opHandle);
    4. else
    5.     opHandle.Completed += CompletedCallback;
     
    dforstmaier likes this.
  13. therobby3

    therobby3

    Joined:
    Jan 30, 2019
    Posts:
    130
    Ok thanks for the reply! I understand what you mean, that definitely makes sense then. I just did a test, and what you said seems to be the case. The first time I call InstantiateAsync on a small object, isDone is false after InstantiateAsync. However, after the second, third, etc calls to InstaniateAsync on the object, isDone is immediately true. So it's safe to say that is what's happening. I'll have to remember to do a check for isDone like you mentioned from now on, and take that approach. Thanks again.
     
  14. su9257

    su9257

    Joined:
    Jun 13, 2017
    Posts:
    27

    I have the same problem with Unity version 2019.4.26F1 Addressables version 1.8.4


    `var handle = Addressables.LoadAssetAsync<GameObject>("TestPrefab");`


    will occur:


    Assertion failed on expression: 'ShouldRunBehaviour()'
     
  15. DoubleLoopAndy

    DoubleLoopAndy

    Joined:
    May 1, 2020
    Posts:
    3
    FWIW, I ran into the same ShouldRunBehaviour assertion failure with Addressables 1.8.4, but do not get it with 1.17.15.
     
  16. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,796
    Version 1.8.4 is a pretty old version of the package.
     
  17. DoubleLoopAndy

    DoubleLoopAndy

    Joined:
    May 1, 2020
    Posts:
    3
    Sorry, typo, I meant 1.18.4
     
  18. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,796