Hello. every one.
Recently I was interested in addressables and I am developing super simple demo.
But i have some problems in Cache clearing.
I am using Unity 2019.4.5f1 and addressable 1.8.4. Also I am testing my demo on windows standalone.
My develop step is like following.
I created two PackedAsset groups and named them (groupA, groupB).
groupA has asset1, asset2, asset3
groupB has asset4, asset5
I built addressable assetbundle and upload it to aws s3 bucket.
I run the app and app downloaded all assets (both groupA, groupB)
After that, I modified only asset4 from groupB.
I built addressable assetbundle again and replace old ones with this new ones in aws s3 bucket.
When i run the app, app detects the updated size correctly and download only updates (updates was groupB but not only asset4).
But problem was app size increasing because of cached size.
It seems app downloaded groupB bundles again but not replace old groupB bundle.
so I was thinking Addressables.ClearDependencyCacheAsync(key).
IEnumerator checkUpdate()
{
var initHandle = Addressables.InitializeAsync();
yield return initHandle;
var handler = Addressables.CheckForCatalogUpdates(false);
yield return handler;
if (handler.Status == AsyncOperationStatus.Succeeded)
{
var catalogs = handler.Result;
if (catalogs != null && catalogs.Count > 0)
{
Addressables.ClearDependencyCacheAsync(key);
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
yield return updateHandle;
}
}
Addressables.Release(handler);
}
I inserted Addressables.ClearDependencyCacheAsync(key); but I am not sure it is right or not.
maybe it is not correct.
Also how can i define key as parameters on that function?
what can i use for params key.
I hope ClearDependencyCacheAsync will delete old cached (groupB) and download new groupB.
I have found some idea.
You need to compare old cache list and new bundles list from updated catalog.
so you can get lists which caches are old ones from comparation.
That lists will be bundle names.
Using Caching.ClearAllCachedVersions(bundlename) , you can clear old bundles you need to clear.
From my side, this is working great.
of course, this solution seems like manual way but i think it will be useful for some developers.
If there will be some problem for this, please feel free to advice me.
Thank you.
I just need to confirm what I am reading. Caching.ClearAllCachedVersions(bundlename) clears all the bundles with the specific Bundle Name / Addressable Groups name?
Meaning if I have a bundle named my_bundle, gets build into my_bundle_<hash>. Does it account for all the ones with different hashes?
what I mean is this name. maybe “otherpack_assets_all_0f34bb97cbdf95f6c57024202bff5c80”
when you build addressable bundle, you can see this type files in your ServerData. right?
For me, it works great.
Hi, I couldn’t make the Caching.ClearAllCachedVersions(bundlename) works.
When I call it nothing happen. Are you pass in the “otherpack” or “otherpack_assets_all_0f34bb97cbdf95f6c57024202bff5c80”
I’m currently making a Unity apps that contains many minigames and really need the function to remove downloaded game for the storage management feature:face_with_spiral_eyes:.
I’m doing the same thing - for now we’re just not doing it, in the hope that it gets fixed soon. Is there anyone from Unity who can respond to this please?
Currently i’m using this for temporary solution on Editor/Standalone Build, haven’t test it on mobile yet. Still I guess it’ll only work if Addressable is only thing you use as cache since idk what order of Caching.GetAllCachePaths will return
public static void RemoveGameAsset(string gameID, Action onFinish)
{
#region Temporary solution for clearing game
//This solution assume the Caching.GetAllCachePaths will return a list that have first element is the path that addressable use
//If not pray and search on Unity forum for better solution;
Instance.StartCoroutine(Instance.DeleteGame(gameID, result =>
{
List<string> allCachePath = new List<string>();
Caching.GetAllCachePaths(allCachePath);
string path = allCachePath[0];
foreach (var bundle in (List<IAssetBundleResource>)result.Result)
{
try{
Directory.Delete(path + "/" + bundle.GetAssetBundle().name, true);
}
catch(Exception e){
// This to ignore the fact that _lockfile inside assetbundle cannot be delete
}
}
onFinish?.Invoke();
}));
#endregion
}
IEnumerator DeleteGame(string gameID, Action<AsyncOperationHandle> onFinish)
{
var getSizeHandle = Addressables.GetDownloadSizeAsync(gameID);
while (!getSizeHandle.IsDone)
{
yield return new WaitForEndOfFrame();
}
if (getSizeHandle.Result == 0)
{
//Game downloaded process to remove stuff
var getAllBundleHandle = Addressables.DownloadDependenciesAsync(gameID);
while (!getAllBundleHandle.IsDone)
{
yield return new WaitForEndOfFrame();
}
onFinish(getAllBundleHandle);
Addressables.Release(getAllBundleHandle);
}
else
{
Debug.LogError("Error removing game, game hasn't been downloaded");
}
}
Edit: After running for awhile I notice that Directory.Delete cannot delete folder that not empty and cannot delete _lockfile that generate when using Download to fetch all bundle name. So I add try/catch block to ignore that. Still not test on mobile tho
We haven’t, although it’s mentioned in the patch notes for the latest update so it might be resolved?! (although the patch notes themselves appear to be broken, sigh - No documention or changelog for v1.14.2? )
It’d be fantastic if the docs were updated to reflect what’s actually available for use!
I tried to use the newly added ClearCacheDependancy API using label, group’s name, scene’s and nothing seem to work. Well at least the API return AsyncOperation now to properly execute event if I can use it someday. PROGRESS!
As far as I understand it when I skipped through the code of the new method ist, that it’s just a wrapper around the old method that simply returns true instead of void.
I managed to write a workaround. As far as I see it there were not one but two problems in the code.
The Addressables method uses Caching.ClearAllCachedVersions(), which seems to be broken. I fixed it by using the more specific method Caching.ClearCachedVersion();
The Addressables method is using Path.GetFileName(dep.InternalId) instead of the bundle name, which returns the actual hash instead.
I fixed these two issues by copying all methods necessary into my own fixing class and fixed these two issues there. This is my working code (tested in editor, standalone and oculus Quest (Android) with addresses as well as labels. Also getDownLoadSize won’t break like with the workaround mentioned above):
public class AddressablesFixes : MonoBehaviour
{
/// <summary>
/// This is a workaround the broken Addressables method. If Addressable Package is updated this method could break, or in the best case become obsolete.
/// </summary>
/// <param name="key"></param>
public static AsyncOperationHandle ClearDependencyCacheForKey(object key)
{
AsyncOperationHandle initHandle = Addressables.InitializeAsync();
if (initHandle.IsDone)
{
ClearDependencyCacheForKeyInternal(key);
}
else
{
initHandle.Completed += (handle) =>
{
ClearDependencyCacheForKeyInternal(key);
};
}
return initHandle;
}
private static void ClearDependencyCacheForKeyInternal(object key)
{
#if ENABLE_CACHING
IList<IResourceLocation> locations;
if (key is IResourceLocation resourceLocation && resourceLocation.HasDependencies)
{
foreach (var dep in resourceLocation.Dependencies)
{
if (dep.Data is AssetBundleRequestOptions options)
{
ClearCacheForBundle(options.BundleName);
}
}
}
else if (GetResourceLocations(key, typeof(object), out locations))
{
foreach (var dep in GatherDependenciesFromLocations(locations))
{
if (dep.Data is AssetBundleRequestOptions options)
{
ClearCacheForBundle(options.BundleName);
}
}
}
#endif
}
private static void ClearCacheForBundle(string bundleName)
{
List<Hash128> hashList = new List<Hash128>();
Caching.GetCachedVersions(bundleName, hashList);
foreach (Hash128 hash in hashList)
{
Caching.ClearCachedVersion(bundleName, hash);
}
}
static private List<IResourceLocation> GatherDependenciesFromLocations(IList<IResourceLocation> locations)
{
var locHash = new HashSet<IResourceLocation>();
foreach (var loc in locations)
{
if (loc.ResourceType == typeof(IAssetBundleResource))
{
locHash.Add(loc);
}
if (loc.HasDependencies)
{
foreach (var dep in loc.Dependencies)
if (dep.ResourceType == typeof(IAssetBundleResource))
locHash.Add(dep);
}
}
return new List<IResourceLocation>(locHash);
}
private static bool GetResourceLocations(object key, Type type, out IList<IResourceLocation> locations)
{
key = EvaluateKey(key);
locations = null;
HashSet<IResourceLocation> current = null;
foreach (var locatorInfo in Addressables.ResourceLocators)
{
var locator = locatorInfo;
IList<IResourceLocation> locs;
if (locator.Locate(key, type, out locs))
{
if (locations == null)
{
//simple, common case, no allocations
locations = locs;
}
else
{
//less common, need to merge...
if (current == null)
{
current = new HashSet<IResourceLocation>();
foreach (var loc in locations)
current.Add(loc);
}
current.UnionWith(locs);
}
}
}
if (current == null)
return locations != null;
locations = new List<IResourceLocation>(current);
return true;
}
private static object EvaluateKey(object obj)
{
if (obj is IKeyEvaluator)
return (obj as IKeyEvaluator).RuntimeKey;
return obj;
}
}
I will file a bug now and post it here. Thanks for the help eveybody.
@PascalZieglerAzuryLiving
Thanks for the solution, my solution was kind of wacky and not really reliable since it remove file and directory and ignore all thing else.