Skip to content

Instantly share code, notes, and snippets.

@JBurkeKF
Last active January 3, 2024 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JBurkeKF/7d8d06ad0b4dafa15ec3fedc34df376e to your computer and use it in GitHub Desktop.
Save JBurkeKF/7d8d06ad0b4dafa15ec3fedc34df376e to your computer and use it in GitHub Desktop.
Utility method to refresh/reload Unity's Addressable content catalog after the initial load. (Written against Unity Addressables 1.19.19)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.Initialization;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
#if !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_TVOS
using System.Reflection;
#endif
// Written against Unity Addressables 1.19.19
public class AddressableCatalogRefresh : MonoBehaviour
{
public IEnumerator RefreshCatalog()
{
// Always try initializing, just in case this is being called during initialization. It's okay to initialize multiple times, because the internal system can tolerate it
yield return Addressables.InitializeAsync();
// Force the Addressables system to reload and refresh its catalog and pick up any newly registered DLC mount paths
AddressablesRuntimeProperties.ClearCachedPropertyValues();
Addressables.ClearResourceLocators();
#if UNITY_EDITOR
string settingsPath = PlayerPrefs.GetString( Addressables.kAddressablesRuntimeDataPath, Addressables.LibraryPath + "aa/" + PlatformMappingService.GetPlatformPathSubFolder() + "/settings.json" );
#else
string settingsPath = Application.streamingAssetsPath + "/aa/settings.json";
#endif
#if UNITY_EDITOR
if ( !settingsPath.StartsWith( "GUID:" ) ) // Skip reloading the catalog if we're in the editor and not using a packed mode simulation script
#endif
{
AsyncOperationHandle<ResourceManagerRuntimeData> settingsDataOperation = Addressables.LoadAssetAsync<ResourceManagerRuntimeData>(
new ResourceLocationBase(
"RuntimeData",
Addressables.ResolveInternalId( settingsPath ),
typeof( JsonAssetProvider ).FullName,
typeof( ResourceManagerRuntimeData ) ) );
yield return settingsDataOperation;
ResourceLocationMap catalogLocationMap = new ResourceLocationMap( "CatalogLocator", settingsDataOperation.Result.CatalogLocations );
Addressables.AddResourceLocator( catalogLocationMap );
AsyncOperationHandle<IList<IResourceLocation>> catalogPathsOperation = Addressables.LoadResourceLocationsAsync(
ResourceManagerRuntimeData.kCatalogAddress,
typeof( ContentCatalogData ) );
yield return catalogPathsOperation;
IList<IResourceLocation> catalogPaths = catalogPathsOperation.Result;
if ( catalogPaths != null && catalogPaths.Count > 0 )
{
#if UNITY_STANDALONE || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS
yield return Addressables.LoadContentCatalogAsync( catalogPaths[0].InternalId );
#else
yield return LoadContentCatalogPlatformAsync( catalogPaths[0] );
#endif
}
Addressables.RemoveResourceLocator( catalogLocationMap );
}
}
#if !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_TVOS
/// <summary>
/// The Addressables system <see cref="Addressables.LoadContentCatalogAsync(string, string)" /> checks for cached versions
/// of the content catalog, which require access to <see cref="Application.persistentDataPath" />, something not allowed on
/// certain platforms. Unfortunately, we have to use reflection to dig into the underlying methods and bypass the check for
/// cached data.
/// </summary>
private AsyncOperationHandle<IResourceLocator> LoadContentCatalogPlatformAsync( IResourceLocation catalogLocation )
{
System.Type addressablesType = typeof( Addressables );
System.Type addressablesImplType = System.Type.GetType( "UnityEngine.AddressableAssets.AddressablesImpl, Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" );
if ( addressablesImplType == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get AddressablesImpl type!" );
return Addressables.ResourceManager.CreateCompletedOperation( default( IResourceLocator ), "Could not get AddressablesImpl type!" );
}
System.Type addressablesInitializationOperationType = System.Type.GetType( "UnityEngine.AddressableAssets.Initialization.InitializationOperation, Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" );
if ( addressablesInitializationOperationType == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get InitializationOperation type!" );
return Addressables.ResourceManager.CreateCompletedOperation( default( IResourceLocator ), "Could not get InitializationOperation type!" );
}
// Get the Addressables instance
PropertyInfo addressablesImplInstancePropertyInfo = addressablesType.GetProperty( "Instance", BindingFlags.NonPublic | BindingFlags.Static );
if ( addressablesImplInstancePropertyInfo == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get AddressablesImpl Instance property type!" );
return Addressables.ResourceManager.CreateCompletedOperation( default( IResourceLocator ), "Could not get AddressablesImpl Instance property type!" );
}
object addressablesImplInstance = addressablesImplInstancePropertyInfo.GetValue( null );
if ( addressablesImplInstance == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get AddressablesImpl instance!" );
return Addressables.ResourceManager.CreateCompletedOperation( default( IResourceLocator ), "Could not get AddressablesImpl instance!" );
}
// ShouldChainRequest value
PropertyInfo addressablesImplShouldChainRequestPropertyInfo = addressablesImplType.GetProperty( "ShouldChainRequest", BindingFlags.NonPublic | BindingFlags.Instance );
bool shouldChainRequest = false;
if ( addressablesImplShouldChainRequestPropertyInfo == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get ShouldChainRequest property type!" );
}
else
{
shouldChainRequest = (bool) addressablesImplShouldChainRequestPropertyInfo.GetValue( addressablesImplInstance );
}
if ( shouldChainRequest )
{
// ChainOperation value
PropertyInfo addressablesImplChainOperationPropertyInfo = addressablesImplType.GetProperty( "ChainOperation", BindingFlags.Public | BindingFlags.Instance );
if ( addressablesImplChainOperationPropertyInfo == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get ChainOperation property type!" );
return Addressables.ResourceManager.CreateChainOperation( default( AsyncOperationHandle ), ( operation ) =>
{
return LoadContentCatalogPlatformAsync( catalogLocation );
} );
}
AsyncOperationHandle chainOperation = (AsyncOperationHandle) addressablesImplChainOperationPropertyInfo.GetValue( addressablesImplInstance );
return Addressables.ResourceManager.CreateChainOperation( chainOperation, ( operation ) =>
{
return LoadContentCatalogPlatformAsync( catalogLocation );
} );
}
// LoadContentCatalog method
MethodInfo loadContentCatalogMethodInfo = addressablesInitializationOperationType.GetMethod( "LoadContentCatalog",
new System.Type[] { addressablesImplType, typeof( IResourceLocation ), typeof( string ), typeof( IResourceLocation ) } );
if ( loadContentCatalogMethodInfo == null )
{
Debug.LogError( "AddressableCatalogRefresh.LoadContentCatalogPlatformAsync: could not get LoadContentCatalog method type!" );
return Addressables.ResourceManager.CreateCompletedOperation( default( IResourceLocator ), "Could not get LoadContentCatalog method type!" );
}
AsyncOperationHandle<IResourceLocator> handle = (AsyncOperationHandle<IResourceLocator>) loadContentCatalogMethodInfo.Invoke( null, new object[] { addressablesImplInstance, catalogLocation, null, null } );
return handle;
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment