Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Feedback / request for being able to cache addressables at runtime

Discussion in 'Addressables' started by wheee09, Jul 24, 2018.

  1. wheee09

    wheee09

    Joined:
    May 21, 2018
    Posts:
    22
    Hi,

    Imagine the scenario where you have a number of "players" in the scene and these players have their own set of inventory/armour. I rather pre-emptively determine what assets I need to render the scene at runtime and then let the rendering proceed as normal without having to worry about callbacks scattered everywhere. I can then provide a nice UX experience (ie. progress bar on scene load).

    Therefore ideally I would want to cache these assets in a dictionary where the key is the addressable string/key and the value is the actual asset.

    This would also encourage the use of methods like loadAssets instead of loadAsset.

    In order to make this use case possible, I have two requests:
    1. Provide a getter for the Addressable string/key for any addressable asset (GameObject, Material, etc.) Right now, if you loop over the results from the complete callback of loadAssets, it's a collection of GameObjects with no way to access its Addressable string/key to use as the cache's key.

    2. Provide another overloaded method for loadAssets that will take in a list of strings/keys - this will allow the selective and precise loading of only the assets that we care about at runtime as opposed to everything under a label. While this can be achieved by using loadAsset, it's a bit of a pain in the ass to set this up.

    Thoughts?

    Thanks!
     
  2. unity_bill

    unity_bill

    Unity Technologies

    Joined:
    Apr 11, 2017
    Posts:
    954
    Thanks for your feedback.
    #1 is something we've considered, but there is not an easy way for us to backtrack lookup key to asset because we provide many ways to lookup an item (address, label, AssetReference). We are working on a good solution, but don't have one yet. The workflow for now is that you can either workout a way to store the assets yourself, or continue to use LoadAsset with whatever key you need. If you have already loaded the item, then subsequent LoadAsset calls will complete the next frame.
    #2 should already exist. LoadAssets can take an IList of keys. This can be used to return items that match all keys (Intersection), or items that match any (Union). Your use case would be the "any" case. The "all" case would be, for example, if you had a label for some level, as well as labels for language or seasonal theme. Load "Desert" and "Spanish" to get only the spanish version of your desert.
    Thanks,
    Bill
     
  3. wheee09

    wheee09

    Joined:
    May 21, 2018
    Posts:
    22
    Hi @unity_bill ,

    Thanks for the response!

    I didn't realize that you can pass an IList of keys to LoadAssets, that's great.

    I did want to dig deeper on what you said earlier in #1 which was: "If you have already loaded the item, then subsequent LoadAsset calls will complete the next frame."

    Are you implying that Addressables.LoadAsset has built-in caching?
    And if so, I assume that these assets will be cached until you call ReleaseAsset?

    Lastly, this is a more general question: assuming that the Asset is on file (ie. not hosted on a CDN), is it safe to say that the loading of the asset (via LoadAsset) for the first time is super fast that it can be done in "real time"? By "real time" I mean within a very small number of frames?

    I'm currently creating an Asset Cache Manager that would be responsible for pre-emptively loading and caching assets... but now based on what you had said earlier, and what you may respond with above, it's making me wonder if I should even bother.

    Thanks!
     
  4. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    456
    Hi @unity_bill, I think u can introduce something like Addressables.Preload() to preload prefab or asset into memory then calling Addressables.Instantiate() will Instantiate game object instantly without waiting and there will be something like Addressables.DisableRefCount() to pass a list of addresses to avoid those prefab or asset removed from memory when ref count reached zero. Then u will have something like Addressables.Release() to pass a list of addresses to remove them from memory. I think this will achieve caching and object pooling.
     
  5. unity_bill

    unity_bill

    Unity Technologies

    Joined:
    Apr 11, 2017
    Posts:
    954
    ok, I'll try to answer each question separately...

    built-in caching... yes, in order to load things from bundles, you must use caching as you cannot load a bundle that is already loaded. By default, both bundles and assets are cached, though you could turn off asset caching (by editing the code, we haven't exposed that yet). "Window->Asset Management->Resource Profiler" is a view we provide so you can see the status of ref-counts and when things are/should-be unloaded. And yes, when you call Load*, we up a ref count on all loaded things (meaning the main asset, and any loaded dependencies). When you call Release, we reduce the ref count. At 0, we unload. If you find yourself needing your own "Asset Cache Manager" then we have something seriously wrong with our system. You should definitely not need that.

    Speed of on disk loading... under the covers, we'll do an async load. So it will be much faster than a download, but I cannot predict how fast exactly that will be. It depends on your devices load-from-disk speeds as well as how large the asset is.

    Addressables.Preload()... To do what you say, you would actually just call LoadAsset(). As stated, caching already happens, so that call will load the asset, increment the ref-count, and have the thing in-memory & ready to go. You can the later use Addressables.Instantiate and it will instantiate the already-loaded asset. You just then need to make sure you do Addressables.ReleaseInstance for each instantiate call (or rely on our scene closure mechanisms), and then call Addressables.ReleaseAsset once you're done. We do have one preload method, Addressables.PreloadDependencies, but this does not load assets into memory. It (generally) loads the asset bundles. The primary use for this method is actually to ensure all your needed bundles are downloaded prior to when you need to load.

    -Bill
     
    optimise likes this.
  6. one_one

    one_one

    Joined:
    May 20, 2013
    Posts:
    491
    Hi, I know it's been a while but I find myself in exactly this situation. I have a ScriptableObject database and instead of direct references, I'd like it to run with asset references now. A frequent scenario is that I have a SO (database entry) and I would like to get an asset from an asset reference in there - say a sprite for previewing an item in the UI.
    I could (though begrudgingly) live with the fact that anytime I want to get an asset, I'll have to do so in a coroutine. But since Load increases the reference counter it appears not to be 'fire-and-forget' - any class I load assets with needs to babysit and release assets it has loaded. Not really something I want to be doing in any UI class that might want to display preview icons.
    So the logical step appears to be to outsource these two responsibilities to an asset cache manager which does the following:
    • Provide asset directly if already loaded
    • Internally handle the monkeying around with coroutines, using a nicer async pattern for the API
    • Internally set the reference (probably I'd have one such reference handler per-scene, so it wouldn't completely circumvent the idea of the reference counter for my use case)
    Would I be re-inventing the wheel there or is that still an area that needs improvement?
     
  7. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    This is exactly the API that should have been and is what games actually need.
    An async Addressables.Preload() / PreloadAssets() call, and then a synchronous Addressables.InstantiatePreloaded()

    This is what I've ended up implementing myself to make life easier ... however I seem to still be running into problems / debugging it.
     
    one_one likes this.
  8. unity_bill

    unity_bill

    Unity Technologies

    Joined:
    Apr 11, 2017
    Posts:
    954
    It should be fairly straightforward to create an extension method for something like Addressables.InstantiatePreloaded. The big issue is that using that system will be delicate. We've created a sync demo to help people that really want to go down that path, but as you can see in the sample, misuse causes a lot of exception throwing...

    https://github.com/Unity-Technologies/Addressables-Sample
     
  9. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    Very interesting. I see that it uses Addressables.InitializeAsync() and Addressables.LoadAssetAsync .. what is the difference between that and non Async versions? Couldn't find them in API documentation.

    Also, I don't see where assets are actually preloaded. It just seems to make sure Addressables.InitializeAsync is done?

    EDIT: OH they are just the new 0.8.4 names. In that case, I don't understand how things are preloaded, it looks like it just expects the assets to be ready for sync load.

    EDIT2: So it works because the Sync providers are being used instead? Then how does it make sure an assetbundle is downloaded first?
     
    Last edited: May 11, 2019
  10. unity_bill

    unity_bill

    Unity Technologies

    Joined:
    Apr 11, 2017
    Posts:
    954
    Sync will explode (ok, just throw an exception) if some sort of download is required. In the demo if you are calling SyncAddressables.Whatever, it has to work sync. Which downloads can't.

    as mentioned in the readme:

    If you are using it, all the existing async methods would still work, so you are capable of doing a mix & match in your game, if you are willing to accept the constraints when doing things sync. Note that the group schema is what associates a given asset group with either the sync providers or the regular ones. So you could not mix & match within a group

    So this demo is setup to make a group either by sync or not. If you wanted to be able to deal with the bundle async, but the contents sync, you could probably make the bundle provider be the regular one, and the bundled asset provider be the sync one. I think. I haven't actually tested that.

    Or you could crate a provider that could handle both scenarios, but then you have to figure out where exactly you want your error checking.
     
    chanon81 likes this.
  11. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    Actually, that sounds like the API that I'd really want to use.

    Having to load everything using async complicates my code a lot. I've tried to improve it by pre-caching stuff in an Asset cache and then being able to synchrounously return the assets.

    However, ideally, I would just make sure any asset bundles required are downloaded first before starting a level, and then be able to use sync calls for all the actual asset loading/instantiation in the game level code.

    It would be pretty simple in my case as I will have 1 asset bundle per "game world" that might need to be downloaded.

    I don't know, but I think this is the most common use case.
    While being able to load anything without worrying about managing downloads of asset bundles may be convenient, in practice you would almost always want to show a download progressbar anyways rather than have it start downloading in the middle of a level. The complexity that arises from async in game code is too much.

    For example, in my game, I can't use Start to initialize my game objects as some of them require dynamic loading of assets. If I do that, game objects will not all appear when starting a level ... they will blink into existence a bit afterwards .. which doesn't look good. So I have another "Init" call in each compenent that begins asset loading, and then I have to wait until all children components finish loading before actually SetActive(true) on the root level object.

    Then there is complexity due to some gameobjects depending on other gameobjects to finish initialization which due to async can be hard.

    I have created a framework to manage this stuff more easily ... but at some point I question myself if it is worth it.

    A simpler, less complex (and thus less chance for bugs) approach is to just make sure required asset bundles are downloaded and then just using sync calls for loading.

    So thanks for the examples. Maybe I will look into using or creating these "Providers"
     
    Last edited: May 14, 2019
    Flavelius and one_one like this.
  12. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    22
    Yes... this is what I want to do too... loadassetasync everything we need up front so it is available and then use a synchronous instantiate to pop out ref counted instances.
    How can I do this most easily?
     
  13. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    Easiest way is to have a singleton that has a PreCache method that uses LoadAssetAsync. Then make sure everything requested has finished loading before starting the level.

    The singleton should have a GetLoadedAsset that returns previously PreCached assets (and errors if the requested asset isn't preloaded). For prefabs it would be InstantiateLoadedAsset and instantiates the requested loaded prefab.

    Singleton could have a UnloadAllAssets to release all loaded assets using Addressables.Release.

    In my case instead of a Singleton, I have it as a component on the Level object and it cleans itself up whenever the Level object is destroyed.