Search Unity

Bug (Case 1343032) Updating Localization through Addressables causes System.Exception

Discussion in 'Localization Tools' started by Peter77, Jun 14, 2021.

  1. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    Using Unity 2019.4.20f1, Addressables 1.18.4 and Localization 1.0.0-pre.9

    This is a pretty big issue for me. I hope there is a workaround or fix on the way :)

    Calling
    Addressables.DownloadDependenciesAsync
    to download a localization (stringtable) update causes Unity to output the following error:
    Code (CSharp):
    1. System.Exception: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown.,
    2. Key=localization-string-tables-english(en)_assets_all_19ac7c008226fffe203c44a1db5ec43c.bundle, Type=System.Object
    The problem occurs when the game displays or displayed localized text before downloading the new content.

    It's very much necessary to display localized text before downloading new content, for example to display a "A content update is available. It's recommended to connect with Wi-Fi" message to the user before the update.

    I've attached two videos to the bug-report. The video posted in this thread is the "short" version of the two, that gets to the point more quickly.



    Reproduce
    1. Open project on Windows
    2. Open "Addressables Profiles" window and activate "Editor Hosted"
    3. Open "Addressables Hosting" window and disable the service
    4. Open "Addressables Groups" window and choose "Use existing Build" in the "Play Mode Script" dropdown
    5. Select all "Assets/AddressableAssetsData/AssetGroups/Localization-" assets
    6. In the Inspector, set all Localization-* assets to BuildPath=LocalBuildPath and LoadPath=LocalLoadPath
    7. Click in the Addressables Groups window "Build > Clean Build > All"
    8. Click in the Addressables Groups window "Build > New Build > Default Build Script"
    9. Press from main menu "File > Build and Run"
    10. In the Player press "Check for Catalog Update"
    11. In the Player press "Check for new Content"
    12. Close Player, switch back to Unity
    13. Open "Window > Asset Management > Localization Tables"
    14. Change english and german text
    15. Select all "Assets/AddressableAssetsData/AssetGroups/Localization-" assets
    16. In the Inspector, set all Localization-* assets to BuildPath=RemoteBuildPath and LoadPath=RemoteLoadPath
    17. Click in the Addressables Groups window "Build > New Build > Default Build Script"
    18. Run earlier built player (do NOT build it again at this time)
    19. In the Player press "Check for Catalog Update"
    20. In the Player press "Check for new Content"

    Actual
    Localization or Addressables causes System.Exception.

    Expected
    Localization can be updated via Addressables content update.

    Notes
    If you want to test this multiple times, it seems you need to delete "C:\Users\%UserName%\AppData\LocalLow\DefaultCompany\AddressablesContentUpdate_3"

    Notes 2
    Here is explained why the Local & Remote Build/LoadPath changes are necessary: https://forum.unity.com/threads/how...les-as-local-and-remote.1118533/#post-7221787

    Code (CSharp):
    1. System.Exception: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=localization-string-tables-english(en)_assets_all_19ac7c008226fffe203c44a1db5ec43c.bundle, Type=System.Object
    2. UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    3. UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    4. UnityEngine.Logger:Log(LogType, Object)
    5. UnityEngine.Debug:LogError(Object)
    6. UnityEngine.AddressableAssets.AddressablesImpl:LogException(AsyncOperationHandle, Exception) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\AddressablesImpl.cs:253)
    7. UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:set_OperationException(Exception) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:306)
    8. UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:Complete(Int64, Boolean, Exception, Boolean) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:425)
    9. UnityEngine.ResourceManagement.CompletedOperation`1:Execute() (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\ResourceManager.cs:485)
    10. UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:InvokeExecute() (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:470)
    11. UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:Start(ResourceManager, AsyncOperationHandle, DelegateList`1) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:465)
    12. UnityEngine.ResourceManagement.ResourceManager:StartOperation(AsyncOperationBase`1, AsyncOperationHandle) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\ResourceManager.cs:440)
    13. UnityEngine.ResourceManagement.ResourceManager:CreateCompletedOperationInternal(Int64, Boolean, Exception, Boolean) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\ResourceManager.cs:581)
    14. UnityEngine.ResourceManagement.ResourceManager:CreateCompletedOperation(Int64, String) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\ResourceManager.cs:562)
    15. UnityEngine.AddressableAssets.AddressablesImpl:GetDownloadSizeAsync(IEnumerable) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\AddressablesImpl.cs:817)
    16. UnityEngine.AddressableAssets.Addressables:GetDownloadSizeAsync(IEnumerable) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\Addressables.cs:833)
    17. <CheckForDownload>d__7:MoveNext() (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Assets\Scripts\Loader.cs:114)
    18. UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
     

    Attached Files:

    Last edited: Jun 14, 2021
    karl_jones likes this.
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    Hey, I think this is more of an Addressables issue. Localization just calls into the Addressables system and lets it do the asset handling. Although it looks like you want to load a table Locally and then pull the updated version of the same table? That may require some changes on our side, ill speak to the Addressables team.

    Edit: I spoke to the team and I think the issue is that you are trying to update an asset that has already been loaded (The String Table). They said the the original Content needs to be Released before trying to use anything new. They will look more into it once the bug report has gone through QA and is with them. For now I would try and use a static String Table, one that wont be updated, for your loading menu and then allow the other ones to be dynamic.
    The ideal solution would be for us to either merge the new data or do the unload and reload in the background but ill have to investigate to see if that is possible.
     
    Last edited: Jun 14, 2021
    Peter77 likes this.
  3. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    Thank you for the quick reply. That's correct. I want to pull an updated version of the same table.

    It's fine if the localized texts that are currently being displayed on the screen continue to show the texts prior the update. It's fine if I need to reload the scene for the update to take affect or call a "Localization.Reload" method.

    Throwing an exception during the update and then having to restart the application is a problem.
     
    Last edited: Jun 14, 2021
  4. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    We do have an API to release the table however I dont think this would work as any LocalizedStrings in the scene will hold a reference to the table. We probably need some sort of ReloadTable method to support this use case. Ill investigate and see if its possible.

    What may work for you now is if you were to switch to an empty scene to do the content update, this would release the table from any LocalizedStrings, then call ReleaseTable before performing the content update. This should in theory cause the table to be unloaded so that it can be reloaded. You could then switch back to the other scene after updating.
     
    Peter77 likes this.
  5. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    The addressables team said you should not be using InitialiseAsync after Initialising to get the ResourceLocators.
    Changing CheckForDownload so that it gets the keys like so stopped the error although the asset does not refresh

    Code (csharp):
    1.  
    2.         IEnumerable<object> allKeys = null;
    3.  
    4.         foreach (IResourceLocator l in Addressables.ResourceLocators)
    5.         {
    6.             if (allKeys == null)
    7.                 allKeys = l.Keys;
    8.             else
    9.                 allKeys.Concat(l.Keys);
    10.         }
    Ill do some debugging and see if I can figure out how to reload the table.
    I did find a bug in our release code so that could be part of the problem.
     
    Last edited: Jun 14, 2021
    Peter77 likes this.
  6. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    I really don't know what I'd do if you weren't here and help. Thank you!

    Please see attached
    bugreport_1343032_updated.zip
    for the updated test project.

    I tried it, but I the localized texts unfortunately don't update. It's highly likely that my code isn't correct, please see below:
    Code (CSharp):
    1. public class ReleaseEverythingThenLoadScene : MonoBehaviour
    2. {
    3.     [SerializeField] AssetReference m_SceneToLoad;
    4.  
    5.     System.Collections.IEnumerator Start()
    6.     {
    7.         DontDestroyOnLoad(gameObject);
    8.         yield return Resources.UnloadUnusedAssets();
    9.  
    10.         // release all tables, then preload them again
    11.         var tableNames = new List<TableReference>();
    12.         foreach (var table in Resources.FindObjectsOfTypeAll<StringTable>())
    13.         {
    14.             TableReference tableReference = table.TableCollectionName;
    15.             tableNames.Add(tableReference);
    16.             Debug.LogError($"Releasing localization table {tableReference.TableCollectionName}");
    17.  
    18.             LocalizationSettings.StringDatabase.ReleaseTable(tableReference);
    19.         }
    20.         LocalizationSettings.StringDatabase.ResetState();
    21.         yield return LocalizationSettings.StringDatabase.PreloadTables(tableNames);
    22.  
    23.  
    24.         // load the next scne
    25.         var sceneOp = Addressables.LoadSceneAsync(m_SceneToLoad);
    26.         yield return sceneOp;
    27.         Destroy(gameObject);
    28.     }
    29. }
    In the video I quickly go over the test:


    I didn't find a way to grab all StringTable's that're currently loaded other than FindObjectsOfTypeAll. A
    ReloadAll
    method that reloads all StringTable's would be even more useful for me. You probably have all the state what's loaded inside the Localization system already.

    Thank you, that indeed stops the error message to occur.
     

    Attached Files:

  7. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    I also tried to do this but there's some issues with our reference counting for the tables which is preventing them from being released correctly. I'm currently working through them. Hopefully it will work after I figure out that issue.
     
    Peter77 likes this.
  8. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    I think what could be useful for content updates in general is a "Addressables.UnloadAll" method, which basically shuts down Addressables and initializes it again. Then one doesn't need to handle reference counting for specific use-cases like a content update. Making sure everything has a reference count of 0 is very difficult to achieve outside of example projects.
     
    karl_jones likes this.
  9. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    Something like
    LocalizationSettings.StringDatabase.AllTables
    would be quite useful to have.
     
  10. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    Would you want all tables or just all the tables that are currently loaded?
     
  11. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    I was thinking of all tables, assuming I can ask whether a table is currently loaded, perhaps like this:
    Code (CSharp):
    1. foreach(var table in LocalizationSettings.StringDatabase.AllTables)
    2. {
    3.     if (table.IsLoaded) ...
    4.     if (LocalizationSettings.StringDatabase.IsTableLoaded(table)) ...
    5. }
    If it returns all tables, it allows me to easily preload all tables for example:
    Code (CSharp):
    1. var allTables = LocalizationSettings.StringDatabase.AllTables;
    2. LocalizationSettings.StringDatabase.PreloadTables(allTables);
    I didn't find a property that returns whether the table is marked with "Preload". It's probably useful to expose it as well.
     
  12. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    We do have an Editor API to check if a table is preloaded.

    We are not actually aware of all the tables in the player and have to discover them. We do this by loading the, via a table name or Guid. We could find out all the available tables by querying the Addressable system for all SharedTableData and then mapping that across to the tables we have loaded.

    It would likely look like this:
    Code (csharp):
    1.  
    2. var allTables = LocalizationSettings.StringDatabase.AllTables;
    3. allTables.WaitForCompletion();
    4.  
    5. foreach(var table in allTables)
    6. {
    7.     if (LocalizationSettings.StringDatabase.IsTableLoaded(table, French)) ...
    8.     if (LocalizationSettings.StringDatabase.IsTableLoaded(table)) ... // Use selected Locale
    9. }
    So should be possible. Ill make a task to look into it more.
     
  13. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    Hey good news. I managed to get it working.
    I can reload a String Table in the player

    I had to fix some issues in our Addressables handling, we had some bugs in how we do Acquire and Release.
    These are the changes I made to your script
    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEngine;
    5. using UnityEngine.AddressableAssets;
    6. using UnityEngine.AddressableAssets.ResourceLocators;
    7. using UnityEngine.Localization.Settings;
    8. using UnityEngine.SceneManagement;
    9.  
    10. public class Loader : MonoBehaviour
    11. {
    12.     [SerializeField] AssetReference m_SceneToLoad;
    13.  
    14.     void Start()
    15.     {
    16.         // https://forum.unity.com/threads/case-1341439-addressables-initializeasync-returns-invalid-handle.1121872/
    17.         // Call at the beginning to subsequent calls return a valid handle
    18.         Addressables.InitializeAsync();
    19.     }
    20.  
    21.     public void OnButtonLoadFromCache()
    22.     {
    23.         StartCoroutine(Load());
    24.     }
    25.  
    26.     public void OnButtonCheckForCatalogUpdates()
    27.     {
    28.         StartCoroutine(CheckForCatalogUpdates());
    29.     }
    30.  
    31.     public void OnButtonCheckForDownload()
    32.     {
    33.         StartCoroutine(CheckForDownload());
    34.     }
    35.  
    36.     System.Collections.IEnumerator Load()
    37.     {
    38.         var initializeOp = Addressables.InitializeAsync();
    39.         yield return initializeOp;
    40.         if (!initializeOp.IsValid())
    41.             Debug.LogError($"Addressables.InitializeAsync operation invalid");
    42.  
    43.         var sceneOp = Addressables.LoadSceneAsync(m_SceneToLoad);
    44.         yield return sceneOp;
    45.     }
    46.  
    47.     System.Collections.IEnumerator CheckForCatalogUpdates()
    48.     {
    49.         var initializeOp = Addressables.InitializeAsync();
    50.         yield return initializeOp;
    51.         if (!initializeOp.IsValid())
    52.             Debug.LogError($"Addressables.InitializeAsync operation invalid");
    53.  
    54.         // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/UpdateCatalogs.html
    55.         // The list of content catalogs with an available update can be aquired through Addressables.CheckForCatalogUpdates
    56.         var checkForUpdateHandle = Addressables.CheckForCatalogUpdates(false);
    57.         yield return checkForUpdateHandle;
    58.         if (checkForUpdateHandle.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
    59.         {
    60.             Debug.LogError($"CheckForCatalogUpdates failed with status {checkForUpdateHandle.Status}");
    61.             yield break;
    62.         }
    63.  
    64.         var catalogsToUpdate = new List<string>();
    65.         catalogsToUpdate.AddRange(checkForUpdateHandle.Result);
    66.         Addressables.Release(checkForUpdateHandle);
    67.  
    68.         if (catalogsToUpdate.Count > 0)
    69.         {
    70.             Debug.LogError("Catalog update available");
    71.  
    72.             // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/UpdateCatalogs.html
    73.             // Returns a list of the IResourceLocators loaded from the updated catalogs.
    74.             // no further action is needed to use the new content catalogs.
    75.             var updateHandle = Addressables.UpdateCatalogs(catalogsToUpdate, false  );
    76.             yield return updateHandle;
    77.             var updateStatus = updateHandle.Status;
    78.             Addressables.Release(updateHandle);
    79.             if (updateStatus != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
    80.             {
    81.                 Debug.LogError($"UpdateCatalogs failed with status {updateHandle.Status}");
    82.                 yield break;
    83.             }
    84.  
    85.             Debug.LogError("Catalog updated");
    86.         }
    87.         else
    88.         {
    89.             Debug.LogError("No catalog update available");
    90.         }
    91.  
    92.     }
    93.  
    94.     System.Collections.IEnumerator CheckForDownload()
    95.     {
    96.         DontDestroyOnLoad(this.gameObject);
    97.  
    98.         yield return SceneManager.LoadSceneAsync("Empty", LoadSceneMode.Single);
    99.         LocalizationSettings.StringDatabase.ReleaseTable("New Table");
    100.  
    101.         IEnumerable<object> allKeys = null;
    102.  
    103.         foreach (IResourceLocator l in Addressables.ResourceLocators)
    104.         {
    105.             if (allKeys == null)
    106.                 allKeys = l.Keys;
    107.             else
    108.                 allKeys.Concat(l.Keys);
    109.         }
    110.  
    111.         // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/DownloadDependenciesAsync.html
    112.         // GetDownloadSizeAsync checks the total size of all AssetBundles that need to be downloaded. Cached AssetBundles return a size of 0.
    113.         var getDownloadSizeAll = Addressables.GetDownloadSizeAsync(allKeys);
    114.         yield return getDownloadSizeAll;
    115.         if (getDownloadSizeAll.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
    116.         {
    117.             Debug.LogError($"GetDownloadSizeAsync failed with status {getDownloadSizeAll.Status}");
    118.             yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
    119.             yield break;
    120.         }
    121.  
    122.         if (getDownloadSizeAll.Result <= 0)
    123.         {
    124.             Debug.LogError("No content update available");
    125.             yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
    126.             yield break;
    127.         }
    128.  
    129.         var kb = getDownloadSizeAll.Result / 1024;
    130.         Debug.LogError($"Content update available, downloading {kb}KB ...");
    131.  
    132. I attached a patch you can apply the localization package if you want to try it out now. Ill clean these changes up and try and get them into the next release.
    133.         foreach (var key in allKeys)
    134.         {
    135.             var fileSizeHandle = Addressables.GetDownloadSizeAsync(key);
    136.             if (!fileSizeHandle.IsDone)
    137.                 yield return fileSizeHandle;
    138.             if (fileSizeHandle.Result <= 0)
    139.                 continue; // no update available to file
    140.  
    141.             Debug.LogError($"downloading {key}");
    142.             var keyDownloadOperation = Addressables.DownloadDependenciesAsync(key);
    143.             yield return keyDownloadOperation;
    144.             if (keyDownloadOperation.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
    145.             {
    146.                 Debug.LogError($"DownloadDependenciesAsync failed with status {keyDownloadOperation.Status}");
    147.                 yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
    148.                 yield break;
    149.             }
    150.         }
    151.  
    152.         yield return new WaitForSeconds(5);
    153.         yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
    154.         Debug.LogError("Content update completed");
    155.     }
    156. }
     

    Attached Files:

    Peter77 likes this.
  14. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    That's exciting news. I think I'll wait for the official update. Thank you! :)
     
    karl_jones likes this.
  15. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,619
    Do you know in what Localization version the fix is going to land?
     
  16. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    Next one 1.0.0-pre.11. hopefully next week.
     
    Peter77 likes this.
  17. jake_card

    jake_card

    Joined:
    Oct 2, 2014
    Posts:
    57
    Hey there! Sorry to bump an old thread, but I wanted to check and see if there have been any updates over the past year to allow for reloading downloaded string tables without loading an empty scene. I've got everything above working, but it'd be awesome if we could just silently update the string tables after download without showing the player a loading screen.
     
  18. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,299
    Hi,
    Not at the moment, I'm afraid.
    I'll create a task to look into adding a way to do this.
     
    jake_card likes this.
  19. jake_card

    jake_card

    Joined:
    Oct 2, 2014
    Posts:
    57
    Thanks for the update! In the meantime, showing a stringless loading scene during the update should be fine.
     
    karl_jones likes this.