Search Unity

How to join several AsyncOperationHandle? (wait for several assets to load?)

Discussion in 'Addressables' started by oxysofts, Nov 15, 2019.

  1. oxysofts

    oxysofts

    Joined:
    Dec 17, 2015
    Posts:
    124
    This seems like a critical feature for a loading screen where hundreds of assets could be loaded for a map. Yet, nothing in the docs about this. Do we need to manually handle it ourselves, e.g. with a counter or hashset of handles?
     
  2. Jribs

    Jribs

    Joined:
    Jun 10, 2014
    Posts:
    154
  3. FlorentFal

    FlorentFal

    Joined:
    Nov 20, 2015
    Posts:
    55
    Note that you can also use Task.When all if MergeMode is not applicable to your usecase (ie: waiting for several AssetReference).

    Code (CSharp):
    1.  
    2. var loadFirstAsset = _firstAssetReference.LoadAsset<SomeAssetA>();
    3. var loadSecondAsset = _secondAssetReference.LoadAssetAsync<SomeAssetB>();
    4.  
    5. var assets = await Task.WhenAll(loadFirstAsset.Task, loadSecondAsset.Task);
    6. _myAssetA = assets[0];
    7. _myAssetB = assets[1];
    8.  
     
  4. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    I still have the same question.

    It's possible to use await, but that's a different programming paradigm, so it may not be desired in a project (like the current one I'm working on). Then it would be possible to restructure all of the code to use async callback functions, but that can get pretty ugly.

    So, instead, I would like to combine multiple loading operations into a single AsyncOperationHandle, or sometimes I would like to change the type that is loaded. For example, I load a list of sprites because I need to filter for address + label (multiple keys), but if everything is set up correctly, I only ever get a single sprite back, so I want to return AsyncOperationHandle<Sprite> to client code.

    Example code:

    Code (CSharp):
    1. public AsyncOperationHandle<Sprite> LoadMySprite()
    2. {
    3.     var keys = new string[] { "MySpriteName", "MyLabelName"};
    4.     AsyncOperationHandle<IList<Sprite>> handle = Addressables.LoadAssetsAsync<Sprite>(keys, callback: null);
    5.     handle.Completed += h =>
    6.     {
    7.         if(handle.Result.Count == 1)
    8.         {
    9.             Sprite mySprite = handle.Result[0];
    10.             // TODO: return mySprite via the handle.
    11.         }
    12.     };
    13.  
    14.     return ??;
    15. }
    In this case, it would be nice to wrap the inner loading code into a new AsyncOperationHandle that can be yielded for from calling code.

    Shortly after adding my question, I came up with a solution that seems to work:

    Code (CSharp):
    1. public class MergeOperation<T> : AsyncOperationBase<T>
    2. {
    3.     private readonly string[] keys = new string[2];
    4.     private AsyncOperationHandle<IList<T>> handle;
    5.  
    6.     public MergeOperation(string address, string label)
    7.     {
    8.         keys[0] = address;
    9.         keys[1] = label;
    10.     }
    11.  
    12.     protected override void Execute()
    13.     {
    14.         handle = Addressables.LoadAssetsAsync<T>(keys, null, Addressables.MergeMode.Intersection);
    15.         handle.Completed += handle =>
    16.         {
    17.             var result = handle.Result;
    18.  
    19.             if (result.Count > 0)
    20.                 base.Complete(handle.Result[0], true, string.Empty);
    21.  
    22.             else if (result.Count == 0 || result.Count > 1)
    23.                 Debug.LogError("Inconclusive assets loaded for address: " + keys[0]);
    24.         };
    25.     }
    26.  
    27.     protected override void Destroy()
    28.     {
    29.         base.Destroy();
    30.  
    31.         if (handle.IsValid())
    32.             Addressables.Release(handle);
    33.     }
    34. }
    For lack of a better name and no polish yet, this custom operation searches for multiple assets that match an address and a label and returns only the first found.
     
    Last edited: May 8, 2020
  5. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    Hey @Xarbrough, if you don't like the await pattern, check out my Promise library. It makes it incredibly easy to do stuff like that without having to create whole classes for each custom operation:


    Code (CSharp):
    1. public static Promise<T> LoadAssetAsync<T>(string address, string label)
    2. {
    3.     return LoadAssetsAsync<T>(address, label)
    4.         .Then(assets => assets[0]); // Just grab the first asset. If there are no assets, the promise will already be rejected.
    5. }
    6.  
    7. public static Promise<IList<T>> LoadAssetsAsync<T>(string address, string label)
    8. {
    9.     var handle = Addressables.LoadAssetsAsync<T>(new string[] { address, label }, null, Addressables.MergeMode.Intersection)
    10.     return HandleToPromise(handle);
    11. }
    12.  
    13. // Wrap the asyncOperationHandle in a Promise.
    14. private static Promise<T> HandleToPromise<T>(AsyncOperationHandle<T> asyncOperationHandle)
    15. {
    16.     var deferred = Promise.NewDeferred<T>();
    17.     asyncOperationHandle.Completed += handle =>
    18.     {
    19.         if (handle.Status == AsyncOperationStatus.Succeeded)
    20.         {
    21.             deferred.Resolve(handle.Result);
    22.         }
    23.         else
    24.         {
    25.             deferred.Reject(handle.OperationException);
    26.         }
    27.     };
    28.     return deferred.Promise;
    29. }
    30.  
    I actually do this in my own project.
     
    Last edited: May 8, 2020
    Xarbrough and IggyZuk like this.