Addressables unloading dependencies from other assets.

I am using Addressables 1.8.3 and having an issue where unloading some assets causes other dependencies to become unloaded. This applies to things such as materials as well as ScriptableObjects.

Just about everything is marked as addressable. I gather depedencies and try to ensure that no asset duplication happens (it works in a similar fashion to the asset de-duplication analysis). Every single asset in my game except for the bootstrapped scene is loaded through addressables (and are in fact bundles).

However, despite this, it seems as though unloading assets breaks some non-addressable referenced data. So if that piece of data (material, SO, whatever) is just a plain reference it has a good chance of becoming null as the bundle that that data is actually in gets unloaded. That asset itself is also addressable even though in this particular case I’m not using it as one (it’s only marked to eliminate duplication).

It almost seems like the dependency check in the addressables is incomplete? Has anyone else had this happen? Solutions? Suggestions?

2 Likes

I’ll kick this over to the team to provide some guidance.

1 Like

The issue is pretty straightforward. If you use any object that is an addressable but you use it without using it as an AssetReference then things are prone to break.

If a better explanation is needed there are a number of replies here on the matter: Direct reference within asset bundle

2 Likes

Team wanted me to pass along that they have been seeing others reporting this, and it’s on their radar. They also suggested you file a bug report with your project attached so they can look into it further: Unity QA: Building quality with passion

@TreyK-47 I am not in a position to be able to attach a project nor create a minimal reproduction project. Which is usually where Unity bugs get pushed to “can’t fix, no reproduction”. This issue is very similar to the issues arising from the DontDestroyOnLoad issues mentioned in 1.9.2 except it applies to everything not just those. DontDestroyOnLoad assets just happen to be one way that it can occur.

Load a scriptableObject which direct reference to another addressable audio clip(in another group only contains audio clips referenced by scriptableObjects), unload this scriptableObject, call Resouces.UnloadAllUnsuedAssets, Load this scriptableObject again, audio clip is missing. To me this happens 100% in my project, my solution is simple, hold a reference to a random audio clip in that group and never release it…

1 Like

@Thermos That’s the thing - it can be solved by bundling differently as well. But, since that is difficult to do well I think it would make more sense for them to fix it on the addressables end as people will run in to this eventually.

In theory it should be solvable by making sure that dependencies are coalesced when loaded from a bundle. That is: if you load one object from a bundle, and another object from a bundle - technically those two objects may now share cross bundle dependencies because objects cannot be unloaded properly except by unloading a whole bundle.

Was able to get a reproduce with a small test project. Submitted a bug report. Case number 1266005 .
It has to do with indirect bundle dependencies:

Lets assume there are three bundles. A, B and C.
A depends on B and B depends on C.
Note: A does NOT depend on C directly.

So lets say we do:

  1. Load an asset of A
    This will also load B because A depends on B

  2. Instantiate a prefab of B that references an asset in C
    This will also load C because B depends on C

  3. Release the instance of prefab B
    This will unload C but it will NOT unload B because an asset of A is still loaded, which has a dependency on B

  4. Instantiate the prefab of B again. Now the reference on prefab B to the asset in C is missing.

3 Likes

Yep. This is why they need to properly track dependencies for entire bundles and not just individual objects which seems to be the underlying issue.

1 Like

It seems we were having the same issue here, our materials were losing reference to the textures after unloading the prefabs through Addressables and loading them again.
My interpretation would be that, like in the example from @jannysice , We had a bundle for the prefab (A), another for the material (B) and another for the texture (C). When loading the assets a second time after releasing them, the material (B) would lose reference to the texture (C)…

Here we tried to workaround this issue by replacing the “AssetBundleProvider” built-in script in every group for a custom one that would force the internal AssetBundle.Unload() call parameter to “false” (that way it will only unload the bundle but not the assets), and then forcing a Resources.UnloadUnusedAssets() between the scenes loading.
At first it seemed to work, memory still seemed to be stable from one scene to another, although some assets would stay in memory until a second scene loading, but then later we started have what seems to be memory fragmentation issues in console platforms and we are struggling with it right now…

It is just frustrating that we are having so much trouble to implement such simple scenario. We had no problems to adapt the code to work asynchronously, but instead we are having such weird dependency issues…

2 Likes

Yes. They absolutely need to fix the issue. The solution could mostly involve baking more metadata per asset which might increase the footprint.

2 Likes

@KAJed , @jannysice and @Thermos , it seems the latest version, 1.13.1, fixed the issue for us, you should check it out if you didn’t.

Interestingly enough, we have a similar situation occurring that is not fixed by 1.13.1. It was an absolute mess to diagnose but it boils down to something along the lines of:

Addressable asset A is packed separately with its dependencies in bundle a. It also depends on assets from common dependency bundle b.
Addressable asset C is packed separately with its dependencies in bundle c. It also depends on assets from common dependency bundle b.

Load assets A and C, which loads bundles a, b, and c.
Unload asset A but retain asset C.
Resources.UnloadUnusedAssets();
Reload bundle A and its dependencies fail to resolve, dropping back to null or, more confusingly, losing their type back to UnityEngine.Object, even though the bundle should not have been unloaded since its reference count is > 0.

In our case this was caused because our code was occasionally leaking asset C, which I have fixed (i.e. refactored out of existence…) but the underlying issue seems to remain that “loaded” asset bundles are basically unloaded.

1 Like

They do make note of some changes in the change log. I’ll have a look. Thanks!

Oh a new caveat I didn’t previously know (somewhat unrelated to all this). You cannot call ReleaseInstance from Awake or OnEnable. They will fail to release the ref count. It is safe from Start though!

@TreyK-47 and anyone else here interested. If you haven’t noticed Unity did fix the problem. Assets are no longer unloaded when they shouldn’t be. However, because the fix was to precompute all bundle dependencies I highly recommend anyone using them have a look at their dependencies in the event viewer. For my current project the result is that many bundles can no longer ever be unloaded because of the precomputed dependencies. You have to be even more diligent than before to ensure that your bundles are not directly connected (that is: everything must be AssetReferences).

Effectively their solution is forcing us to do exactly what I mentioned previously: you have to put AssetReference everywhere and this can be a massive undertaking. The reason the fix works, and why it is working as such (since the wording “Expanded bundle dependencies so that loaded bundles maintain a reference to all bundle they references. This fixes various bugs when unloading and reloading a bundle that is being referenced by another bundle.” is a bit misleading) is that if you point at any one thing in a bundle now it points at all things in the bundle. This causes the bundle dependency chain to grow a lot.

There are ways to manage long dependency chains without losing automatic dependency tracking by having to use AssetReference everywhere.

What we have is:
Groups with loads of assets representing individually loadable items in the game. Each is a scriptable object with loads of references to other assets. Loads of these assets have shared dependencies, sometimes several bundles deep.
We run a custom version of the Check Duplicate Bundle Dependencies rule, which extracts all common dependencies into a local and a remote dependency isolation group. These groups use a custom packing mode that groups assets by path. It all works a treat, you load the base assets and unload them as needed, and the dependency bundles are loaded and unloaded as needed, provided nothing leaks a reference. When leaking a reference things start going wrong on reloading still, I am supposed to send my repro for to Unity but I have been too busy preparing for a release.

This works much better than making everything addressable and using AssetReferences, because otherwise you have the same nightmare as putting everything in explicit bundles or resources, no real way to tracking whether your assets are used or not. If we delete one of the items from its group, all of its dependencies are stripped from our builds and there are no unused assets creeping in.

All of the customisations to addressables required are actually quite trivial, although the custom packing does require modifying stuff that is not currently customisable without embedding the package into the packages folder.

I already have a completely custom addressable bundle system that allows me to do this. I can isolate any dependencies I want with a few lines of code. However, since there are thousands of objects the number of extra isolation bundles also grows exponentially. Your solution only works to a point. At some point the isolation may as well be using the “pack separately” option on bundles. Which, by the way, fails hard due to path length limits unless you flatten a lot of your project.

As an example of how my “bundle policies” work - the resulting bundles are already equivalent to your packing policy of “pack by path”. They are explicit bundles but the results can effectively be the same.

None of my bundle groups are defined by hand but instead by a complex system of policies. Some of the grouping have to do with where those assets live, and some of them have to do with their dependencies. It’s entirely customizable. It still isn’t enough to fix this problem. Right now I’m trying to track down where these specific instances of assets are causing the chain to go so deep but it’s quite a mess to do so.

That depends on the structure of your assets. We don’t have a rats nest because of what we are actually packing. As a result the number of isolation bundles grows less than linearly. We are currently at something like 5000 individually addressable assets (not-including the isolation groups) and they are packed in about 10000 bundles.

That very much depends on what “by path” means. I extract particular sub-paths (specifically the last three fragments of the path) as the key and those also become the name of the asset bundle, solving both of those issues. None of the dependency bundles contain a single file, it’s usually whole sets of textures and models. That also solves the path length issue on Windows.

Yeah, we use a similar system running on Favo’s Addressables Importer (I wrote the RegEx based rules and address replacement part of that). I’m interested about what rules you have based on your dependencies though, there are use cases I would have for that.

Yeah, thankfully it has solved it for us. It took a 70 hour 5 day week and unholy amouns of debug logging peppered around much of Addressables and ResourceManager to figure out what was going on and another few days to rewrite the offending system, but everything is now loaded, unloaded, being collected and reloaded correctly. I really need to submit the repro for the underlying bug as it absolutely shouldn’t be causing Unity to screw up reloading assets that are in bundles that are still loaded (according to the engine itself).

@AlkisFortuneFish “packed in about 10000 bundles”

This is what I’m trying to avoid. We are currently sitting around 300. With some slight changes I can push that to about 1000 (gathering shared dependencies just between particular sets of levels). There was even a time (maybe it has passed) where having that many bundles could completely break Android due to file handle counts.

My suggested solution to Unity was to gather these interdependencies only at runtime but they chose the faster / easier route to fix the problem. I don’t particularly blame them but the side effects are huge.