Search Unity

Can I ask Addressables if a specified key (addressable name) is valid?

Discussion in 'Addressables' started by chanon81, Oct 25, 2018.

Thread Status:
Not open for further replies.
  1. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    168
    Hello,

    I'm porting a game from another game engine to Unity.

    In the previous engine, I would check the file system and ask it whether certain textures exist at a specified file path, and if they don't, then fall back to another one.

    So what I would like is something like:
    Code (CSharp):
    1. Addressables.IsKeyValid("Powers/PowerLevel_2");
    and then if it isn't valid and Powers/PowerLevel_1 is, then I'd fallback to using Powers/PowerLevel_1.

    Is something like this possible?
     
  2. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    168
    OK, from looking at the Addressables source code, I see that I can use
    Addressables.ResourceLocators
    for this.

    This is what I got:
    Code (CSharp):
    1.  
    2. public bool AddressableResourceExists(object key) {
    3.     foreach (var l in Addressables.ResourceLocators) {
    4.         IList<IResourceLocation> locs;
    5.         if (l.Locate(key, out locs))
    6.             return true;
    7.     }
    8.     return false;
    9. }
    10.  
    Then when using I need to make sure that Addressables has been initialized first. Eg.

    Code (CSharp):
    1.  
    2. Addressables.Initialize().Completed += (op) => {
    3.     string key;
    4.     key = "My/Texture.png";
    5.     Debug.Log("Resource exists for " + key + " = " + AddressableResourceExists(key));
    6. };
    7.  
     
    ChrisVMC, ingkle and QantasVR like this.
  3. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    What you have above probably could work. But the thing I would actually recommend is to just do Addressables.LoadAsset<IResourceLocation>("key");

    This will not load your asset, but will load the location that points to your asset. If your key is valid, then you'll get a valid location back. Otherwise you will not. You can keep and use that location (doing LoadAsset<GameObject>(myNewLocation)) or you can just discard it and continue to load with key once needed.

    -Bill
     
  4. Rafarel

    Rafarel

    Joined:
    Jul 21, 2017
    Posts:
    199
    @unity_bill how can I do that please ?


    I've done this but it isn't working :

    Code (CSharp):
    1.  
    2. var asyncKeyExists = Addressables.LoadAsset<IResourceLocation>(key);
    3.  
    4. if (asyncKeyExists.Status == AsyncOperationStatus.Failed)
    5. {
    6.     Debug.LogError("TurretsManager can't preload " + key + " this key does not exists!");
    7. }
    8.  
    9.  
    Thanks for your help!
     
  5. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    In the soon-to-be-released 0.7.x, you can use a dedicated interface for this: `LoadResourceLocations`. I don't particularly like the name. It should probably be GetResourceLocations, but haven't had time to change that. so fair warning, it may get renamed going into 0.8.x
     
    marcianosd and Rafarel like this.
  6. d_grass

    d_grass

    Joined:
    Mar 6, 2018
    Posts:
    45
    @unity_bill
    Hello,
    I am trying right now to check if the location that points to the asset is available.
    Unfortunatly Addressables.LoadAsset<IResourceLocation>("key"); does not work anymore in 7.5.

    Error Log Shows:
    Exception encountered in operation Resource<IResourceLocation>(2019-001-020749.prefab)

    I also tried Addressables.LoadResourceLocations(key).Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded but it is always false.

    Could it be possible that it isnt checked synchronusly and if its the case, how can i archive a check that an key is available?

    I would be grateful to you if you have an solution.
     
  7. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    31
    If you get a reference to a catalog (an IResourceLocator) then you can iterate through its .Keys and check for a match. I believe you can get to the active set of IResourceLocator(s) from Addressables.ResourceLocators.
     
  8. d_grass

    d_grass

    Joined:
    Mar 6, 2018
    Posts:
    45
    The Amount of ResourceLocators in Addressables.ResourceLocators is 0. But i can load Adressables how is this possible?
     
    Last edited: May 10, 2019
  9. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    31
    It sounds like the Addressables system might not be done initializing when you are asking for the length.
    If I ask for the Count after initialization has finished I do get 1.
     
  10. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    31
    The loading routines for addressables are all setup to wait for initialization to complete.
    E.g.
    public AsyncOperationHandle<TObject> LoadAsset<TObject>(object key)
    {
    if (!InitializationOperation.IsDone)
    return LoadAssetWithChain<TObject>(key);

    That's why your loads work... they are simply waiting patiently for the initialize callback.
    You can manually initialize addressables with something like Addressables.Initialize().Completed += yourCallback;
     
  11. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    This is an async operation. So you need to do Addressables.LoadResourceLocations(key).Completed += whatever. Then check status. If that doesn't work, please let me know.
     
  12. d_grass

    d_grass

    Joined:
    Mar 6, 2018
    Posts:
    45
    @unity_bill & @senfield Thank you for your support, that helped me a lot and cleared things up. In particular initialize missed for me. Sorry that I bothered you with that.
     
    unity_bill likes this.
  13. marcianosd

    marcianosd

    Joined:
    Nov 6, 2018
    Posts:
    4
    Hello @unity_bill.

    When Addressables.LoadResourceLocationsAsync(address) is called and the address is not found, is it supposed to throw an exception? Or is it supposed to complete with AsyncOperationStatus.Failed?

    Currently, it's throwing an exception. I remember that it was the other case back when it was still Addressables.LoadAsset<IResourceLocation>(address). Just wondering if that is the correct behavior moving forward.

    Thanks!
     
  14. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    We're planning to remove that exception to make LoadResourceLocationsAsync be a safe & easy way to validate an address or label.
     
  15. marcianosd

    marcianosd

    Joined:
    Nov 6, 2018
    Posts:
    4
    Hi @unity_bill ,

    Thank you for your response.

    I've just updated to 1.1.4, and according to the changelogs for 1.1.3, there's this line:
    • Made LoadResourceLocations no longer throw an exception if given unknown keys. This method is the best way to check if an address exists.
    But upon testing (as of 1.1.4), LoadResourceLocationsAsync still throws an exception when:
    • passing a single unknown key
    • passing a list where all keys are unknown (lists work if there is at least 1 known key)
    Are there prior steps that need to be done for this to work?

    Thanks!
     
    NickZhao, DanielGamaga and chanon81 like this.
  16. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    Is it the InvalidKeyException? Looking through the code I'm not sure how this could happen, and this is specifically what we fixed. Perhaps you could debug or add some logging to the code to see where this get's thrown? There are only a few spots where we throw it.

    If it's a different exception, then knowing which one would help.
     
    marcianosd likes this.
  17. marcianosd

    marcianosd

    Joined:
    Nov 6, 2018
    Posts:
    4
    my bad. they were Error Logs and i got Error Pause on.
     
    Last edited: Jul 18, 2019
    unity_bill likes this.
  18. funbites

    funbites

    Joined:
    Oct 9, 2015
    Posts:
    17
    I'm using Unity 2019.2.3f1 and Addressables 1.2.2 and I'm getting InvalidKeyException.

    Code (CSharp):
    1.  
    2. Addressables.LoadAssetAsync<GameObject>(lastLoadedSceneName).Completed += OnCompleteLoading;
    3.  
    Exception encountered in operation UnityEngine.ResourceManagement.ResourceManager+CompletedOperation`1[UnityEngine.GameObject], result='', status='Failed': Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=ALICE_01_A_MAOS_ALGEMADAS
    UnityEngine.AddressableAssets.Addressables:LoadAssetAsync(Object)
    Funbites.GudeInk.Core.LoadSceneFunction`1:OnCall(LineTag[]) (at Assets/_GUDE_CORE/Scripts/Story/ContextFunctions/LoadSceneFunction.cs:38)
     
  19. peeka

    peeka

    Joined:
    Dec 3, 2014
    Posts:
    113
    I don't want exception to Log into player.log when pack doesn't exist, I am using LoadResourceLocationsAsync, but it logs the file name and everything to player.log.

    in my addressableassetsettings I have Log Runtime Exceptions unchecked, but it still log the exception..

    I am on aa 1.2.4
     
    joaobsneto and funbites like this.
  20. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    Same here. No updates so far?
     
  21. unity_bill

    unity_bill

    Joined:
    Apr 11, 2017
    Posts:
    1,053
    to be clear, the original post was about LoadResourceLocationsAsync, which will not throw an exception if the key is unknown.

    LoadAssetAsync (which is what your code is calling) will and should throw an InvalidKey exception if it does not recognize the key. So whatever you are passing in as "lastLoadedSceneName" is not actually an addressable thing.
     
  22. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    I understand, but a system that is based on string values, a typo or a missing address may happen in production. So, should I always ask for Location and then ask for the asset itself?
     
  23. MSFX

    MSFX

    Joined:
    Sep 3, 2009
    Posts:
    116
    @unity_bill Also getting this exception when using LoadAssetAsync and wondering what the correct pattern is, I assumed I could just request a load and the error could be dealt and handled with awaitable.Status..? Using 1.3.8 with Unity 2019.1
     
    Last edited: Nov 27, 2019
    Jaimi likes this.
  24. Gwom

    Gwom

    Joined:
    Aug 5, 2015
    Posts:
    21
    For anyone else that stumbled here, looking for a way to suppress the errors when things are missing, I have a manager singleton that I use for all things addressables, that Initializes the system in Start() - then for Images / Game Objects, I use the following:

    Code (CSharp):
    1.     // Used for setting sprites from the addressable system
    2.     public static void LoadImage(string reference, Image img)
    3.     {
    4.         // Gives us a list of locations, this tells us if the reference is valid
    5.         Addressables.LoadResourceLocationsAsync(reference).Completed += (loc) =>
    6.         {
    7.             // If it is valid, there will be a result
    8.             if(loc.Result.Count > 0)
    9.                 Addressables.LoadAssetAsync<Sprite>(reference).Completed += (obj) => SetImage(obj, reference, img);
    10.             else
    11.                 Debug.LogWarningFormat("Reference {0} not found in Addressable System", reference);
    12.         };
    13.     }
    14.  
    15.     // Here we set an image if it did succeed
    16.     public static void SetImage(AsyncOperationHandle<Sprite> obj, string reference, Image img)
    17.     {
    18.         if(obj.Status == AsyncOperationStatus.Succeeded)
    19.         {
    20.             var sprite = obj.Result;
    21.  
    22.             // Could be the image has gone by this point
    23.             if(sprite != null && img != null)
    24.                 img.sprite = sprite;
    25.         }
    26.         else if(obj.Status == AsyncOperationStatus.Failed)
    27.             Debug.LogFormat("Cant get sprite from Addressable {0}", reference);
    28.     }
    29.  
    30.     // Used for loading a game object from the addressable system
    31.     public static void LoadGO(string reference, Action<GameObject> callback, Transform handle)
    32.     {
    33.         // Gives us a list of locations, this tells us if the reference is valid
    34.         Addressables.LoadResourceLocationsAsync(reference).Completed += (loc) =>
    35.         {
    36.             // If it is valid, there will be a result
    37.             if(loc.Result.Count > 0)
    38.             {
    39.                 if(callback != null)
    40.                     Addressables.InstantiateAsync(reference, handle).Completed += (obj) => SetGO(obj, reference, callback);
    41.                 else
    42.                     Addressables.InstantiateAsync(reference, handle);
    43.             }
    44.             else
    45.                 Debug.LogWarningFormat("Reference {0} not found in Addressable System", reference);
    46.         };
    47.     }
    48.  
    49.     public static void SetGO(AsyncOperationHandle<GameObject> obj, string reference, Action<GameObject> callback)
    50.     {
    51.         if(obj.Status == AsyncOperationStatus.Succeeded)
    52.         {
    53.             var gObj = obj.Result;
    54.             //var sprite = obj.Result.GetComponent<Sprite>();
    55.  
    56.             if(gObj != null)
    57.                 callback.Invoke(gObj);
    58.         }
    59.         else if(obj.Status == AsyncOperationStatus.Failed)
    60.         {
    61.             Debug.LogFormat("Cant get gameObject from Addressable {0}", reference);
    62.         }
    63.     }
    64.  
     
  25. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    This is a snippet of the code I use in my AssetManager. When Addressables is initialized, it provides a resourcelocator that contains all the valid keys which I use to build a hashset for quick lookup. Then I expose an IsValidAddress function and let the normal load throw like normal if the caller fails to check their key.


    Code (CSharp):
    1. // Addressables uses this to set the remote asset path at runtime when AssetManager is initialized.
    2. public static string AssetPath { get; private set; }
    3.  
    4. private static HashSet<object> _keys;
    5.  
    6. /// <summary>
    7. /// Initialize the AssetManager to download assets from the specified <paramref name="assetPath"/>.
    8. /// </summary>
    9. public static Promise Initialize(string assetPath)
    10. {
    11.     if (string.IsNullOrWhiteSpace(assetPath))
    12.     {
    13.         throw new System.ArgumentException("assetPath cannot be null or whitespace.", nameof(assetPath));
    14.     }
    15.     if (AssetPath != null)
    16.     {
    17.         throw new InitializationException("AssetManager is already initialized.");
    18.     }
    19.  
    20.     AssetPath = assetPath;
    21.  
    22.     return Addressables.InitializeAsync().ToPromise() // ToPromise is an extension I wrote
    23.         .Then(resourceLocator =>
    24.         {
    25.             // Cache the keys so we can check if any key exists.
    26.             _keys = new HashSet<object>(resourceLocator.Keys);
    27.         });
    28. }
    29.  
    30. /// <summary>
    31. /// Is the address valid to load an asset?
    32. /// </summary>
    33. public static bool IsValidAddress(string address)
    34. {
    35.     ValidateInitialized();
    36.  
    37.     return _keys.Contains(address);
    38. }
    39.  
    40. private static void ValidateInitialized()
    41. {
    42.     if (AssetPath == null)
    43.     {
    44.         throw new InitializationException("AssetManager must be initialized before accessing any other functions.");
    45.     }
    46. }
     
  26. cathalmcd

    cathalmcd

    Joined:
    Jul 15, 2019
    Posts:
    5
    Can you please tell us how to load all the label names from a group, so that we can create UI elements dynamically
     
  27. neoangelique

    neoangelique

    Joined:
    Nov 19, 2014
    Posts:
    30
    Is there a way to use async and await to fulfill the checking instead of using a callback? I've been trying this approach but addressables always returns me IsValid = true and Status = completed even if the asset and address does not exist.... this is incredibly frustrating as this is such a basic requirement yet isn't as straightforward.....

    Code (CSharp):
    1. await Addressables.LoadResourceLocationsAsync(address).Task;
    2. if (Addressables.LoadResourceLocationsAsync(address).Status == Completed)
     
  28. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    Try this:

    Code (CSharp):
    1. var op = Addressables.LoadResourceLocationsAsync(address);
    2. await op.Task;
    3. if (op.Status == Completed)
     
    neoangelique likes this.
  29. cathalmcd

    cathalmcd

    Joined:
    Jul 15, 2019
    Posts:
    5
    Can anyone tell me how to load all the label names from a group, so that we can create UI elements dynamically
     
  30. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    Groups don't exist at runtime. You can build a lookup dictionary for that purpose similar to what I did here (just use the group's name instead of asset entry's address).
     
  31. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,622
    Hey Bill,

    passing the
    assetGUID
    or
    assetName
    as key, causes an
    InvalidKeyException
    always. The key seems to be valid though, because if I use the same key to load the scene, it actually does work.

    Code (CSharp):
    1. var key = "a94a6ab75b682934e90dd036edebebc4";
    2.  
    3. // Causes InvalidKeyException
    4. var op1 = Addressables.LoadAssetAsync<IResourceLocation>(key);
    5. yield return op1;
    6. Debug.Log($"op1.Status: {op1.Status}");
    7.  
    8. // Does load the scene properly
    9. var op2 = Addressables.LoadSceneAsync(key, LoadSceneMode.Single);
    10. yield return op2;
    11. Debug.Log($"op2.Status: {op2.Status}");
    Running this code outputs the following messages to the Console window:
    Code (CSharp):
    1. Exception encountered in operation CompletedOperation, status=Failed, result= : Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=a94a6ab75b682934e90dd036edebebc4, Type=UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation
    2.  
    3. op1.Status: Failed
    4.  
    5. op2.Status: Succeeded
    I really need to find a way how to check whether the requested scene exists, to fall back to another scene, in case it's missing. Any pointers how to get there would be appreciated.

    I'm using Unity 2019.4.9f1 and Addressables 1.16.10.
     
  32. LuGus-Jan

    LuGus-Jan

    Joined:
    Oct 3, 2016
    Posts:
    179
    @Peter77 Why not use the
    Addressables.LoadResourceLocationsAsync
    method? Since the original suggestion is from quite a while ago (I guess this was added because of it), some additional optimizations may have been done already that may or may not break the line you're trying to use, while I use the
    LoadResouceLocationsAsync
    quite frequently without issue.
     
  33. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,622
    Thanks for the tip! Unfortunately, using
    LoadResourceLocationsAsync
    causes a different issue.
    Code (CSharp):
    1. var key = "a94a6ab75b682934e90dd036edebebc4";
    2. var op1 = Addressables.LoadResourceLocationsAsync(key);
    3. yield return op1;
    The above code causes Unity to output the following error:
    Code (CSharp):
    1. Scene assets can not be used with GetPlayerObjectIdentifiersInAsset API.
    2. Please use ProcessScene API instead.
    I've searched for a "ProcessScene API", but no clue what that is.
     
  34. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    That sounds like a bug. You should submit a bug report if you haven't already.
     
  35. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,622
    Thanks for the reminder. I submitted the following bug-report now:
    (Case 1292806) Addressables: LoadResourceLocationsAsync doesn't work with scenes

    Addressables: LoadResourceLocationsAsync doesn't work with scenes

    Calling Addressables.LoadResourceLocationsAsync(key) seems to be the way to check if an asset exists. I came to this conclusion after reading the following forum threads:

    However, calling LoadResourceLocationsAsync with a key that points to a scene, causes Unity to output the following error and the result is incorrect:
    Code (csharp):
    1. Scene assets can not be used with GetPlayerObjectIdentifiersInAsset API. Please use ProcessScene API instead.
    2. UnityEngine.AddressableAssets.Addressables:LoadResourceLocationsAsync(Object, Type)
    3. <Start>d__3:MoveNext() (at Assets/Scripts/NewBehaviourScript.cs:25)
    4. UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
    Reproduce
    • Open attached project
    • Open SampleScene
    • Press Play

    Actual
    Unity outputs an error and returns 0 results for the scene key.

    Expected
    Unity outputs no error and returns 1 result for the scene key.

    If QA is able to reproduce the issue, it should become available at:
    https://issuetracker.unity3d.com/product/unity/issues/guid/1292806
     

    Attached Files:

  36. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,622
    As mentioned earlier, this doesn't work (anymore) with scenes (see here). So I submitted the following bug-report for you to look at:
    (Case 1292809) Addressables: LoadAssetAsync<IResourceLocation> doesn't work with scenes

    Addressables: LoadAssetAsync<IResourceLocation> doesn't work with scenes

    Calling
    Addressables.LoadAssetAsync<IResourceLocation>(key)
    seems to be another way to check if an asset exists. I came to this conclusion after reading the following forum thread:

    However, calling
    LoadAssetAsync<IResourceLocation>
    with a key that points to a scene, causes Unity to output the following
    InvalidKeyException
    :
    Code (csharp):
    1. Exception encountered in operation CompletedOperation, status=Failed, result= : Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=6aa8426b03343f940ba16fcce5d8b933, Type=UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation
    2. UnityEngine.AddressableAssets.Addressables:LoadAssetAsync(Object)
    3. <Start>d__1:MoveNext() (at Assets/Scripts/NewBehaviourScript.cs:19)
    4. UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
    However, the same key can be used to successfully load the scene, so the key can't be invalid!

    Reproduce
    • Open attached project
    • Open SampleScene
    • Press Play
    Actual
    Unity outputs an InvalidKeyException and the LoadAssetAsync<IResourceLocation>(key) operation fails.

    Expected
    Unity outputs no error and the LoadAssetAsync<IResourceLocation>(key) operation succeeds.

    If QA is able to reproduce the issue, it should become available at:
    https://issuetracker.unity3d.com/product/unity/issues/guid/1292809
     

    Attached Files:

  37. sniffle63

    sniffle63

    Joined:
    Aug 31, 2013
    Posts:
    365
    Been trying this for about an hour now and no matter what I do it always returns succeeded for the status, even if i put some random key in that I KNOW doesn't exist.

    Code (CSharp):
    1. protected IEnumerator GetImage(string src, Image img)
    2.         {
    3.             var url = $"12312312312";
    4.             var location = Addressables.LoadResourceLocationsAsync(url);
    5.             yield return location.Task;
    6.            
    7.             Debug.LogWarning($"{url}, status: {location.Status}");
    8.            
    9.             if(location.Status == AsyncOperationStatus.Failed)
    10.             {
    11.                 url = $"Assets/Shared/assets/628x354/map/Temp-Image.jpg";
    12.             }
    13.            
    14.             var handle = Addressables.LoadAssetAsync<Sprite>(url);
    15.             _imageHandles.Add(handle);
    16.             yield return handle.Task;
    17.            
    18.             if (handle.IsValid())
    19.             {
    20.                 img.sprite = handle.Result;
    21.             }
    22.         }
     
  38. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    586
    That's because you're yielding the Task when you should just be yielding the handle.
    yield return location;
    . Otherwise, if you're trying to use the Task for async function instead of Coroutine, change
    protected IEnumerator GetImage
    to
    protected async Task GetImage
    and change
    yield return
    to
    await
    .
     
  39. sniffle63

    sniffle63

    Joined:
    Aug 31, 2013
    Posts:
    365

    Yielding the task was just the last attempt i did before posting, i was originally just yielding the handle and had the same results
     
Thread Status:
Not open for further replies.