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

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 :slight_smile:

Calling Addressables.DownloadDependenciesAsync to download a localization (stringtable) update causes Unity to output the following error:

System.Exception: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown.,
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

  • Open project on Windows
  • Open “Addressables Profiles” window and activate “Editor Hosted”
  • Open “Addressables Hosting” window and disable the service
  • Open “Addressables Groups” window and choose “Use existing Build” in the “Play Mode Script” dropdown
  • Select all “Assets/AddressableAssetsData/AssetGroups/Localization-” assets
  • In the Inspector, set all Localization-* assets to BuildPath=LocalBuildPath and LoadPath=LocalLoadPath
  • Click in the Addressables Groups window “Build > Clean Build > All”
  • Click in the Addressables Groups window “Build > New Build > Default Build Script”
  • Press from main menu “File > Build and Run”
  • In the Player press “Check for Catalog Update”
  • In the Player press “Check for new Content”
  • Close Player, switch back to Unity
  • Open “Window > Asset Management > Localization Tables”
  • Change english and german text
  • Select all “Assets/AddressableAssetsData/AssetGroups/Localization-” assets
  • In the Inspector, set all Localization-* assets to BuildPath=RemoteBuildPath and LoadPath=RemoteLoadPath
  • Click in the Addressables Groups window “Build > New Build > Default Build Script”
  • Run earlier built player (do NOT build it again at this time)
  • In the Player press “Check for Catalog Update”
  • 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: How to have the same bundles as local and remote?

System.Exception: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=localization-string-tables-english(en)_assets_all_19ac7c008226fffe203c44a1db5ec43c.bundle, Type=System.Object
UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:LogError(Object)
UnityEngine.AddressableAssets.AddressablesImpl:LogException(AsyncOperationHandle, Exception) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\AddressablesImpl.cs:253)
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)
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)
UnityEngine.ResourceManagement.CompletedOperation`1:Execute() (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\ResourceManager\ResourceManager.cs:485)
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)
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)
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)
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)
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)
UnityEngine.AddressableAssets.AddressablesImpl:GetDownloadSizeAsync(IEnumerable) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\AddressablesImpl.cs:817)
UnityEngine.AddressableAssets.Addressables:GetDownloadSizeAsync(IEnumerable) (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Library\PackageCache\com.unity.addressables@1.18.4\Runtime\Addressables.cs:833)
<CheckForDownload>d__7:MoveNext() (at D:\Projects\_BugReports\AddressablesContentUpdate_2\Assets\Scripts\Loader.cs:114)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

7236353–871247–bugreport_1343032.zip (183 KB)

1 Like

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.

1 Like

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.

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.

1 Like

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

        IEnumerable<object> allKeys = null;

        foreach (IResourceLocator l in Addressables.ResourceLocators)
        {
            if (allKeys == null)
                allKeys = l.Keys;
            else
                allKeys.Concat(l.Keys);
        }

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.

1 Like

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:

public class ReleaseEverythingThenLoadScene : MonoBehaviour
{
    [SerializeField] AssetReference m_SceneToLoad;

    System.Collections.IEnumerator Start()
    {
        DontDestroyOnLoad(gameObject);
        yield return Resources.UnloadUnusedAssets();

        // release all tables, then preload them again
        var tableNames = new List<TableReference>();
        foreach (var table in Resources.FindObjectsOfTypeAll<StringTable>())
        {
            TableReference tableReference = table.TableCollectionName;
            tableNames.Add(tableReference);
            Debug.LogError($"Releasing localization table {tableReference.TableCollectionName}");

            LocalizationSettings.StringDatabase.ReleaseTable(tableReference);
        }
        LocalizationSettings.StringDatabase.ResetState();
        yield return LocalizationSettings.StringDatabase.PreloadTables(tableNames);


        // load the next scne
        var sceneOp = Addressables.LoadSceneAsync(m_SceneToLoad);
        yield return sceneOp;
        Destroy(gameObject);
    }
}

In the video I quickly go over the test:

https://www.youtube.com/watch?v=-u_8ZwxU9JM

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.

7239191–871637–bugreport_1343032_updated.zip (187 KB)

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.

1 Like

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.

1 Like

Something like LocalizationSettings.StringDatabase.AllTables would be quite useful to have.

Would you want all tables or just all the tables that are currently loaded?

I was thinking of all tables, assuming I can ask whether a table is currently loaded, perhaps like this:

foreach(var table in LocalizationSettings.StringDatabase.AllTables)
{
    if (table.IsLoaded) ...
    if (LocalizationSettings.StringDatabase.IsTableLoaded(table)) ...
}

If it returns all tables, it allows me to easily preload all tables for example:

var allTables = LocalizationSettings.StringDatabase.AllTables;
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.

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:

var allTables = LocalizationSettings.StringDatabase.AllTables;
allTables.WaitForCompletion();

foreach(var table in allTables)
{
    if (LocalizationSettings.StringDatabase.IsTableLoaded(table, French)) ...
    if (LocalizationSettings.StringDatabase.IsTableLoaded(table)) ... // Use selected Locale
}

So should be possible. Ill make a task to look into it more.

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

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.Localization.Settings;
using UnityEngine.SceneManagement;

public class Loader : MonoBehaviour
{
    [SerializeField] AssetReference m_SceneToLoad;

    void Start()
    {
        // https://discussions.unity.com/t/843842
        // Call at the beginning to subsequent calls return a valid handle
        Addressables.InitializeAsync();
    }

    public void OnButtonLoadFromCache()
    {
        StartCoroutine(Load());
    }

    public void OnButtonCheckForCatalogUpdates()
    {
        StartCoroutine(CheckForCatalogUpdates());
    }

    public void OnButtonCheckForDownload()
    {
        StartCoroutine(CheckForDownload());
    }

    System.Collections.IEnumerator Load()
    {
        var initializeOp = Addressables.InitializeAsync();
        yield return initializeOp;
        if (!initializeOp.IsValid())
            Debug.LogError($"Addressables.InitializeAsync operation invalid");

        var sceneOp = Addressables.LoadSceneAsync(m_SceneToLoad);
        yield return sceneOp;
    }

    System.Collections.IEnumerator CheckForCatalogUpdates()
    {
        var initializeOp = Addressables.InitializeAsync();
        yield return initializeOp;
        if (!initializeOp.IsValid())
            Debug.LogError($"Addressables.InitializeAsync operation invalid");

        // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/UpdateCatalogs.html
        // The list of content catalogs with an available update can be aquired through Addressables.CheckForCatalogUpdates
        var checkForUpdateHandle = Addressables.CheckForCatalogUpdates(false);
        yield return checkForUpdateHandle;
        if (checkForUpdateHandle.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
        {
            Debug.LogError($"CheckForCatalogUpdates failed with status {checkForUpdateHandle.Status}");
            yield break;
        }

        var catalogsToUpdate = new List<string>();
        catalogsToUpdate.AddRange(checkForUpdateHandle.Result);
        Addressables.Release(checkForUpdateHandle);

        if (catalogsToUpdate.Count > 0)
        {
            Debug.LogError("Catalog update available");

            // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/UpdateCatalogs.html
            // Returns a list of the IResourceLocators loaded from the updated catalogs.
            // no further action is needed to use the new content catalogs.
            var updateHandle = Addressables.UpdateCatalogs(catalogsToUpdate, false  );
            yield return updateHandle;
            var updateStatus = updateHandle.Status;
            Addressables.Release(updateHandle);
            if (updateStatus != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
            {
                Debug.LogError($"UpdateCatalogs failed with status {updateHandle.Status}");
                yield break;
            }

            Debug.LogError("Catalog updated");
        }
        else
        {
            Debug.LogError("No catalog update available");
        }

    }

    System.Collections.IEnumerator CheckForDownload()
    {
        DontDestroyOnLoad(this.gameObject);

        yield return SceneManager.LoadSceneAsync("Empty", LoadSceneMode.Single);
        LocalizationSettings.StringDatabase.ReleaseTable("New Table");

        IEnumerable<object> allKeys = null;

        foreach (IResourceLocator l in Addressables.ResourceLocators)
        {
            if (allKeys == null)
                allKeys = l.Keys;
            else
                allKeys.Concat(l.Keys);
        }

        // https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/DownloadDependenciesAsync.html
        // GetDownloadSizeAsync checks the total size of all AssetBundles that need to be downloaded. Cached AssetBundles return a size of 0.
        var getDownloadSizeAll = Addressables.GetDownloadSizeAsync(allKeys);
        yield return getDownloadSizeAll;
        if (getDownloadSizeAll.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
        {
            Debug.LogError($"GetDownloadSizeAsync failed with status {getDownloadSizeAll.Status}");
            yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
            yield break;
        }

        if (getDownloadSizeAll.Result <= 0)
        {
            Debug.LogError("No content update available");
            yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
            yield break;
        }

        var kb = getDownloadSizeAll.Result / 1024;
        Debug.LogError($"Content update available, downloading {kb}KB ...");

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. 
        foreach (var key in allKeys)
        {
            var fileSizeHandle = Addressables.GetDownloadSizeAsync(key);
            if (!fileSizeHandle.IsDone)
                yield return fileSizeHandle;
            if (fileSizeHandle.Result <= 0)
                continue; // no update available to file

            Debug.LogError($"downloading {key}");
            var keyDownloadOperation = Addressables.DownloadDependenciesAsync(key);
            yield return keyDownloadOperation;
            if (keyDownloadOperation.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
            {
                Debug.LogError($"DownloadDependenciesAsync failed with status {keyDownloadOperation.Status}");
                yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
                yield break;
            }
        }

        yield return new WaitForSeconds(5);
        yield return SceneManager.LoadSceneAsync("Loader", LoadSceneMode.Single);
        Debug.LogError("Content update completed");
    }
}

7247186–873176–0001-Improved-the-way-we-release-handles-so-that-we-can-b.patch.txt (24.2 KB)

1 Like

That’s exciting news. I think I’ll wait for the official update. Thank you! :slight_smile:

1 Like

Do you know in what Localization version the fix is going to land?

Next one 1.0.0-pre.11. hopefully next week.

1 Like

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.

Hi,
Not at the moment, I’m afraid.
I’ll create a task to look into adding a way to do this.

1 Like

Thanks for the update! In the meantime, showing a stringless loading scene during the update should be fine.

1 Like