Skip to content

Allow loading of assets with Master_Bundle_Override to be delayed until masterbundle has been loaded #5495

Description

@stefanomerotta

Hi!

following the evolution of workshop files loading mechanics, I found that the loading race condition between workshops are still present.
The feature of Master_Bundle_Override still can't be used to refer override or extend a workshop masterbundle from another workshop.

I propose than a small code modification to allow at least the ability to delay a specific asset load until the referred masterbundle has been loaded or at least removed by the pending masterbundle code.

This is a diff of the latest available version (at time of writing) of the Assets.cs file from the UnturnedDataMining repository where I basically:

  • added a dictionary to track assets that depends on a masterbundle not yet loaded
  • modified the Master_Bundle_Override check by putting the asset in a delayed list, grouped by masterbundle
  • added the logic to enqueue again when the delayed masterbundle has been loaded
  • added the logic to track unresolvable assets when all masterbundle has been loaded

This should be working for both client and server, because the logic is in common and don't modify other parts of download and load logic.
I can't try it by myself on both server and client, so I hope this could be sufficient to add and test it on the real codebase.

I hope this could be useful as a fast path until a real dependency load order will be implemented that could refactor entirely this behavior.

diff --git a/UnturnedDataMining/Assembly-CSharp/SDG.Unturned/Assets.cs b/UnturnedDataMining/Assembly-CSharp/SDG.Unturned/Assets.cs
index ee3629bc..3dc3bb2b 100644
--- a/UnturnedDataMining/Assembly-CSharp/SDG.Unturned/Assets.cs
+++ b/UnturnedDataMining/Assembly-CSharp/SDG.Unturned/Assets.cs
@@ -145,6 +145,8 @@ public class Assets : MonoBehaviour
 
     private static Queue<AssetsWorker.AssetDefinition> pendingAssetsToLoad;
 
+    private static Dictionary<MasterBundleConfig, List<AssetsWorker.AssetDefinition>> pendingAssetsToLoadByMasterBundle;
+
     /// <summary>
     /// While an asset is being loaded, this is the asset.
     /// Used by some error logging.
@@ -934,7 +936,10 @@ public class Assets : MonoBehaviour
         try
         {
             loadingStats.totalFilesLoaded++;
-            LoadFile(file);
+            if (!LoadFile(file))
+            {
+                loadingStats.totalFilesLoaded--;
+            }
         }
         catch (Exception e)
         {
@@ -943,7 +948,7 @@ public class Assets : MonoBehaviour
         }
     }
 
-    private static void LoadFile(AssetsWorker.AssetDefinition file)
+    private static bool LoadFile(AssetsWorker.AssetDefinition file)
     {
         string path = file.path;
         IDatDictionary assetData = file.assetData;
@@ -968,13 +973,13 @@ public class Assets : MonoBehaviour
             if (!node.TryParseGuid("GUID", out value))
             {
                 reportError("Unable to parse Metadata.GUID in \"" + path + "\"");
-                return;
+                return true;
             }
             type = node.ParseType("Type");
             if (type == null)
             {
                 reportError("Unable to parse Metadata.Type in \"" + path + "\"");
-                return;
+                return true;
             }
         }
         else if (!assetData.ContainsKey("GUID"))
@@ -995,12 +1000,12 @@ public class Assets : MonoBehaviour
         else if (!assetData.TryParseGuid("GUID", out value))
         {
             reportError("Unable to parse GUID in \"" + path + "\"");
-            return;
+            return true;
         }
         if (value.IsEmpty())
         {
             reportError("Cannot use empty GUID in \"" + path + "\"");
-            return;
+            return true;
         }
         IDatDictionary datDictionary = assetData;
         if (assetData.TryGetDictionary("Asset", out var node2))
@@ -1013,7 +1018,7 @@ public class Assets : MonoBehaviour
             if (string.IsNullOrEmpty(@string))
             {
                 reportError("Missing asset Type in \"" + path + "\"");
-                return;
+                return true;
             }
             type = assetTypes.getType(@string);
             if (type == null)
@@ -1022,14 +1027,14 @@ public class Assets : MonoBehaviour
                 if (type == null)
                 {
                     reportError("Unhandled asset type \"" + @string + "\" in \"" + path + "\"");
-                    return;
+                    return true;
                 }
             }
         }
         if (!typeof(Asset).IsAssignableFrom(type))
         {
             reportError($"Type \"{type}\" is not a valid asset type in \"{path}\"");
-            return;
+            return true;
         }
         MasterBundleConfig masterBundleConfig = findMasterBundleByPath(path);
         string string2 = datDictionary.GetString("Master_Bundle_Override");
@@ -1037,10 +1042,29 @@ public class Assets : MonoBehaviour
         {
             masterBundleConfig = findMasterBundleByName(string2);
             if (masterBundleConfig == null)
+            {
+                MasterBundleConfig pendingMasterBundleConfig = findMasterBundleInListByName(pendingMasterBundles, string2);
+                if (pendingMasterBundleConfig != null)
+                {
+                    UnturnedLog.info("Asset '{0}' is pending master bundle override '{1}', loading delayed", path, string2);
+                    
+                    if(pendingAssetsToLoadByMasterBundle.TryGetValue(pendingMasterBundleConfig, out List<AssetsWorker.AssetDefinition>? list))
+                    {
+                        list.Add(file);
+                    }
+                    else
+                    {
+                        pendingAssetsToLoadByMasterBundle.Add(pendingMasterBundleConfig, new List<AssetsWorker.AssetDefinition>() { file });
+                    }
+
+                    return false;
+                }
+                else
                 {
                     UnturnedLog.warn("Unable to find master bundle override '{0}' for '{1}'", string2, path);
                 }
             }
+        }
         else if (datDictionary.ContainsKey("Exclude_From_Master_Bundle"))
         {
             masterBundleConfig = null;
@@ -1105,7 +1129,7 @@ public class Assets : MonoBehaviour
             bundle.unload();
             currentMasterBundle = null;
             currentAsset = null;
-            return;
+            return true;
         }
         if (asset == null)
         {
@@ -1113,7 +1137,7 @@ public class Assets : MonoBehaviour
             bundle.unload();
             currentMasterBundle = null;
             currentAsset = null;
-            return;
+            return true;
         }
         currentAsset = asset;
         try
@@ -1166,6 +1190,8 @@ public class Assets : MonoBehaviour
         }
         currentMasterBundle = null;
         currentAsset = null;
+
+        return true;
     }
 
     /// <summary>
@@ -1744,10 +1770,32 @@ public class Assets : MonoBehaviour
                             UnturnedLog.info("Unable to find a fallback asset bundle for \"" + masterBundleConfig.assetBundleName + "\"");
                         }
                     }
+
+                    if (pendingAssetsToLoadByMasterBundle.Remove(masterBundleConfig, out List<AssetsWorker.AssetDefinition>? list))
+                    {
+                        foreach (AssetsWorker.AssetDefinition file in list)
+                        {
+                            pendingAssetsToLoad.Enqueue(file);
+                        }
+                    }
                 }
                 if (pendingMasterBundles.Count < 1)
                 {
                     loadingStats.isLoadingAssetBundles = false;
+
+                    if(pendingAssetsToLoadByMasterBundle.Count > 0)
+                    {
+                        UnturnedLog.warn("Some master bundle overrides were not loaded because their master bundles failed to load:");
+                        foreach (KeyValuePair<MasterBundleConfig, List<AssetsWorker.AssetDefinition>> kvp in pendingAssetsToLoadByMasterBundle)
+                        {
+                            MasterBundleConfig failedMasterBundle = kvp.Key;
+                            UnturnedLog.warn($"- {failedMasterBundle.assetBundleName} in {failedMasterBundle.directoryPath} (origin: {failedMasterBundle.origin.name})");
+                            foreach (AssetsWorker.AssetDefinition assetDefinition in kvp.Value)
+                            {
+                                UnturnedLog.warn($"    - {assetDefinition.path}");
+                            }
+                        }
+                    }
                 }
             }
             else if (coreMasterBundle != null && pendingAssetsToLoad.TryDequeue(out result2))
@@ -1799,11 +1847,13 @@ public class Assets : MonoBehaviour
             allMasterBundles = new List<MasterBundleConfig>();
             pendingMasterBundles = new List<MasterBundleConfig>();
             pendingAssetsToLoad = new Queue<AssetsWorker.AssetDefinition>();
+            pendingAssetsToLoadByMasterBundle = new Dictionary<MasterBundleConfig, List<AssetsWorker.AssetDefinition>>();
         }
         else
         {
             UnloadAllMasterBundles();
             pendingAssetsToLoad.Clear();
+            pendingAssetsToLoadByMasterBundle.Clear();
         }
         assetOrigins = new List<AssetOrigin>();
         loadingStats.Reset();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions