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. Dismiss Notice

Question InvalidKeyException throws when download after updating catalogs

Discussion in 'Addressables' started by TCYeh, May 25, 2023.

  1. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    51
    Hi,

    I'm trying to update catalogs and download assets on the Android build.
    The APIs I'm using are CheckForCatalogUpdates -> UpdateCatalogs -> GetDownloadSizeAsync -> DownloadDependenciesAsync.
    When running DownloadDependenciesAsync, it always throws the error below:
    UnityEngine.AddressableAssets.InvalidKeyException: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. No MergeMode is set to merge the multiple keys requested. Keys=Assets/Scenes/SampleTest.unity, 9fc0d4010bbf28b4594072e72b8655ab, default, Assets/Sprites/landscape.jpg, 3f7eb7fa7c7f89e4181ca71134d20508, preload, Fonts & Materials/LiberationSans SDF, 8f586378b4e144a9851e7b34d9b748ee, Fonts & Materials/LiberationSans SDF - Drop Shadow, e73a58f6e2794ae7b1b7e50b7fb811b0, Fonts & Materials/LiberationSans SDF - Fallback, 2e498d1c8094910479dc3e1b768306a4, Fonts & Materials/LiberationSans SDF - Outline, 79459efec17a4d00a321bdcc27bbc385, 478f9d872356d6d95aa6e6d15a8d82a8_unitybuiltinshaders_7e69a9eec65e68d343cefc2c712767ad.bundle, defaultlocalgroup_scenes_all_befe9d799cf7c9e61e212850bacb5c36.bundle, sprites_assets_all_5590ba05d762df7e6ed3c837f37c2617.bundle, LineBreaking Following Characters, fade42e8bc714b018fac513c043d323b, LineBreaking Leading Characters, d82c1b31c7e74239bff1220585707d

    The progress only calls when I press an update button, and the button will be locked to ensure users won't push it twice while progressing.

    I've read the document about MergeMode, but I'm still unsure about the issue.

    Can anyone help me identify what I might be missing or doing wrong?

    Additionally, I have included the code I'm using to load the assets:
    Code (CSharp):
    1.  async Task UpdateCatalogs()
    2.     {
    3.         Debug.Log("Check updates begin.");
    4.  
    5.         var checkUpdatHandle = Addressables.CheckForCatalogUpdates();
    6.    
    7.         var catalogs = await checkUpdatHandle.Task;
    8.         Debug.Log($"Catalogs: {catalogs.Count}");
    9.  
    10.         if (catalogs != null && catalogs.Count > 0)
    11.         {
    12.             Debug.Log("Update catalogs begin.");
    13.  
    14.             var locators = await Addressables.UpdateCatalogs(catalogs).Task;
    15.            
    16.             Debug.Log("Update catalogs ended.");
    17.  
    18.             if (locators.Count <= 0)
    19.             {
    20.                 Debug.Log("No avaliable updates.");
    21.                 return;
    22.             }
    23.  
    24.             long downloadSize = 0;
    25.             foreach (var locator in locators)
    26.             {
    27.                 foreach (object key in locator.Keys)
    28.                 {
    29.                     downloadSize += await Addressables.GetDownloadSizeAsync(key).Task;
    30.                 }
    31.  
    32.                 Debug.Log($"{locator.LocatorId} download size: {downloadSize}");
    33.  
    34.                 if (downloadSize > 0)
    35.                 {
    36.  
    37.                     var downloadHandle = Addressables.DownloadDependenciesAsync(locator.Keys);
    38.  
    39.                     while (downloadHandle.Status == AsyncOperationStatus.None)
    40.                     {
    41.                         float percentageComplete = downloadHandle.GetDownloadStatus().Percent;
    42.                         Debug.Log($"{locator.LocatorId} downloading: {percentageComplete}");
    43.                         await Task.Delay(50);
    44.                     }
    45.                 }
    46.             }
    47.         }
    48.  
    49.         Debug.Log("Update ended.");
    50.     }
     
  2. andymilsom

    andymilsom

    Unity Technologies

    Joined:
    Mar 2, 2016
    Posts:
    294
    This is a single object method. As such it will be looking for the locations that have the key of the object passed in, which of course will be none. You will need to pass a merge mode to declare it as a collection of keys in this method.

    Note: Your code
    Mahy be getting multiple entries for the same download, e.g. for every key for an asset in the bundle, and every bundle key it would be accumulated.

    Better yet, you could collect all the bundle locations like so. Which also includes getting the download size directly:

    Code (CSharp):
    1.  
    2. List<IResourceLocation> bundleLocationsNotCached = new List<IResourceLocation>();
    3. long totalDownloadToDo = 0;
    4. foreach (IResourceLocator locator in Addressables.ResourceLocators)
    5. {
    6. var map = locator as ResourceLocationMap;
    7. foreach (KeyValuePair<object,IList<IResourceLocation>> mapLocation in map.Locations)
    8. {
    9. foreach (var loc in mapLocation.Value)
    10. {
    11. if (loc.Data is ILocationSizeData sizeData && !bundleLocationsNotCached.Contains(loc)) // any locations with data are bundle locations
    12. {
    13. long sizeToDownload = sizeData.ComputeSize(loc, Addressables.ResourceManager);
    14. totalDownloadToDo += sizeToDownload;
    15. bundleLocationsNotCached.Add(loc);
    16. }
    17. }
    18. }
    19. }
    20. Addressables.DownloadDependenciesAsync(bundleLocationsNotCached);
    21.  
     
  3. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    51
    Thank @andymilsom for your reply.
    I made some modifications to the code based on the suggestions, and it seems to be working now. Thank you very much!
    Code (CSharp):
    1. public static async Task UpdateCatalogs()
    2.     {
    3.         Debug.Log("Check updates begin.");
    4.  
    5.         var checkUpdatHandle = Addressables.CheckForCatalogUpdates();
    6.         var catalogs = await checkUpdatHandle.Task;
    7.         Debug.Log($"Catalogs: {catalogs.Count}");
    8.  
    9.         if (catalogs != null && catalogs.Count > 0)
    10.         {
    11.             Debug.Log("Update catalogs begin.");
    12.          
    13.             // Update the catalog cached locally
    14.             Addressables.UpdateCatalogs(catalogs);
    15.  
    16.             Debug.Log("Update catalogs ended.");
    17.         }
    18.     }
    19.  
    20.     public static async Task DownloadUncachedBundles()
    21.     {
    22.         List<IResourceLocation> bundleLocationsNotCached = new List<IResourceLocation>();
    23.         long totalDownloadSize = 0;
    24.  
    25.         foreach (IResourceLocator locator in Addressables.ResourceLocators)
    26.         {
    27.             var map = locator as ResourceLocationMap; // Simple implementation of IResourceLocator
    28.  
    29.             if (map == null || map.Locations.Count <= 0)
    30.             {
    31.                 Debug.Log("Skipping bundle: map.Locations = 0");
    32.                 continue;
    33.             }
    34.  
    35.             foreach (KeyValuePair<object, IList<IResourceLocation>> mapLocation in map.Locations)
    36.             {
    37.                 foreach (var loc in mapLocation.Value)
    38.                 {
    39.                     if (loc.Data is ILocationSizeData sizeData && !bundleLocationsNotCached.Contains(loc))
    40.                     {
    41.                         long downloadSize = sizeData.ComputeSize(loc, Addressables.ResourceManager);
    42.                         totalDownloadSize += downloadSize;
    43.                         bundleLocationsNotCached.Add(loc);
    44.                     }
    45.                 }
    46.             }
    47.         }
    48.  
    49.         if (bundleLocationsNotCached.Count <= 0)
    50.         {
    51.             Debug.Log("No available updates.");
    52.             return;
    53.         }
    54.         else
    55.         {
    56.             var downloadHandle = Addressables.DownloadDependenciesAsync(bundleLocationsNotCached);
    57.             downloadHandle.Completed += (handle) =>
    58.             {
    59.                 Debug.Log("Download updates ended.");
    60.             };
    61.  
    62.             await downloadHandle.Task;
    63.         }
    64.     }

    However, I have encountered a new issue. After updating the asset bundles and attempting to load the latest cached bundles, an error occurs stating: "can't be loaded because another AssetBundle with the same files is already loaded."

    Here's the relevant callback that is triggered once the update process is finished:
    Code (CSharp):
    1. void LoadBackgroundSprite()
    2.     {
    3.         if (handle.IsValid())
    4.         {
    5.             background.ReleaseAsset();
    6.         }
    7.      
    8.         handle = background.LoadAssetAsync();
    9.         handle.Completed += (handle) =>
    10.         {
    11.             img.sprite = handle.Result;
    12.             Debug.Log("Load background complete");
    13.         };
    14.     }
    15. }
    The asset is packed in an individual bundle. My understanding was that once the asset is released, the bundle should be unloaded, allowing for loading the updated asset. Am I missing something or misunderstanding?

    If the app is closed and reopened, it will display the latest assets.

    I would greatly appreciate any insights or suggestions to help resolve this issue. Thank you in advance for your valuable assistance.
     
    Last edited: Jun 9, 2023
  4. andymilsom

    andymilsom

    Unity Technologies

    Joined:
    Mar 2, 2016
    Posts:
    294
    Firstly make sure you are on a recent version of addressables, as there was a bug with using DownloadDependencies and then Loading, where it wasn't being cached correctly a while back. Resulting in it attempting to load twice.

    Make sure to only do download dependencies when there is size to download, looks like I didn't put that in my example.

    Better to wait on the downloads before trying any loading too, though that shouldn't be required
     
  5. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    51
    @andymilsom Sorry for the late reply.
    Currently, I am using version 1.19.19, which I believe should have addressed the bug we were discussing.

    In reference to the thread, I have checked the available space before downloading, and it seems that there is sufficient free space. Therefore, I don't believe this is the cause of the error.

    After thorough testing, I have discovered that setting the autoReleaseHandle parameter of DownloadDependenciesAsync to true resolves the error. However, when I subsequently load the sprite using the callback triggered upon successful dependency download (possibly after the handler has been released), it loads the old sprites instead. Only when I navigate to another scene and return to the original scene does the latest background sprite appear (the loading of the background is also triggered upon entering the scene).

    To clarify the flow of updating the background sprite:
    1. Update Catalog
    2. Download Uncached Bundles
    3. Load Background Sprite (callback upon successful download)
    4. Loading again, but showing the old sprite
    5. Navigate to another scene
    6. Return to the original scene
    7. Load Background Sprite (Initialize background in Start()), showing the latest sprite
    According to the documentation, DownloadDependenciesAsync should release immediately when the status is "success." Additionally, after calling the update, if I use LoadAssetAsync, I should obtain the latest asset.
    Could you please confirm if my understanding is correct or if there is something I may be overlooking?