Just getting started with Addressables and would like advice on how to minimise any and all impact on main thread as it’s being used with Quest2. I’m loading with LoadAssetAsync however the game stutters. Any advice and tips welcome.
The context is an inventory, and items can be loaded from Addressables on demand. The item has some physics components and a script.
What are the best practises for loading a gameobject - for example one with a physics collider/rigidbody and injecting it into the scene with as little overhead as possible?
With only 13ms available for the entire application, I find us at around 12 most of the time so ideally reducing the impact (async loading/streaming cost) to 1ms on this hardware (can take longer to load and integrate) would be mostly OK. As you can imagine any form of hiccup in VR is a complete disaster for the user experience, and this platform is low on memory.
But there just doesn’t seem to be any control over this. Please advise.
You probably know well more stuff about this than I do, but I was also noticing hitching when loading assets asynchronously using C#'s Task class (which is a bit heavy for games). From the suggestion of a friend on Discord, I started using this package which gives you a ‘UniTask’ non-allocating struct to use for async code: GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
I’ve been using addressables in the same manner (among others) as you, loading inventory icons in the UI, and meshes/materials when the player drops an item on the ground. Before using UniTask there was always a notable pause before the asset was loaded in, and post using it I’ve noticed much faster loading; or at the very least, no pause before the asset starts being streamed.
Should be easy to test and use. All I did was install the package and swap Task with UniTask in my addressables extension methods, and it was good to go.
As for best practices… the way I see most often described is async load the game object to instantiate on awake/start, and then instantiate that as necessary. In the case of different inventory items, I made a blank slate pick-up object that gets instantiated then passed one of my item scriptable objects, which it uses to load the mesh and materials of via addressables.
Though I kinda feel like I’m slinging beginner advice to the professional here. Shrug!
Missed this post, but thanks for tips, will look into UniTask. However I think in my case it may be a main thread issue, where while it is loaded async it isn’t integrated async.
I wish Unity wasn’t so black-boxed about this. The docs and general info surrounding addressables tends to avoid, well, addresssing the elephant in the room. The elephant is too big to be taken out without causing stutters.
I feel like a fool because I’m not actually sure what you’re referring to here. I’m gonna guess… how Unity is somewhat constrained to the main thread 99% of the time?
You are no fool. What is happening here is that Untiy’s coroutines by default cause allocations when used, unless you pre allocate - perfectly fine to do so for just the area of waiting for a callback to finish.
The next problem though is Unity doesn’t seem to give any control over creating meshes, gameobjects, components etc in a time-sliced manner or async manner at all. That part is brutally slammed into your main thread all in one go (as far as I can tell). Any scripts on these are disabled so that I can run my own time sliced setup as mentioned in the talk for Firewatch/Inside etc - many games time slice object setup.
Problem is that Unity does not seem to offer much control over integrating loaded things, or how long we can cap it. It just briefly mentions that the it’ll be something like 2ms if you use the lowest setting for async instantiation. That’s actually still too much for mobile VR My wiggle room is something like 2 ms already, and that’s for the regular running game
Either give up and keep everything loaded or accept the hitch.
It’s been over 5 years since PlayDead’ seminal article on how they had to fight against Unity and fix the source code to get smooth streaming on Inside, and so far seems Unity brought none of their changes into the engine.
I’ve just been using C#'s async methods to do so, augmented by UniTask for allocation free asynchronous methods.
This is the main extension method I use to load asset references of any type:
public static async UniTask<T> LoadAssetReference<T>(this AssetReferenceT<T> assetReference)
where T : UnityEngine.Object
{
if (assetReference.RuntimeKeyIsValid())
{
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(assetReference);
await handle.Task;
switch (handle.Status)
{
case AsyncOperationStatus.Succeeded:
T loadedAsset = handle.Result;
Addressables.Release(handle);
return loadedAsset;
case AsyncOperationStatus.None:
case AsyncOperationStatus.Failed:
Debug.LogError("Addressables Loading: Failed to load Asset Reference");
break;
}
}
return null;
}
And I have a few other extension methods for the other type of AssetReference (AssetReferenceSprite for example) that just return this same method.
Then you just use it in an async void or (Uni)Task like: SceneDataObject someSceneDataObject = await someSceneDataObjectReference.LoadAssetReference<SceneDataObject>(); for example.
Worth giving a shot and see if it runs smoother than Coroutines.
What does the profiler show in the frame the hitch happens? Usually the allocation for a coroutine or async takes much less time than the many things that can cause a prefab instantiation to hitch.
“Async Asset Upload” settings found in “Quality Settings”, see blog-post.
Addressables: The “Asset Bundle Compression” setting found in an Asset Group under “Advanced Settings”. LZ4 is preferred for local bundles for faster loading.
Addressables: The “Asset Bundle CRC” setting found in an Asset Group under “Advanced Settings”. No CRC checks for local bundles is often faster.
Addressables: Don’t put too much stuff in one bundle. Having more smaller bundles seems to reduce hiccups, but causes additional memory overhead.
If the collider is a MeshCollider, does it improve performance when you approximate the shape using primitive-colliders instead? I remember that integration MeshColliders was a reeeally expensive operation in Unity 5.x.