Search Unity

Question tons of questions about asset management

Discussion in 'Asset Bundles' started by seeyou2014, Dec 23, 2020.

  1. seeyou2014

    seeyou2014

    Joined:
    Aug 19, 2019
    Posts:
    3
    I've read the document many times here: https://learn.unity.com/tutorial/assets-resources-and-assetbundles#5c7f8528edbc2a002053b5a9
    but I still have some questions:

    For most projects, this behavior is undesirable. Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated. Two common methods are:

    1.Having well-defined points during the application's lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen. This is the simpler and most common option.
    (so I should have a Dictionary that maps the resouce names to the loaded AssetBundles, because when I want to load a new AssetBundle, I must know if the new AssetBundle's dependencies are already loaded for avoiding loading dependencies duplicately, is this correct? Does addressable solve this automatically?)

    2.Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload and reload individual Objects without duplicating memory.
    (This really confuse me...
    1.How can I "Maintaining reference-counts for individual Objects"? For example, I want to load a UI prefab that have many textures, should I create a new MonoBehaviour that tell my ResourceManager "i am referecing this texture" when Start called and tell the ResourceManager "i am not referecing this texture" when OnDestroy called for every RawImage? what about another types of asset? What if other programmer forgot to add this MonoBehaviour to the RawImages? that's wired and I think it's not acceptable and easy to make mistake.)

    If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:

    1.Eliminate all references to an unwanted Object, both in the scene and in code. After this is done, call Resources.UnloadUnusedAssets.
    (this is the pattern I use in my jam, the critical class is WeakReference<T>, I've tested it simply, you can see the code below, does it have any problem?)

    2.Load a scene non-additively. This will destroy all Objects in the current scene and invoke Resources.UnloadUnusedAssets automatically.
    (Do these "Objects" only include the GameObjects in the scene view? if not, how shuold I do if I want to keep common assets I will use in any scene?)


    Code (CSharp):
    1. //just a jam, didn't use it in my company's project
    2.  
    3. //ResourceManager.cs
    4. public class ResourceManager : Singleton<ResourceManager>
    5. {
    6.     Dictionary<string, WeakReference<Object>> loadedResources = new Dictionary<string, WeakReference<Object>>();
    7.  
    8.     float tick = 0f;
    9.     public void Update() //called elsewhere
    10.     {
    11.         tick += Time.deltaTime;
    12.         if(tick >= 5f)
    13.         {
    14.             Resources.UnloadUnusedAssets();
    15.             tick = 0f;
    16.         }
    17.     }
    18.  
    19.     public Task<T> LoadAsync<T>(string name) where T: Object
    20.     {
    21.         TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
    22.  
    23.         Object existResource = null;
    24.         //check if the resouce is already loaded
    25.         if(loadedResources.ContainsKey(name) && loadedResources[name].TryGetTarget(out existResource))
    26.         {
    27.             tcs.SetResult(existResource as T);
    28.         }
    29.         else
    30.         {
    31.             var resourceRequest = Resources.LoadAsync<T>(name);//Can be replaced with AssetBundle
    32.             resourceRequest.completed += (ao) => {
    33.                 if (loadedResources.ContainsKey(name))
    34.                 {
    35.                     var wr = loadedResources[name];
    36.                     wr.SetTarget(resourceRequest.asset);
    37.                 }
    38.                 else
    39.                 {
    40.                     var wr = new WeakReference<Object>(resourceRequest.asset);
    41.                     loadedResources.Add(name, wr);
    42.                 }
    43.  
    44.                 tcs.SetResult(resourceRequest.asset as T);
    45.             };
    46.         }
    47.  
    48.         return tcs.Task;
    49.     }
    50. }
    51.  
    52. //MonsterManager.cs
    53. public class MonsterManager : Singleton<ResourceManager>
    54. {
    55.     //maintain the references of monster models that current level may use
    56.     //because I don't want the Resources.UnloadUnusedAssets to unload these monsters
    57.     Dictionary<string, GameObject> templates = new Dictionary<string, GameObject>();
    58.    
    59.     public async void OnNewLevelLoaded()
    60.     {
    61.         //eliminate all reference, Resources.UnloadUnusedAssets can unload these monster templates
    62.         templates.Clear();
    63.  
    64.         foreach(var monsterName in monsterNamesFromSomewhere)
    65.         {
    66.             GameObject monsterTemplate = await ResourceManager.Instance.LoadAsync<GameObject>("monster");
    67.             templates.Add(monsterName, monsterTemplate);
    68.         }
    69.     }
    70.  
    71.     public GameObject CreateNew(string monsterName)
    72.     {
    73.         GameObject go;
    74.         if(templates.TryGetValue(monsterName, out go))
    75.         {
    76.             return GameObject.Instantiate(go);
    77.         }
    78.  
    79.         return null;
    80.     }
    81. }
    again, does this have any problem with asset management(never mind async/await and the way(Resources.LoadAsync) I load assets here)?
    finally, is this document already obsolete? should I use Addressables instead of AssetBundle currently?
    thank for answering, and please use simpler words, my english sucks :)
     
  2. nilsdr

    nilsdr

    Joined:
    Oct 24, 2017
    Posts:
    374
    1.Having well-defined points during the application's lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen. This is the simpler and most common option.
    (so I should have a Dictionary that maps the resouce names to the loaded AssetBundles, because when I want to load a new AssetBundle, I must know if the new AssetBundle's dependencies are already loaded for avoiding loading dependencies duplicately, is this correct? Does addressable solve this automatically?)


    This is indeed a common and practical use case. The way we do this is to think in terms of groups of content (we call them contentroots) that should be downloaded, loaded and in memory concurrently at a given moment because they depend on each other (cute). Each root has a 'main asset' that can be loaded from it, most of the time a scene or a prefab. We then store recursively the names and hashes of the assetbundles needed for this asset (scene needs material => material needs texture etc)

    We then do exactly as you suggested, we load and unload these interdependent groups of content at well-defined points (scene loads, for example). In this simple setup you probably wouldn't have to maintain reference counts for bundles. However, there is another common usecase; bundles that are dependencies of multiple 'groups'. If this is something you want to be able to facilitate, you could store a referencecount for each bundle and only unload it if on loaded ' group of content ' references it.

    2.Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload and reload individual Objects without duplicating memory.
    (This really confuse me...
    1.How can I "Maintaining reference-counts for individual Objects"? For example, I want to load a UI prefab that have many textures, should I create a new MonoBehaviour that tell my ResourceManager "i am referecing this texture" when Start called and tell the ResourceManager "i am not referecing this texture" when OnDestroy called for every RawImage? what about another types of asset? What if other programmer forgot to add this MonoBehaviour to the RawImages? that's wired and I think it's not acceptable and easy to make mistake.)


    As described in my annswer to question 1, i think you'll want to store referencounts for bundles, not individual assets..

    finally, is this document already obsolete? should I use Addressables instead of AssetBundle currently?


    AssetBundles aren't obsolote, they are at the core of Addressables. Addressables is a more high level system which tries to facilitatie common work flows. If your needs match these workflows, addressables could definately make your life a lot easier.

    If you want/need a more granular low-level in control kind of setup, create your own flow for building assetbundles and managing memory. Scriptable build pipeline would be your starting point, this is also what addressables uses to build its bundles i believe.

    https://docs.unity3d.com/Packages/com.unity.scriptablebuildpipeline@1.15/manual/index.html
     
  3. seeyou2014

    seeyou2014

    Joined:
    Aug 19, 2019
    Posts:
    3
    Thanks very much
     
    nilsdr likes this.