Search Unity

Re-initializing addressables with another catalog

Discussion in 'Addressables' started by earlGrey_, Apr 3, 2020.

  1. earlGrey_

    earlGrey_

    Joined:
    Jan 30, 2020
    Posts:
    6
    I have data bound to different catalogs. So, my question is how do I make Addressables know that I want to change catalog? I could try using LoadContentCatalogAsync, but then how do I use an advantage of custom variables for RemoteLoadPath, which allow you do {Class.variable}/[BuildTarget] ?

    When I built catalogs, I set the same Player Version Override string for both catalogs. In the example bellow.
    I also have custom variables in addressable settings set to 'D:/MyDLC/Jackets/[BuildTarget]', 'D:/MyDLC/Hats/[BuildTarget]' for build paths and {PathsRegistry.GearPath}/[BuildTarget] for load path.. It's an test project I use to figure how could I write custom build script later...

    What I tried so far is this:
    Code (CSharp):
    1. private async void Start()
    2.   {
    3.     PathsRegistry.GearPath = "D:/MyDLC/Jackets";
    4.     await Addressables.InitializeAsync().Task;
    5.     await Addressables.InstantiateAsync("Jacket").Task;
    6.  
    7.     PathsRegistry.GearPath = "D:/MyDLC/Hats";
    8.     await Addressables.InitializeAsync().Task;
    9.     await Addressables.InstantiateAsync("Hat").Task;
    10.   }
    So, here It spawns a Jacket, but then throws 'UnityEngine.AddressableAssets.InvalidKeyException' on Hat. Or vise versa if I try spawn them the other way around. So how do I 're-initialize' addressables or how to get access to my custom variables, that stored in settings? Otherwice, If it is platform specific, I guess I'll have to determine platform to make proper path when call LoadContentCatalogAsync.. I'm very confused..
     
  2. supersolid-jelte

    supersolid-jelte

    Joined:
    Dec 3, 2018
    Posts:
    22
    Exactly the problem I had :).

    On initializatoin the {PathsRegistry.GearPath} is cached. But you can clear this.

    Code (CSharp):
    1. AddressablesRuntimeProperties.ClearCachedPropertyValues();
    This doesn't solve the problem however...., as calling InitializeAsync again does not reload the settings.
    To get around this I'm using some Reflection to get the Remote HashLocation

    Code (CSharp):
    1.  
    2. private void UpdateHashLocation(...)
    3. {
    4.    var addressablesImpl = ReflectionUtils.GetStaticValue(typeof(Addressables), "m_Addressables");
    5.    var resourceLocators = ReflectionUtils.GetPrivateFieldValue<IList>(addressablesImpl, "m_ResourceLocators");
    6.    foreach (var info in resourceLocators)
    7.    {
    8.       var hashLocation = ReflectionUtils.GetPublicPropertyValue<object>(info, "HashLocation");
    9.       string internalId = ReflectionUtils.GetPrivateFieldValue<string>(hashLocation, "m_Id");
    10.       string newInternalId = <WHAT EVER LOGIC YOU WANT TO CHANGE THE URL>
    11.       ReflectionUtils.SetPrivateFieldValue(hashLocation, "m_Id", newInternalId);
    12.    }
    13. }
    14.  
    Note: We use different urls for content updates, as we need the server to be in control to when new content is available. Not whenever a dev uploads new assets. So we go form "{url}/1/catalog_current.hash" to "{url}/2/catalog_current.hash"

    Note2: ReflectionUtils is a custom class, you'll need to use your own reflection logic, but the methods are descriptive enough to understand what it's doing I guess :)

    Anything but the cleanest solution though...
     
    IceCone_Topy and earlGrey_ like this.
  3. earlGrey_

    earlGrey_

    Joined:
    Jan 30, 2020
    Posts:
    6
    Thank you for the reply! I'll try that a bit later. Now I'm trying a bit different approach. So, I wrote an Editor script that go thru my assets and split them by n assets per assetBundle and creates catalog also. After it finish execution I have a json mapping file which tells which asset is stored in which catalog. So, then I'm trying to load my assets with the following code:
    Code (CSharp):
    1.  
    2.  
    3. ....
    4. ....
    5.   await SpawnAssetAsync("Hat");
    6.   await SpawnAssetAsync("Glasses");
    7.   await SpawnAssetAsync("Gloves");
    8.   await SpawnAssetAsync("Jacket");
    9.   await SpawnAssetAsync("Sweatshirt"); [I]// here it throws an argument out of range..
    10.      //Jacket and sweatshirt tied with the same catalog[/I]
    11. }
    12.  
    13. private static async Task SpawnAssetAsync(string address)
    14. {
    15.   if (!assetToCatalogMapping.TryGetValue(address, out var catalogName))
    16.   {
    17.     Debug.LogError($"could not find catalog for asset with address: {address}");
    18.     return;
    19.   }
    20.  
    21.   var catalogPath = Path.Combine(catalogsFolder, catalogName);
    22.   await Addressables.LoadContentCatalogAsync(catalogPath).Task;
    23.   await Addressables.InstantiateAsync(address).Task;
    24. }
    25.  
    It works fine until after it encounters an asset that associated with catalog that has been loaded before.. Then it throws an error:
    ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: startIndex
    System.ThrowHelper.ThrowArgumentOutOfRangeException (System.ExceptionArgument argument, System.ExceptionResource resource) (at <437ba245d8404784b9fbab9b439ac908>:0)
    System.BitConverter.ToInt32 (System.Byte[] value, System.Int32 startIndex) (at <437ba245d8404784b9fbab9b439ac908>:0)
    UnityEngine.AddressableAssets.ResourceLocators.ContentCatalogData.CreateLocator (System.String providerSuffix) (at Library/PackageCache/com.unity.addressables@1.7.5/Runtime/ResourceLocators/ContentCatalogData.cs:289)


    Does this sound like the problem of the same nature as above? I've found this thread on the forum, is it still an Addressables bug or I'm trying to do something wrong?

    I have 1.7.5 ver.
     
    Last edited: Apr 4, 2020
  4. kmy_

    kmy_

    Joined:
    Apr 19, 2018
    Posts:
    4
  5. KBaxtrom-LevelEx

    KBaxtrom-LevelEx

    Joined:
    Feb 5, 2018
    Posts:
    28
    We're in the same boat and would like a server to control which contnet catalogs are available.

    We want QA to be able to verify a contnet update before it goes out
    We'd like our devs to be able to share their changes with other devs for testing.

    Ideally we would not have to reach into the module using reflection. Has anyone found another way to point to a new catalog after having loaded one already?
     
  6. liyangw107

    liyangw107

    Joined:
    Aug 27, 2019
    Posts:
    12
    It's great. can you please share more code? I cannot find ReflectionUtils function?:)
     
  7. supersolid-jelte

    supersolid-jelte

    Joined:
    Dec 3, 2018
    Posts:
    22
    It's not something special, just some helper methods around reflections:

    Code (CSharp):
    1. using System;
    2. using System.Reflection;
    3.  
    4. namespace Supersolid.AssetManagement.Utils
    5. {
    6.     public static class ReflectionUtils
    7.     {
    8.         public static object GetStaticValue(Type type, string name)
    9.         {
    10.             return GetStaticValue<object>(type, name);
    11.         }
    12.        
    13.         public static TReturn GetStaticValue<TReturn>(Type type, string name) where TReturn : class
    14.         {
    15.             return type.GetField(name, BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) as TReturn;
    16.         }
    17.        
    18.         public static TReturn GetPrivateFieldValue<TReturn>(object subject, string name) where TReturn : class
    19.         {
    20.             return subject.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(subject) as TReturn;
    21.         }
    22.  
    23.         public static void SetPrivateFieldValue(object subject, string name, string value)
    24.         {
    25.             subject.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(subject, value);
    26.         }
    27.  
    28.         public static TReturn GetPublicPropertyValue<TReturn>(object subject, string name) where TReturn : class
    29.         {
    30.             return subject.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(subject) as TReturn;
    31.         }
    32.     }
    33. }