How to get asset and it's guid from known lable?

Hi.
I need to load all assets with label. Then I need to create dictionary of that asset and its guid in order to get asset i need from that dictionary by its guid (AssetReference.RuntimeKey is a guid).

I know how to load assets or its locations with lable but i do not know how to find the key in form of guid not in form of name.

AssetReference class has a runtime key in form of guid, which is further used to load corresponding asset.

How do i load and get matched pairs of assets and it’s guids knowing only lable?

Or how do i convert guid based address of AssetReference into name based adress that I see in addressable window?

I found out that resource locator contains both name based and guid based location key.
Currently addressables.LoadResourceLocations( myLable) returns only locations with name based address…
How do i get guid based locations?

I dont know answer for your exact question, but if you want to create cache of your assets to prevent loading delay - you dont need to actually store them.

If you just load them into memory and do not release them - following calls to the same asset seems to get already loaded copy of said asset.

In our project when we preload - we just get assets we want async and store handle (to release later). When we need to actually use the asset - we send another LoadAssetAsync that returns same asset we already loaded instantly (or with 1 frame unnoticeable delay, not sure). Just need to keep track and release all the handles when you don`t need the assets.

Sorry if this is not the answer you were looking for :slight_smile:

1 Like

Thanks for answer.
But it still returns assets in asynchronous way with 1 frame delay.
I need instant synchronous access after i load all this assets and its dependendensies asynchronously at start of the game.

I actually found the way and will post my solution after I test it.

Here it is how i did it.

using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;

public  class SyncAddressableDatabase : MonoBehaviour
{
   public static SyncAddressableDatabase Instance;
   public static readonly string SynchronousLabel = "Synchronous";

   private readonly Dictionary<string, Object> m_AssetKeyDatabase = new Dictionary<string, Object>();
  
   private void Awake()
   {
      if(Instance != null)
         Debug.LogError("There is multiple instances of single class " + nameof(SyncAddressableDatabase));

      Instance = this;
      DontDestroyOnLoad(this);
   }

   public async Task Initialize()
   {

      // Loading locations by label
      var labelOperation = Addressables.LoadResourceLocationsAsync(SynchronousLabel);
      var syncLocations = await labelOperation.Task;
     
      // Loading assets and making dictionary of location and corresponding asset
      var tempLocationIdDatabase = new Dictionary<string, Object>();
      var operationHandles = new List<AsyncOperationHandle<Object>>();
      foreach (var syncLocation in syncLocations)
      {
         var asyncOperationHandle = Addressables.LoadAssetAsync<Object>(syncLocation.PrimaryKey);
         operationHandles.Add(asyncOperationHandle);
         asyncOperationHandle.Completed += handle =>
            tempLocationIdDatabase.Add(syncLocation.InternalId, handle.Result);
      }

      // Awaiting for all asset loadings to be completed
      var curTime = Time.time;
      foreach (var asyncOperationHandle in operationHandles)
         await asyncOperationHandle.Task;
      Debug.Log(Time.time - curTime);
    
      // Iterate for each resource locators and it's keys
      // In order to find matched locations and add key into database
      m_AssetKeyDatabase.Clear();
      foreach (var resourceLocator in Addressables.ResourceLocators)
      {
         foreach (var key in resourceLocator.Keys)
         {
            var hasLocation = resourceLocator.Locate(key, typeof(Object), out var keyLocations);
            if (!hasLocation)
               continue;
            if(keyLocations.Count == 0)
               continue;
            if(keyLocations.Count > 1)
               continue;

            var keyLocationId = keyLocations[0].InternalId;
            var locationMatched = tempLocationIdDatabase.TryGetValue(keyLocationId, out var asset);
            if (!locationMatched)
               continue;

            m_AssetKeyDatabase.Add(key.ToString(), asset);
         }
      }
   }
  
   public static Object GetAssetSync(string key)
   {
      Instance.m_AssetKeyDatabase.TryGetValue(key, out var asset);
      return asset;
   }
}


public static class SynchronousAssetKeyDatabaseExtension
{
   public static Object GetAssetSync(this IKeyEvaluator assetReference) =>
      SyncAddressableDatabase.GetAssetSync(assetReference.RuntimeKey.ToString());
}

In short i load locations by lable. Than i load assets and make pairs of location and loaded asset into temporary dictionary. Than I iterate for all RessourceLocators and their Keys and try to locate and if location matches i add key and previously loaded asset into final dictionary. I now able to retrieve Loaded asset from that dictionary by key ( both name based and guid based) via simple extention method AssetReferenceField.GetAssetSync().

From my understanding Addressable system has some form of built-in cache and will not actually load asset second time if it is in memory. And this system appears to duplicate that behavior.
I may be wrong, though, because so far my understanding of Addressables is limited.

 Debug.Log(Time.frameCount);
        var key = TestAssetReference.RuntimeKey.ToString();
        var op = Addressables.LoadAssetAsync<Object>(key);

        await op.Task;

        await Task.Delay(5000);
       
        Debug.Log(Time.frameCount);
        var repeatedOp = Addressables.LoadAssetAsync<Object>(key);
        repeatedOp.Completed += handle =>
            Debug.Log(Time.frameCount);

Calling AddressablesLoadAssetAsync(key) still has one frame delay.
I tested it with the above code.

If there is the chacke may be there is a way to simpli iterate through it without creating custom database, i need to inverstigate more on that subject.

Hey TextusGames. Thanks a ton for this code. I was running into this exact same problem.

Any update on your research into the internal cache?

1 Like

A quick update:

I turned your code into an AsyncOperation using the Addressables AsyncOperationBase class. It allows you to yield return the operation in a coroutine and generally fits the Addressables theme. Heres the code:

public class LoadAssetsByLabelOperation : AsyncOperationBase<List<AsyncOperationHandle<Object>>>
    {
        string _label;
        Dictionary<object, AsyncOperationHandle> _dictionary;
       
        public LoadAssetsByLabelOperation(Dictionary<object, AsyncOperationHandle> dictionary, string label)
        {
            _dictionary = dictionary;
            if(_dictionary == null)
                _dictionary = new Dictionary<object, AsyncOperationHandle>();
            _label = label;
        }
        protected override void Execute()
        {
            //Task.Factory.StartNew(new Action(LoadAssetsByLabelAsync);
            DoTask();
        }
       
        public async Task DoTask()
        {
            var locationsHandle = Addressables.LoadResourceLocationsAsync(_label);
            var locations = await locationsHandle.Task;

            var locationInternalIdDictionary = new Dictionary<string, AsyncOperationHandle<Object>>();
            var operationHandles = new List<AsyncOperationHandle<Object>>();
            foreach (var resourceLocation in locations)
            {
                AsyncOperationHandle<Object> assethandle = Addressables.LoadAssetAsync<Object>(resourceLocation.PrimaryKey);
               
                operationHandles.Add(assethandle);
                assethandle.Completed += assetOp =>
                {
                    if (!locationInternalIdDictionary.ContainsKey(resourceLocation.InternalId))
                        locationInternalIdDictionary.Add(resourceLocation.InternalId, assetOp);
                };
            }
           
            foreach (var handle in operationHandles)
                await handle.Task;
           
            foreach (var locator in Addressables.ResourceLocators)
            {
                foreach (var key in locator.Keys)
                {
                    var hasLocation = locator.Locate(key, typeof(Object), out var keyLocations);
                    if (!hasLocation)
                        continue;
                    if (keyLocations.Count == 0)
                        continue;
                    if (keyLocations.Count > 1)
                        continue;

                    var keyLocationID = keyLocations[0].InternalId;

                    var locationMatched = locationInternalIdDictionary.TryGetValue(keyLocationID, out var loadedHandle);
                    if (!locationMatched)
                        continue;
                   
                    if(!_dictionary.ContainsKey(key))
                        _dictionary.Add(key, loadedHandle);
                }
            }
           
            Complete(operationHandles, true, string.Empty);
        }
    }

An example method that would call this is:

public static AsyncOperationHandle<List<AsyncOperationHandle<Object>>> LoadAssetsByLabelAsync(string label)
        {
            var handle = Addressables.ResourceManager.StartOperation(new LoadAssetsByLabelOperation(_loadedAssets, label), default);
            return handle;
        }

It also allows you the chain it along with other AsyncOperationHandles.

1 Like