Search Unity

Assetbundle caching and load from file

Discussion in 'Asset Bundles' started by Roryyyyyyyyyy, Sep 14, 2018.

  1. Roryyyyyyyyyy

    Roryyyyyyyyyy

    Joined:
    Jun 8, 2015
    Posts:
    22
    Hi all,
    I am trying to work out a system for downloading when the game is loaded up. I am currently using UnityWebRequest.GetAssetBundle (I am using 2017.4) to cache the assetbundle in the system. I have an asset manager that tracks everything loaded, but I'm wondering which way would be better to handle the bundles. Am I right in thinking I cannot use AssetBundle.LoadFromFile if I have downloaded it from the cache?

    Our current process is that the app gets the bundle crc from the server which replaces the bundle in the cache if so with the UnityWebRequest.GetAssetBundle. It doesn't store the bundle anywhere, it just caches it.

    How is the best way to get the bundles after this? E.g. I want to load a building model, I will want to load the building bundle from the cache, get the asset, store it in the manager then unload the bundle. Would it be best just to use the web request each time?

    Would anyone recommend me doing it a different way or is this a reasonable approach?
     
  2. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    One I thing I suggest you to do is to figure out what you need exactly and see if you aren't reinventing the wheel.
    UnityWebRequest.GetAssetBundle() (the overloads with CRC/version arguments) is loading AssetBundle from cache, if given AssetBundle with given crc/version/hash is in the cache, otherwise it downloads and caches it for future use. As such you just do UnityWebRequest.GetAssetBundle() and then take the bundle via DownloadHandlerAssetBundle.GetContent(), this way you don't need to bother, whether tahe bundle was downloaded or loaded from the cache.

    If you are looking into using AssetBundle.LoadFromFile, the you shouldn't use UnityWebRequest.GetAssetBundle, you need to assign DownloadHandlerFile instead to bypass AssetBundle system at download time. But by doing so you also lose the builtin caching etc. and reinvent them yourself. So it sounds you should first make sure the builtin caching/loading is not enough for you.
     
    MattJackB and MaryamKamel like this.
  3. Roryyyyyyyyyy

    Roryyyyyyyyyy

    Joined:
    Jun 8, 2015
    Posts:
    22
    Ah okay, so the only real difference would be I would need to create a system for caching it manually? I guess the UWR method would be much better for me then. In terms or memory usage is there much in it between UWR and LoadFromFile?
     
  4. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    UWR caching stores AssetBundle on disk decompressed, so the load will be faster. In terms of memory usage there should be no significant difference between the two, only that in case of LoadFromFile you'd have to download and store the bundle first and ensure efficient memory usage during download (such as DownloadHandlerFile).
     
  5. Roryyyyyyyyyy

    Roryyyyyyyyyy

    Joined:
    Jun 8, 2015
    Posts:
    22
    That makes sense, thanks for the information! I already have UWR quite heavily implemented so it seems it makes sense to just stick with that. Cheers!
     
  6. SketchWork

    SketchWork

    Joined:
    Jul 6, 2012
    Posts:
    254
    @Aurimas-Cernius What about if the app is offline with no internet - UnityWebRequest would fail because there is no internet, but will it still download from the local cache if it exists? having a problem with it when offline on Android.
     
  7. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    If AssetBundle is cached, UnityWebRequest should loading without even attempting to download anything. It should only go to internet if the bundle is not cached or if cached bundle is of different version/hash/crc than requested.
     
    MattJackB, tgrotte and MaryamKamel like this.
  8. stgs73

    stgs73

    Joined:
    Mar 23, 2015
    Posts:
    59
    Hi Aurimas,
    We implemented the assetbundle download via a custom DownloadHandlerScript - this works fine aside from being a bit slow.

    All our assetbundles are LZ4 which is fine once on disk but we'd like to take advantage of the extra compression for download and decompress as it is downloading rather than at the end.

    The options we see are:
    1) Gzip the LZ4 and decompress ourselves - works but is clunky, bit slow compression still not as good as LZMA + compression occurs after entire download.
    2) Build assetbundles as LZMA and use the GetAssetBundle to decompress straight to disk

    2) is a problem as there is no way to pipe the result to a particular file/folder

    Is there anyway to get the benefits of Unity AssetBundle decompression and output the file somewhere?

    If not, is 1) our only solution?

    thanks
     
  9. unity_0fmczLs2vNuMTQ

    unity_0fmczLs2vNuMTQ

    Joined:
    Jun 6, 2018
    Posts:
    8
    Hello, I don't understant one thing. How UnityWebRequest understands if bundle is cached or not? If I use version parameter, does it just use Caching.currentCacheForWriting's version and compares it to the one I specified in UnityWebRequestAssetBundle.GetAssetBundle() or what? For example if I have two bundles - bundle1 and bundle2. After first laucng they were downloaded and cached in AssetBundlesCache/bundle1, AssetBundlesCache/bundle2 acordingly. Now I restart my game with no internet connection. Usually I try to load bundle on server response. It sends me uri and version. I don't have connection, so can I just pass empty uri and version of the current cache to UnityWebRequestAssetBundle.GetAssetBundle() and specify currentCacheForWriting accordingly for each bundle? Also I don't know how to get version from the cache. Caching.GetVersionFromCache() is obsolete.
     
    Last edited: Nov 1, 2018
  10. unity_0fmczLs2vNuMTQ

    unity_0fmczLs2vNuMTQ

    Joined:
    Jun 6, 2018
    Posts:
    8
    Also I checked if UnityWebRequestAssetBundle.GetAssetBundle() throws an exception with no Internet. It doesn't. But the problem is DownloadHandlerAssetBundle.GetContent() throws InvalidOperationException: Cannot resolve destination host. So I can't load bundle from cache.
     
  11. unity_0fmczLs2vNuMTQ

    unity_0fmczLs2vNuMTQ

    Joined:
    Jun 6, 2018
    Posts:
    8
    Another thing I tried was to directly assign downloadHandler property of my UWR with DownloadHandlerAssetBundle object. This time there was no error but assetBundle itself was null. Why is it so hard to just load bundle from cache?! Looks like I'd have to use AssetBundle.LoadFromFileAsync() for this.
     
  12. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870

    When I load asset bundle with a terrain, the terrain doesnt show up, player falls thru the floor ending the game immediately.

    Please see this post,

    https://forum.unity.com/threads/terrain-doesnt-appear-when-loading-from-asset-bundle.578254/

    Others having same problem

    https://forum.unity.com/threads/bitfieldinsert-and-shader-error.548491/

    https://answers.unity.com/questions...n-fou.html?childToView=1568496#answer-1568496
     
    Last edited: Nov 3, 2018
  13. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    But why do you want to save bundles on disk? When you use GetAssetBundle() with additional parameters, it will use caching system and will avoid downloading an already downloaded bundle.
    As for additional compression, first of all make sure it does reduce the size significantly. If using GZip does benefit you, first thing to do is to check the request headers and configure server to use gzip if cleant accepts it. The gzip compression will work out of the box if device has builtin support for it.
     
    Goonworker and MaryamKamel like this.
  14. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Cached asset bundles are identified by name, version, crc and hash. For UnityWebRequest name is taken from url and you have to pass at least one of other three for the cache to be used. If you pass only url, it will always download bundle from the internet. Passing the same url and the same crc/hash/version like previously should load asset bundle from cache (if it is cached and you haven't cleared it) and this should work without internet connection.
     
    MaryamKamel likes this.
  15. unity_0fmczLs2vNuMTQ

    unity_0fmczLs2vNuMTQ

    Joined:
    Jun 6, 2018
    Posts:
    8
    Oh, OK. So in case I get URL from the server I have to save it somewhere to be able to pass it even without connection. I decided to load it frrom cache folder with the use of LoadFromFile functionality.
     
  16. stgs73

    stgs73

    Joined:
    Mar 23, 2015
    Posts:
    59
    Hi Aurimas, we wrote our own caching system based on prior experiences with earlier versions of Unity caching - our understanding was that there is no guarantee the cache is available or enough space is available to cache, the side result is it loads into memory instead or ejects out other assetbundles in the cache? Is that still the case? We do all our checks up front and ensure we have space and decompress into our persistentDataFolder so we put up disk space requirements etc etc - it all works fine.
    Unless things have changed then we could switch if you feel there are no issues now.

    As for gzip and header re: yes we already did this, we were just wondering if it was possible to just utilize the decompression from LZMA -> LZ4 and stream that into our location that was all.

    best
     
    Energy0124 likes this.
  17. TomNCatz

    TomNCatz

    Joined:
    Jan 6, 2015
    Posts:
    24
    Ok, so that all sounds good and well, but it does not actually play out that way.

    In my case I want the bundles to update when they reach the server if the server file has changed, good so far.
    Then if it doesn't reach the server I want them to use the locally cached files, so I need to use the matching hash from the last downloaded version. Accept the manifest files with those hashes come from the server and don't make it into cache, and the AssetBundleManifest file does not even seem to get a hash.

    I tried taking a step back and tracking an internal version number that I update whenever I get a positive read from the server, but the version number based UnityWebRequest.GetAssetBundle does not seem to pull correctly from the cache. Even checking the cache manually for version numbers seems to have been marked obsolete. There also does not seem to be any methodology for looking up the bundles in cache manually and pulling them out directly.

    I have found a somewhat Frankenstein solution, but while it works, it has a lot of potential for failure cases. Until there is a built in Unity solution, here you go.

    AssetBundle.manifest doesn't contain a hash, but it does contain a CRC value, so if you download a manifest file that has a CRC, but no hash, you know you have a good line on a fresh version of the AssetBundle file. Clear all cached versions of the assetBundle file and then download a new one from the same source.
    Code (CSharp):
    1. string[] lines = manifest.text.Split('\n');
    2.  
    3. if( lines[1].Contains( "CRC:" ) )
    4. {
    5.     Log.LogL( "Bundle {1} CRC: {0}",lines[1].Substring( 5, lines[1].Length - 5 ), config.name );
    6.     uint.TryParse( lines[1].Substring( 5, lines[1].Length - 5 ), out config.crc );
    7. }
    8.  
    9. config.hash = default(Hash128);
    10. if( lines[5].Contains( "Hash:" ) )
    11. {
    12.     Log.LogL( "Bundle {1} Hash: {0}",lines[5].Split (':') [1].Trim (), config.name );
    13.     config.hash = Hash128.Parse(lines[5].Split (':') [1].Trim ());
    14. }
    15. else if(config.crc != 0)
    16. {
    17.     // new manifest bundle found
    18.     Caching.ClearAllCachedVersions( config.name );
    19. }
    Unity won't cache the bundle unless you give it a version number or a version hash, so you have to make one up when asking for it. At the same time, you need to know what version it was next time in case you can't hit the server. This is why I clear the cache when I am about to get a fresh version and use 0 as the version number. This means that the assetbundle file always has to be downloaded, even if it is current, but as far as I can tell there is not a way to find out what the hash should be for it. Oh, did I mention that version numbers do not update in the manifest files either?

    Anyway, after I've gotten a CRC and a hash, just a CRC, or just nothing at all, I now can try to download the actual bundle file. This logic could probably be simplified if I handled the main assetbundle file through a different flow, but they are largely the same logic outside of this point.

    Code (CSharp):
    1. UnityWebRequest download;
    2. if( config.hash == default(Hash128) )
    3. {
    4.     if( config.crc == 0 )
    5.     {
    6.         // file not found
    7.         List<Hash128> listing = new List<Hash128>();
    8.         Caching.GetCachedVersions( config.name,listing );
    9.         if( listing.Count > 0 )
    10.         {
    11.             // No manifest, but previous versions were cached
    12.             download = UnityWebRequest.GetAssetBundle( Path.Combine( config.path, config.name ), listing[listing.Count-1], config.crc );
    13.             Log.LogL( "{0} did not have a hash key, attempting to use most recent version {1}",
    14.                 config.name,
    15.                 listing[listing.Count - 1] );
    16.         }
    17.         else
    18.         {
    19.             // No manifest, no cache, trying to get the bundle anyway just in case
    20.             download = UnityWebRequest.GetAssetBundle( Path.Combine( config.path, config.name ), config.crc );
    21.             Log.LogL( "{0} did not have a hash key and no previous versions found", config.name );
    22.         }
    23.     }
    24.     else
    25.     {
    26.         // was manifest bundle
    27.         download = UnityWebRequest.GetAssetBundle( Path.Combine( config.path, config.name ),
    28.             0,
    29.             config.crc );
    30.     }
    31. }
    32. else
    33. {
    34.     // have everything we need, just grab the file
    35.     download = UnityWebRequest.GetAssetBundle( Path.Combine( config.path, config.name ), config.hash, config.crc );
    36.     Log.LogL( "{0} is cached : {1}", config.name, Caching.IsVersionCached( download.url, config.hash ) );
    37. }
    38.            
    39. download.SendWebRequest();
    Sorry that this bit is such a mess, I just got it working really. I'm going to walk through it backwards because that is actually more the order of case regularity.
    The last else is processing a case where you have a good hash. This means it is a normal asset bundle and we got the manifest file alright. Basically, this is the call just working as intended.
    Next to last case we have no hash, but we do have a CRC. This is a properly loaded manifest file for a AssetBundleManifest bundle. Here we set the version number to zero and check the CRC. Still mostly normal.
    Above that we have no CRC or hash, plus we didn't have a version in cache. This is the worst case scenario and there isn't really anything Unity can do to help you here. I try downloading the file anyway, just for giggles. In truth, I have logic elsewhere that if the download fails tries to pull it from streaming assets instead, but that's not really relevant here.
    Finally we start getting to the workaround, sorry about that. I pull down a list of all cached version hashes for this bundle. We can't get a list of the actual bundles, the version numbers, or the bundles themselves, but we can get this list. If there are items in the list, we have cached versions to work with. This list is in order from oldest downloaded to newest, so I grab the last hash in the list and use it as the download hash, effectively loading that version from cache. As a note, it looks like I pass it a CRC, but it's really zero so I'm basically just ignoring it.

    This all works, but it still has a gaping hole. Lets say you have character sprites in an asset bundle, and you want to have them wear Santa hats for a Christmas event. You upload a new asset bundle for Christmas, and then afterwords you switch back to the old bundle. Here in we have a problem. the list is in order of oldest to newest DOWNLOADED, not used. This means that the Christmas event bundle is the last in the list. So any user who played before and through Christmas will have the Christmas event bundle when they are offline even after the event has ended and they've received a more recent bundle.

    You can work around this by manually clearing items out of the list when you get new ones, forcing the order of cached items, or a few other methods, but honestly? It's a mess.

    The point is, this feature doesn't quite let you do what it says on the box, and the unity API documentation on it is both out of date AND marks it as obsolete. Hopefully this helps someone else to not spend their whole weekend combing through documentation and forums like I did.
     
    FoxsterDev likes this.
  18. TomNCatz

    TomNCatz

    Joined:
    Jan 6, 2015
    Posts:
    24
    Oh, uh...you can replace any of the "Log.LogL" bits with "Debug.LogFormat" if you want to use this code.
     
  19. AustynPember

    AustynPember

    Joined:
    Mar 14, 2018
    Posts:
    17
    Thanks for showing all that you did. This is what I am having to use because Addressable Assets are not mature enough to do this type of thing as I've found and been told.
    Could you help me understand a bit about how you are implementing this? What the code outside of this snippet looks like?
    For example - what the config looks like, and the manifest - and how these things are declared and called?

    It seems like this is set up to just download one asset bundle, but do you have this in a loop to grab many which are declared in the AssetBundle.manifest?
     
  20. TomNCatz

    TomNCatz

    Joined:
    Jan 6, 2015
    Posts:
    24
    I am so sorry, I didn't see this for a while. You likely aren't looking anymore, but for anyone who is I can post this. It is probably not the best solution, but it is the one I've been using.


    Code (CSharp):
    1.     /// <summary>
    2.     /// Facilitates loading, caching, downloading, and managing AssetBundles
    3.     /// </summary>
    4.     public static class AssetBundleManager
    5.     {
    6.         /// <summary>
    7.         /// check this value once a <see cref="DownloadAllWithManifest"/> has been started to see how close it is to finishing
    8.         /// </summary>
    9.         public static float Progress => m_total == 0 ? -1 : (float)m_done / m_total;
    10.  
    11.         /// <summary>
    12.         /// check this value once a <see cref="DownloadAllWithManifest"/> has been started to see if it has finished
    13.         /// </summary>
    14.         public static bool IsDone => m_total > 0 && m_done == m_total;
    15.  
    16.         private static readonly Dictionary<string,AssetConfig> m_configs = new Dictionary<string, AssetConfig>();
    17.         private static int m_done;
    18.         private static int m_total;
    19.  
    20.         /// <summary>
    21.         /// Retrieves and <see cref="AssetBundle"/> that has already been loaded into memory
    22.         /// </summary>
    23.         /// <param name="bundleName">name of the <see cref="AssetBundle"/></param>
    24.         /// <returns>null if the bundle is not currently loaded into memory</returns>
    25.         public static AssetBundle GetBundle( string bundleName)
    26.         {
    27.             if( !m_configs.ContainsKey( bundleName ) )
    28.             {
    29.                 Log.Warn( "Bundle {0} not loading", bundleName );
    30.                 return null;
    31.             }
    32.  
    33.             AssetConfig config = m_configs[bundleName];
    34.             if( config.failed )
    35.             {
    36.                 Log.Warn( "Bundle {0} Failed to load", bundleName );
    37.                 return null;
    38.             }
    39.  
    40.             if( !config.succeeded )
    41.             {
    42.                 Log.Warn( "Bundle {0} is still loading", bundleName );
    43.                 return null;
    44.             }
    45.            
    46.             if( !config.isLoaded )
    47.             {
    48.                 Log.Warn( "Bundle {0} has been unloaded", bundleName );
    49.                 return null;
    50.             }
    51.  
    52.             return config.bundle;
    53.         }
    54.  
    55.         /// <summary>
    56.         /// Tries to get an asset from a given <see cref="AssetBundle"/>
    57.         /// </summary>
    58.         /// <returns>null if not found</returns>
    59.         public static T GetAsset<T>( this AssetBundle bundle, string assetName ) where T : UnityEngine.Object
    60.         {
    61.             if( bundle == null )
    62.             {
    63.                 Log.Warn( "Bundle is null" );
    64.                 return null;
    65.             }
    66.  
    67.             if( !bundle.Contains( assetName ) )
    68.             {
    69.                 Log.Warn( "Bundle {0} does not contain asset {1}", bundle.name, assetName );
    70.                 return null;
    71.             }
    72.  
    73.             return bundle.LoadAsset<T>( assetName );
    74.         }
    75.  
    76.         /// <summary>
    77.         /// Tries to get an asset from the named <see cref="AssetBundle"/> if it is loaded into memory
    78.         /// </summary>
    79.         /// <returns>null if not found</returns>
    80.         public static T GetAsset<T>( string bundleName, string assetName ) where T : UnityEngine.Object
    81.         {
    82.             AssetBundle bundle = GetBundle( bundleName );
    83.  
    84.             return bundle.GetAsset<T>( assetName );
    85.         }
    86.  
    87.         /// <summary>
    88.         /// Unloads the bundle from memory and marks it as having been unloaded
    89.         /// </summary>
    90.         /// <param name="unloadAllLoadedObjects">should references to data in this bundle be cleared when it unloads</param>
    91.         public static void UnloadAssetBundle(string bundleName, bool unloadAllLoadedObjects = true)
    92.         {
    93.             AssetBundle bundle = GetBundle(bundleName);
    94.  
    95.             if( bundle == null ) return;
    96.  
    97.             m_configs[bundleName].isLoaded = false;
    98.            
    99.             bundle.Unload( unloadAllLoadedObjects );
    100.         }
    101.  
    102.         /// <summary>
    103.         /// Get all bundle names and their current loaded state
    104.         /// </summary>
    105.         public static Dictionary<string, bool> GetBundleActivity()
    106.         {
    107.             Dictionary<string,bool> bundles = new Dictionary<string, bool>();
    108.            
    109.             foreach( AssetConfig config in m_configs.Values )
    110.             {
    111.                 bundles[config.name] = config.isLoaded;
    112.             }
    113.  
    114.             return bundles;
    115.         }
    116.  
    117.         /// <summary>
    118.         /// Quick loads an unloaded <see cref="AssetBundle"/> back into memory. also can be used to try and restart a failed load.
    119.         /// </summary>
    120.         /// <param name="bundleName">name of the bundle including variant</param>
    121.         /// <param name="onComplete">called if the load succeeds</param>
    122.         /// <param name="onFailure">called if the load fails</param>
    123.         public static void ReloadAssetBundle(string bundleName, Action<AssetBundle> onComplete = null, Action onFailure = null )
    124.         {
    125.             if( !m_configs.ContainsKey( bundleName ) )
    126.             {
    127.                 onFailure?.Invoke();
    128.                 return;
    129.             }
    130.  
    131.             AssetConfig config = m_configs[bundleName];
    132.             if( config.isLoaded )
    133.             {
    134.                 Log.LogL( "Bundle {0} Already Loaded", bundleName );
    135.                 onComplete?.Invoke( config.bundle );
    136.                 return;
    137.             }
    138.            
    139.             config.onComplete += onComplete;
    140.             config.onFailure += onFailure;
    141.            
    142.             config.failed = false;
    143.             config.succeeded = false;
    144.  
    145.  
    146.             if( File.Exists( Path.Combine( config.path, bundleName ) ) )
    147.             {
    148.                 Log.LogL( "Bundle {0} is local", bundleName );
    149.                 LoadFromFile( config );
    150.                 return;
    151.             }
    152.  
    153.            
    154.             Log.LogL( "Downloading bundle {0}", bundleName );
    155.             Tool.StartCoroutine( Download( config ) );
    156.         }
    157.  
    158.         /// <summary>
    159.         /// Tries to load an individual <see cref="AssetBundle"/> into memory. If the bundle needs to be downloaded to cache first it does this as well.
    160.         /// Will try to load a manifest file first and validate the data if it finds one.
    161.         /// </summary>
    162.         /// <param name="path">URL where the bundle is located</param>
    163.         /// <param name="bundleName">name of the bundle including variant</param>
    164.         /// <param name="onComplete">called if the load succeeds</param>
    165.         /// <param name="onFailure">called if the load fails</param>
    166.         /// <param name="restartIfFailed">if this bundle has already started loading, but failed, should it try again now?</param>
    167.         public static void LoadAssetBundle(string path, string bundleName, Action<AssetBundle> onComplete = null, Action onFailure = null, bool restartIfFailed = false)
    168.         {
    169.             if( m_configs.ContainsKey( bundleName ) )
    170.             {
    171.                 if( onComplete == null ) return;
    172.  
    173.                 if( m_configs[bundleName].isLoaded )
    174.                 {
    175.                     Log.LogL( "Bundle {0} Already Loaded", bundleName );
    176.                     onComplete.Invoke( m_configs[bundleName].bundle );
    177.                     return;
    178.                 }
    179.  
    180.                 if( m_configs[bundleName].failed )
    181.                 {
    182.                     if( !restartIfFailed )
    183.                     {
    184.                         Log.LogL( "Bundle {0} Already Failed", bundleName );
    185.                         onFailure?.Invoke();
    186.                         return;
    187.                     }
    188.                 }
    189.                 else
    190.                 {
    191.                     Log.LogL( "Bundle {0} Still Loading", bundleName );
    192.                     m_configs[bundleName].onComplete += onComplete;
    193.                     m_configs[bundleName].onFailure += onFailure;
    194.                     return;
    195.                 }
    196.             }
    197.            
    198.             AssetConfig config = new AssetConfig
    199.             {
    200.                 path = path,
    201.                 name = bundleName,
    202.                 onComplete = onComplete,
    203.                 onFailure = onFailure
    204.             };
    205.  
    206.             m_configs[bundleName] = config;
    207.  
    208.             if( File.Exists( Path.Combine( path, bundleName ) ) )
    209.             {
    210.                 Log.LogL( "Bundle {0} is local", bundleName );
    211.                 LoadFromFile( config );
    212.                 return;
    213.             }
    214.  
    215.            
    216.             Log.LogL( "Downloading bundle {0}", bundleName );
    217.             Tool.StartCoroutine( Download( config ) );
    218.         }
    219.  
    220.         /// <summary>
    221.         /// Attempts to download the AssetBundleManifest from the location and if it succeeds it does the same for all bundles listed and caches them.
    222.         /// If the a bundle's hash(based on its contents) has not changed since it was last cached then it will use the cached version instead of re-downloading.
    223.         /// </summary>
    224.         /// <param name="url">URL to find the assetBundles for download(if null will skip this step and try falling back to streaming assets)</param>
    225.         /// <param name="streamingAssetsSubFolder">Subfolder in streaming assets to load assetBundles from if they were not successfully downloaded from the server. Skipped if null or empty</param>
    226.         public static void DownloadAllWithManifest( string url, string streamingAssetsSubFolder = null )
    227.         {
    228.             m_total = 1;
    229.             m_done = 0;
    230.  
    231.             Action fallBack = OnFailureEach;
    232.             if( !string.IsNullOrEmpty( streamingAssetsSubFolder ) )
    233.             {
    234.                 fallBack = () => { CheckStreamingAssets( streamingAssetsSubFolder ); };
    235.             }
    236.  
    237.             if( string.IsNullOrEmpty( url ) )
    238.             {
    239.                 fallBack.Invoke();
    240.             }
    241.             else
    242.             {
    243.                 LoadAssetBundle( url, "AssetBundles", OnCompleteManifest, fallBack );
    244.             }
    245.         }
    246.  
    247.         private static void CheckStreamingAssets(string url)
    248.         {
    249.             m_configs.Clear();
    250.             m_total = 1;
    251.             m_done = 0;
    252.             LoadAssetBundle( Path.Combine( Application.streamingAssetsPath, url ),
    253.                 "AssetBundles",
    254.                 OnCompleteManifest,
    255.                 OnFailureEach );
    256.         }
    257.  
    258.         private static void OnCompleteManifest( AssetBundle bundle )
    259.         {
    260.             AssetBundleManifest manifest = bundle.LoadAsset<AssetBundleManifest>( "AssetBundleManifest" );
    261.  
    262.             string[] bundles = manifest.GetAllAssetBundles();
    263.            
    264.             m_total += bundles.Length;
    265.             m_done++;
    266.  
    267.             string path = m_configs["AssetBundles"].path;
    268.  
    269.             string output = "bundles :";
    270.             foreach( string child in bundles )
    271.             {
    272.                 LoadAssetBundle( path, child, OnCompleteEach, OnFailureEach);
    273.                 output += " '" + child + "'";
    274.             }
    275.  
    276.             Log.LogL( output);
    277.         }
    278.        
    279.         private static void OnCompleteEach( AssetBundle bundle )
    280.         {
    281.             m_done++;
    282.         }
    283.        
    284.         private static void OnFailureEach()
    285.         {
    286.             m_done++;
    287.         }
    288.  
    289.         private static IEnumerator Download(AssetConfig config)
    290.         {
    291.             UnityWebRequest manifest = UnityWebRequest.Get (Path.Combine( config.path, config.name )+ ".manifest");
    292.             yield return manifest.SendWebRequest ();
    293.            
    294.             if( !string.IsNullOrEmpty( manifest.error ) )
    295.             {
    296.                 Log.Warn( "Failed to download manifest file for {0} from URL '{2}' :\n\t{1}",
    297.                     config.name,
    298.                     manifest.error,
    299.                     manifest.url );
    300.             }
    301.             else
    302.             {
    303.                 string[] lines = manifest.downloadHandler.text.Split('\n');
    304.                
    305.                 if( lines[1].Contains( "CRC:" ) )
    306.                 {
    307.                     Log.LogL( "Bundle {1} CRC: {0}",lines[1].Substring( 5, lines[1].Length - 5 ), config.name );
    308.                     uint.TryParse( lines[1].Substring( 5, lines[1].Length - 5 ), out config.crc );
    309.                 }
    310.  
    311.                 config.hash = default(Hash128);
    312.                 if( lines[5].Contains( "Hash:" ) )
    313.                 {
    314.                     Log.LogL( "Bundle {1} Hash: {0}",lines[5].Split (':') [1].Trim (), config.name );
    315.                     config.hash = Hash128.Parse(lines[5].Split (':') [1].Trim ());
    316.                 }
    317.                 else if(config.crc != 0)
    318.                 {
    319.                     // new manifest bundle found
    320.                     Caching.ClearAllCachedVersions( config.name );
    321.                 }
    322.             }
    323.  
    324.             UnityWebRequest download;
    325.             if( config.hash == default(Hash128) )
    326.             {
    327.                 if( config.crc == 0 )
    328.                 {
    329.                     // Manifest file not found
    330.                     List<Hash128> listing = new List<Hash128>();
    331.                     Caching.GetCachedVersions( config.name,listing );
    332.                     if( listing.Count > 0 )
    333.                     {
    334.                         download = UnityWebRequestAssetBundle.GetAssetBundle( Path.Combine( config.path, config.name ), listing[listing.Count-1], config.crc );
    335.                         Log.LogL( "{0} did not have a hash key, attempting to use most recent version {1}",
    336.                             config.name,
    337.                             listing[listing.Count - 1] );
    338.                     }
    339.                     else
    340.                     {
    341.                         download = UnityWebRequestAssetBundle.GetAssetBundle( Path.Combine( config.path, config.name ) );
    342.                         Log.LogL( "{0} did not have a hash key and no previous versions found", config.name );
    343.                     }
    344.                 }
    345.                 else
    346.                 {
    347.                     // was manifest bundle
    348.                     download = UnityWebRequestAssetBundle.GetAssetBundle( Path.Combine( config.path, config.name ),
    349.                         0,
    350.                         config.crc );
    351.                 }
    352.             }
    353.             else
    354.             {
    355.                 // proper and standard loading of file
    356.                 download = UnityWebRequestAssetBundle.GetAssetBundle( Path.Combine( config.path, config.name ), config.hash, config.crc );
    357.                 Log.LogL( "{0} is cached : {1}", config.name, Caching.IsVersionCached( download.url, config.hash ) );
    358.             }
    359.            
    360.             download.SendWebRequest();
    361.            
    362.             while( !download.isDone )
    363.             {
    364.                 config.progress = download.downloadProgress;
    365.                
    366.                 Log.LogL( "Bundle {0} progress {1}", config.name, config.progress );
    367.                 yield return null;
    368.             }
    369.  
    370.             if( !string.IsNullOrEmpty( download.error ) )
    371.             {
    372.                 Log.Error( "Failed to download bundle {0} from URL '{2}' :\n\t{1}",
    373.                     config.name,
    374.                     download.error,
    375.                     download.url );
    376.                 config.failed = true;
    377.                 config.onFailure?.Invoke();
    378.                 config.onComplete = null;
    379.                 config.onFailure = null;
    380.  
    381.                 yield break;
    382.             }
    383.  
    384.             Log.LogL( "Bundle {0} downloaded", config.name );
    385.            
    386.             config.bundle = DownloadHandlerAssetBundle.GetContent(download);
    387.                
    388.             config.succeeded = true;
    389.             config.isLoaded = true;
    390.             Log.LogL( "Bundle {0} loaded", config.name );
    391.             config.onComplete?.Invoke( config.bundle );
    392.             config.onComplete = null;
    393.             config.onFailure = null;
    394.            
    395.            
    396.             List<Hash128> cacheListing = new List<Hash128>();
    397.             Caching.GetCachedVersions( config.name,cacheListing );
    398.             if( cacheListing.Count > 1 )
    399.             {
    400.                 Caching.ClearCachedVersion( config.name, cacheListing[0] );
    401.             }
    402.         }
    403.  
    404.         private static void LoadFromFile(AssetConfig config)
    405.         {
    406.             if( !File.Exists( Path.Combine( config.path, config.name ) ) )
    407.             {
    408.                 Log.Error( "Bundle {0} was not local or failed to download", config.name );
    409.                 config.failed = true;
    410.                 config.onFailure?.Invoke();
    411.                 config.onComplete = null;
    412.                 config.onFailure = null;
    413.                 return;
    414.             }
    415.            
    416.             config.bundle = AssetBundle.LoadFromFile( Path.Combine(config.path,config.name) );
    417.            
    418.             config.succeeded = true;
    419.             config.isLoaded = true;
    420.            
    421.             Log.LogL( "Bundle {0} loaded", config.name );
    422.             config.onComplete?.Invoke( config.bundle );
    423.             config.onComplete = null;
    424.             config.onFailure = null;
    425.         }
    426.        
    427.     }
    428.  
    429.     public class AssetConfig
    430.     {
    431.         public string name;
    432.         public string path;
    433.         public uint crc;
    434.         public Hash128 hash;
    435.         public bool succeeded;
    436.         public bool failed;
    437.         public bool isLoaded;
    438.         public float progress;
    439.         public AssetBundle bundle;
    440.         public Action<AssetBundle> onComplete;
    441.         public Action onFailure;
    442.     }
     
    digitalmkt likes this.
  21. digitalmkt

    digitalmkt

    Joined:
    Dec 1, 2016
    Posts:
    7
    I was just looking right now how to load asset bundles from cache instead of loading from the internet every time I request the content. I'll try it and will let you know how is it going. Thank you very much.
     
    Last edited: Apr 4, 2020
  22. X-Ingredient

    X-Ingredient

    Joined:
    Sep 29, 2016
    Posts:
    2
    Hi all,

    Maybe a little bit of a side question (please don't hurt me ;)) but has anyone got the progress working when loading the assetbundles from cache?

    It's working fine while downloading but loading it from cache just keeps the downloadProgress property at 0.

    Hope you guys can help out!
     
    MaryamKamel likes this.
  23. theintellifygames

    theintellifygames

    Joined:
    Sep 13, 2020
    Posts:
    1
    Use this,

    1. CachedAssetBundle cached = new CachedAssetBundle();
    2. cached.name = <yourAssetBundleName>;

    Then pass this cached variable in WebRequest

    3. UnityWebRequestAssetBundle.GetAssetBundle(<download_url>, cached);

    It will always load from cache when its available and UnityWebRequest.downloadProgress will be 1 instantly, but if its not available in cache, then it will download & store in cache.
     
    gun_man and FloBeber like this.
  24. gun_man

    gun_man

    Joined:
    Sep 19, 2020
    Posts:
    16
    Hi,

    i implementing button to download asset bundle. now i dont know how to check there is cache available and user dont have to see the button any more? Thanks by the way
     
  25. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    gun_man likes this.
  26. gun_man

    gun_man

    Joined:
    Sep 19, 2020
    Posts:
    16
  27. francismoy

    francismoy

    Joined:
    Jul 5, 2017
    Posts:
    46
    Is this still correct on Unity 2021.3.x? I've noticed that if I enable "Use UnityWebRequest" in the Group Settings, the loading is significantly faster than if it's disabled. However, the official docs state that LoadFromFileAync is the recommended and faster way to load an asset bundle.