Addressable data causing errors on player build

Hello all, I’ve implemented addressables for the first time trying to improve my project’s performance. At editor level, everything is works, but the build version does not, apparently all addressable data is not being loaded correctly, causing common errors like NullPointerException and the likes…

Already lost hours searching for an answer and learn about addressables’ build settings and stuff like that (I mean about it’s existence).

Using the following settings on Editor the application work as expected:

Play mode script set to “Asset database (fastest)”
Build-clean build → All
new build → default

The same error observed on build version is observed on Editor following these steps:

Play mode script set to “Use existing build (Windows)”
Build-clean build → All
new build → default

Tested both “Build addressables content on player build” and “Use global settings (stored in preferences)” at the AddressableAssetSettings file, nothing changes and the errors still happening on the player build.

Not sure where should I go from there, I mean, why does it work on Editor and doesn’t on build in the first place…

Please help!

Target platform - PC
Unity version - 2021.3.16
Addressables package - 1.19.19

Bump, I really need this one, been stuck researching elsewhere and waiting for an answer or something!

What errors are you getting? Can you post the log file? The first error is likely the problem and the rest are just symptoms of that Unity - Manual: Log files

Here it is, logs from both player build and editor. I don’t know very well how to produce “clean” logs for you, but considering I only work on this project, all data is pertinent to it.

The screenshot is what happens inside the editor if I enter in play mode with the Play mode script > Use existent build(Windows) selected. If I try to run any tasks linked with addressables, the editor produce the same set of errors observed in the player build…

8991583–1238104–Editor.txt (58.9 KB)
8991583–1238107–Player.txt (10.8 KB)

I cant see any errors related to Addressables, You have a null reference coming from a button click callback, likely due to the missing components.
Did you delete some script files?
The referenced script on this Behaviour (Game Object '<null>') is missing!

The thing is, on editor with the standard settings (Play mode script > Use asset database (fastest)), the application works.

That’s the root of my doubt actually, I can’t figure out why it works on editor while it does not on build. I mean, what should I do to correct this considering it suppose to be working like it is on editor…?

I attached another image to illustrate all data correct assigned without any errors/warnings on console using the mentioned default settings.

8991700--1238149--ss2.PNG

Are these all the same script? Can you share the script?
How are they being loaded from Addressables? Is it the whole scene, or single GameObjects/Assets? What does the code look like? Can you reproduce the issues in a simple project and share it?

Yes, I’m trying to keep all addressables’ load calls in the same class.

I’ll post a method of this class below, all the other addressables’ calls are similar or even simpler than this one. They were based on the Code Monkey’s videos on the subject as well as other examples found here or in the documentation.

I’m loading a bunch of SOs and sorting them accordingly to what’s selected in the UI, otherwise I must keep hundreds of assets loaded all the time which isn’t viable considering the scale of my project.

I’ll try to reproduce the issue in a example project and post it here if I manage to do so.

Here’s the example method:

void LoadTeamHeaders()
    {
        teamHeaders = new List<TeamHeader>();
        var searchLabels = new List<string>()
        {
            "Team",
            loadedLeagueHeader.country,
            loadedLeagueHeader.levelOnPyramid.ToString()
        };

        var teamDataLocation = Addressables.LoadResourceLocationsAsync(
            searchLabels, Addressables.MergeMode.Intersection);

        Addressables.LoadAssetsAsync<Team>(teamDataLocation.Result, loadedTeam =>
        {
            TeamHeader teamHeader;
            teamHeader.title = loadedTeam.title;
            teamHeader.country = loadedLeagueHeader.country;
            teamHeader.levelOnPyramid = loadedLeagueHeader.levelOnPyramid;
            teamHeaders.Add(teamHeader);
        }).Completed += asyncHandle =>
        {
            /*if (!asyncHandle.IsDone)
                Maybe a log informing that teams are loading?*/
            if (asyncHandle.IsDone)
            {
                Addressables.Release(asyncHandle);
                if (teamHeaders.Count == 0)
                    throw new Exception("No team data match was found for " + loadedLeagueHeader.title);
                else if(teamHeaders.Count>1)
                    teamHeaders.Sort((s1, s2) => s1.title.CompareTo(s2.title));
                onTeamsHeaderLoaded(teamHeaders);
            }
        };
    }

Thanks for the attention!

I think I see the problem. LoadResourceLocationsAsync is asynchronous, it won’t always be completed immediately. In your code you dont take this into account and do Addressables.LoadAssetsAsync<Team>(teamDataLocation.Result, Result could still be null at this point.

void LoadTeamHeaders()
{
    teamHeaders = new List<TeamHeader>();
    var searchLabels = new List<string>()
    {
        "Team",
        loadedLeagueHeader.country,
        loadedLeagueHeader.levelOnPyramid.ToString()
    };

    var teamDataLocation = Addressables.LoadResourceLocationsAsync(
        searchLabels, Addressables.MergeMode.Intersection);

    // Karl: We need to wait for teamDataLocation to complete, it is async and will not always be completed.
    Addressables.LoadAssetsAsync<Team>(teamDataLocation.Result, loadedTeam =>
    {
        TeamHeader teamHeader;
        teamHeader.title = loadedTeam.title;
        teamHeader.country = loadedLeagueHeader.country;
        teamHeader.levelOnPyramid = loadedLeagueHeader.levelOnPyramid;
        teamHeaders.Add(teamHeader);
    }).Completed += asyncHandle =>
    {
        /*if (!asyncHandle.IsDone)
            Maybe a log informing that teams are loading?*/
        if (asyncHandle.IsDone) // Karl: This should always be true as you are inside of the Completed callback
        {
            Addressables.Release(asyncHandle);
            if (teamHeaders.Count == 0)
                throw new Exception("No team data match was found for " + loadedLeagueHeader.title);
            else if(teamHeaders.Count>1)
                teamHeaders.Sort((s1, s2) => s1.title.CompareTo(s2.title));
            onTeamsHeaderLoaded(teamHeaders);
        }
    };
}

Maybe try turning the LoadTeamHeaders into a coroutine and do something like this:

IEnumerator LoadTeamHeaders()
{
    teamHeaders = new List<TeamHeader>();
    var searchLabels = new List<string>()
    {
        "Team",
        loadedLeagueHeader.country,
        loadedLeagueHeader.levelOnPyramid.ToString()
    };

    var teamDataLocation = Addressables.LoadResourceLocationsAsync(searchLabels, Addressables.MergeMode.Intersection);

    yield return teamDataLocation;

    var assetsOperation = Addressables.LoadAssetsAsync<Team>(teamDataLocation.Result, null);

    yield return assetsOperation;

    if (assetsOperation.Result.Count == 0)
        throw new Exception("No team data match was found for " + loadedLeagueHeader.title);

    foreach (var loadedTeam in assetsOperation.Result)
    {
        TeamHeader teamHeader;
        teamHeader.title = loadedTeam.title;
        teamHeader.country = loadedLeagueHeader.country;
        teamHeader.levelOnPyramid = loadedLeagueHeader.levelOnPyramid;
        teamHeaders.Add(teamHeader);
    }
  
    Addressables.Release(assetsOperation);
    Addressables.Release(teamDataLocation);

    teamHeaders.Sort((s1, s2) => s1.title.CompareTo(s2.title));
    onTeamsHeaderLoaded(teamHeaders);
}

Well, I can try to change things to test, it happens that I managed to create a empty project to test stuff and it worked!

Followed the same structure as the real project, meaning I just loaded stuff and send the result using a “Action<> event”. All tests on editor and build worked, I don’t know, it seems I messed with some addressables’ settings by accident.

Is it possible for me to start with a clean slate regarding addressables? I mean, clean everything and reimport the package or something. I believe this would be easier for me, so I could create my groups and tags again from scratch to test, as I did in the test project…

I attached the test scene if you wish to take a look, created with a 2D project to be simple as possible.

PS: Maybe it will require the library “Mono.Cecil.Cil” to be commented, cause it pointed an error after importation on my test.

8993896–1238548–AddressablesTest.unitypackage (10.6 KB)

So, I tried to wipe off the addressables from the project (removed the package and the AddressableAssetsData folder, let me know if anything else must be removed in order to actually “clean” everything), then I re-add everything from scratch, the package, the groups, the tags… Even so, the problem remains :frowning:

Strangely, the problem is not that the data is null when my class runs, meaning it does load and assign everything correctly (confirmed with Debug.log on both editor and build), but when other classes try to access that data, it isn’t there anymore, meaning it seems to be some kind of persistence issue…

There’s really nothing to clean in addressables, that’s not the problem. Remove the addressables release calls. You could be unloading the data through them.
Release will unload the asset which would make it go null, this would only happen when using asset bundles, so fast mode would not have this issue.

Ok, I will try to remove it, but what does the async handle actually holds persistently? I mean, I have thousands of assets to be filtered, from those, dozens must be keep according to certain criteria, so if it holds all that information at the same time, not releasing it would kill the point of use addressables in the first place.

I’m releasing the data cause I thought this was the reason why addressables improve efficiency, but if it holds one object instance each time, not release the async handles will solve my problem.

I also noticed my previous example was wrong, cause it didn’t reflected the way I’m doing things in my main project, so I attached an updated version if it’s serve of anything, considering you seems to have found the problem already…

8998384–1239508–AddressablesTest.unitypackage (1.28 MB)

Addressables follows a reference counting concept.
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/runtime/AddressableAssetsAsyncOperationHandle.html#releasing-asyncoperationhandle-instances

When you call Release, an internal reference counter is decremented. The AsyncOperationHandle holds a reference to the AsyncOperation, when this operations reference count reaches 0 it is then released, any other operations that depend on it also have their reference released which can then cause them to release. When an asset operation is released then the asset that was loaded is Destroyed, when the asset bundle operation is released then the asset bundle is unloaded.

When using Addressables you want to hold onto the AsyncOperationHandle whilst using the asset, when finished you release it. If you are using the asset in multiple places then you can call Acquire which will increment the internal counter so that the asset will only be destroyed when all users have called Release on it.
There are some examples here to show correct usage https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/runtime/RuntimeAddressables.html