Skip to content

Commit

Permalink
Don't try to display the load screen if a load failure occurred and a…
Browse files Browse the repository at this point in the history
…dd support for warnings and errors in stages.
  • Loading branch information
Sam Byass committed Feb 20, 2020
1 parent fc4a58e commit 5367e5f
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 65 deletions.
3 changes: 2 additions & 1 deletion Source/BetterLoading.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<HintPath>packages\UnlimitedHugs.Rimworld.HugsLib.6.1.1\lib\net35\0Harmony.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=1.0.7213.22423, Culture=neutral, PublicKeyToken=null">
<Reference Include="Assembly-CSharp, Culture=neutral, PublicKeyToken=null">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
Expand All @@ -63,6 +63,7 @@
<Compile Include="LoadingScreen.cs" />
<Compile Include="EnumLoadingStage.cs" />
<Compile Include="LongEventHandlerMirror.cs" />
<Compile Include="ModAssemblyHandlerHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StageTimingData.cs" />
<Compile Include="Stage\InitialLoad\3StageApplyPatches.cs" />
Expand Down
96 changes: 87 additions & 9 deletions Source/BetterLoadingMain.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Harmony;
using JetBrains.Annotations;
Expand All @@ -13,27 +16,101 @@ public sealed class BetterLoadingMain : Mod
public static ModContentPack? ourContentPack;
public static HarmonyInstance? hInstance;
public static LoadingScreen? LoadingScreen;


public static Dictionary<ModContentPack, List<DllLoadError>> DllPathsThatFailedToLoad = new Dictionary<ModContentPack, List<DllLoadError>>();

public class DllLoadError
{
public string dllName;
public LogMessage reasonMessage;
}

public BetterLoadingMain(ModContentPack content) : base(content)
{
ourContentPack = content;

hInstance = HarmonyInstance.Create("me.samboycoding.blm");
if (Camera.main == null) return; //Just in case

LogMsg("BetterLoading :: Init");

LoadingScreen = Resources.FindObjectsOfTypeAll<Root_Entry>()[0].gameObject.AddComponent<LoadingScreen>();
LogMsg("[BetterLoading] Verifying all mods loaded properly...");

var loadFailures = Log.Messages.Where(m => m.text.StartsWith("ReflectionTypeLoadException getting types")).ToList();
foreach (var pack in LoadedModManager.RunningMods)
{
var dllsThatShouldBeLoaded = ModAssemblyHandlerHelper.GetDlls(pack);
var dllsActuallyLoaded = pack.assemblies?.loadedAssemblies;

if (dllsThatShouldBeLoaded.Count == 0) continue;

if (dllsActuallyLoaded == null)
{
Log.Error($"[BetterLoading] DLL Manager for {pack.Name} failed to load!");
continue;
}

LoadingScreen.Instance.numModClasses = typeof(Mod).InstantiableDescendantsAndSelf().Count();
LoadingScreen.Instance.currentModClassBeingInstantiated = typeof(Mod).InstantiableDescendantsAndSelf().FirstIndexOf(t => t == typeof(BetterLoadingMain));
if (dllsActuallyLoaded.Count != dllsThatShouldBeLoaded.Count)
{
//Some assemblies failed to load

var loadedPaths = dllsActuallyLoaded.Select(dll => dll.Location.ToLower()).ToList();
var didntLoad = dllsThatShouldBeLoaded.Select(f => f.FullName).Where(path => loadedPaths.Contains(path.ToLower()) != true).ToList();

//Find the log messages where we identified details about which types failed to load
var failures = didntLoad
.Select(Path.GetFileNameWithoutExtension)
.Select(
filename =>
new DllLoadError
{
dllName = filename,
reasonMessage = loadFailures.First(f => f.text.Contains($"assembly {filename}"))
}
)
.ToList();

Log.Error($"[BetterLoading] {dllsThatShouldBeLoaded.Count - dllsActuallyLoaded.Count} assemblies for {pack.Name} failed to load! The ones that didn't load are: {didntLoad.ToCommaList()}");
Log.Error($"[BL] Got {failures.Count} messages that identify those failures.");

DllPathsThatFailedToLoad[pack] = failures;
}
}

hInstance.Patch(AccessTools.Method(typeof(LongEventHandler), nameof(LongEventHandler.LongEventsOnGUI)),
new HarmonyMethod(typeof(BetterLoadingMain), nameof(DisableVanillaLoadScreen)));
if (DllPathsThatFailedToLoad.Count == 0)
{
Log.Message("[BetterLoading] Injecting into main UI.");
LoadingScreen = Resources.FindObjectsOfTypeAll<Root_Entry>()[0].gameObject.AddComponent<LoadingScreen>();

hInstance.Patch(AccessTools.Method(typeof(LongEventHandler), nameof(LongEventHandler.LongEventsOnGUI)),
new HarmonyMethod(typeof(BetterLoadingMain), nameof(DisableVanillaLoadScreen)));
}
else
{
Log.Message("[BetterLoading] Not showing loading screen, not all mods loaded successfully so we would be unstable.");

hInstance.Patch(AccessTools.Method(typeof(UIRoot_Entry), nameof(UIRoot_Entry.Init)), postfix: new HarmonyMethod(typeof(BetterLoadingMain), nameof(DisplayFailedLoadDialog)));
}

//Harmony.PatchAll(Assembly.GetExecutingAssembly());
}

public static void DisplayFailedLoadDialog()
{
var messageTitle = @"BetterLoading
BetterLoading did not display because not all of your modded .dll files (assemblies) loaded properly.
This probably means you have a bad load order, or you're missing a dependency, such as HugsLib.
BetterLoading has nothing to do with this, and it would have happened anyway, we're just letting you know.
Don't report this to the BetterLoading dev. If you want to report it to anyone, report it to the developers of the mods below, but it's probably your fault.
The assemblies that failed to load are:
" + string.Join("\n", DllPathsThatFailedToLoad.Select(kvp => $"{kvp.Key.Name} - {kvp.Value.Select(e => e.dllName + ".dll").ToCommaList()}").ToArray());

Find.WindowStack.Add(new Dialog_MessageBox(messageTitle));
}

public static bool DisableVanillaLoadScreen()
{
//Disable when our load screen is shown
Expand All @@ -46,6 +123,7 @@ private static void LogMsg(string message)
}

//Following code kept as reference

#region Save Game Loading Patches

[HarmonyPatch(typeof(Game))]
Expand Down
75 changes: 24 additions & 51 deletions Source/LoadingScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,43 +30,18 @@ public sealed class LoadingScreen : MonoBehaviour
new StageRunPostLoadPreFinalizeCallbacks(BetterLoadingMain.hInstance),
new StageRunStaticCctors(BetterLoadingMain.hInstance),
new StageRunPostFinalizeCallbacks(BetterLoadingMain.hInstance)
//TODO: move the rest of the stages to this format.
};

private Texture2D background;
private Texture2D errorBarColor;
private Texture2D warningBarColor;

private LoadingStage _currentStage = BootLoadList[0];

public bool shouldShow = true;

public EnumLoadingStage currentStage = EnumLoadingStage.CreateClasses;

public int numModClasses;
public int currentModClassBeingInstantiated;
public string modBeingInstantiatedName = "";

public ModContentPack currentlyLoadingDefsFrom = BetterLoadingMain.ourContentPack;
public int totalLoadedContentPacks;
public int numContentPacksLoaded;

public int numPatchesToLoad;
public int numPatchesLoaded;
public ModContentPack currentlyPatching = BetterLoadingMain.ourContentPack;

public int numDefsToPreProcess;
public int numDefsPreProcessed;

public int numDefsToProcess;
public int numDefsProcessed;

public int numDefDatabases;
public int numDatabasesReloaded;
public Type currentDatabaseResolving = typeof(Def);

public int numStaticConstructorsToCall;
public int numStaticConstructorsCalled;
public Type? currentStaticConstructor;

//------------File Loading--------------
public int numWorldGeneratorsToRun;
public int numWorldGeneratorsRun;
Expand All @@ -78,15 +53,6 @@ public sealed class LoadingScreen : MonoBehaviour
public int numObjectsToSpawnCurrentMap;
public int numObjectsSpawnedCurrentMap;

public static Texture2D makeSolidColor(Color color)
{
var texture = new Texture2D(Screen.width, Screen.height);
Color[] pixels = Enumerable.Repeat(color, Screen.width * Screen.height).ToArray();
texture.SetPixels(pixels);
texture.Apply();
return texture;
}

public LoadingScreen()
{
Instance = this;
Expand Down Expand Up @@ -115,7 +81,11 @@ public void OnGUI()
}

if (background == null)
background = makeSolidColor(new Color(0.1f, 0.1f, 0.1f, 1));
background = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f, 1));
if(warningBarColor == null)
warningBarColor = SolidColorMaterials.NewSolidColorTexture(new Color(0.89f, 0.8f, 0.11f)); //RGB (226,203,29)
if (errorBarColor == null)
errorBarColor = SolidColorMaterials.NewSolidColorTexture(new Color(0.73f, 0.09f, 0.09f)); //RGB(185, 24, 24)

try
{
Expand Down Expand Up @@ -179,37 +149,40 @@ public void OnGUI()
var bgRect = new Rect(0, 0, Screen.width, Screen.height);
GUI.DrawTexture(bgRect, background);

var pct = currentProgress / (float) maxProgress;

var currentStageText = _currentStage.GetStageName();
var subStageText = _currentStage.GetCurrentStepName();
if (subStageText != null)
currentStageText = $"{currentStageText} - {subStageText}";

// Log.Message($"Rendering bar: Current stage is {currentStageText}, % is {pct * 100.0}", true);

//Draw title
Text.Font = GameFont.Medium;
Text.Anchor = TextAnchor.MiddleCenter;

var titleRect = new Rect(200, 200, Screen.width - 400, 40);
Widgets.Label(titleRect, "Initializing Game...");

Text.Font = GameFont.Small;


//Render current stage bar and label
var pct = currentProgress / (float) maxProgress;

//Draw background
// UIMenuBackgroundManager.background.BackgroundOnGUI();
var currentStageText = _currentStage.GetStageName();
var subStageText = _currentStage.GetCurrentStepName();
if (subStageText != null)
currentStageText = $"{currentStageText} - {subStageText}";

//Draw the bar for current stage progress
var rect = new Rect(200, Screen.height - 440, Screen.width - 400, 40);

Widgets.FillableBar(rect, pct);
var color = _currentStage.HasError() ? errorBarColor : _currentStage.HasWarning() ? warningBarColor : null;

if (color != null)
Widgets.FillableBar(rect, pct, color);
else
Widgets.FillableBar(rect, pct); //use default blue

Widgets.Label(rect, $"{currentProgress}/{maxProgress} ({pct.ToStringPercent()})");
Text.Anchor = TextAnchor.UpperLeft;

rect.y += 50;
Widgets.Label(rect, currentStageText);

//Draw the bar for current stage
//Render global progress bar.
rect = new Rect(200, Screen.height - 240, Screen.width - 400, 40);

Text.Anchor = TextAnchor.MiddleCenter;
Expand Down
20 changes: 20 additions & 0 deletions Source/ModAssemblyHandlerHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Verse;

namespace BetterLoading
{
public static class ModAssemblyHandlerHelper
{
public static List<FileInfo> GetDlls(ModContentPack mod)
{
//Copied and adapter from ModAssemblyHandler#reloadAll
var directoryInfo = new DirectoryInfo(mod.AssembliesFolder);

return directoryInfo.Exists
? directoryInfo.GetFiles("*.*", SearchOption.AllDirectories).Where(file => file.Extension.ToLower() == ".dll").ToList()
: new List<FileInfo>();
}
}
}
26 changes: 22 additions & 4 deletions Source/Stage/InitialLoad/8StageRunStaticCctors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class StageRunStaticCctors : LoadingStage
private static List<Action> _queue;

private static bool _finishedProcessing;
private static Exception _encounteredException;

public StageRunStaticCctors(HarmonyInstance instance) : base(instance)
{
Expand All @@ -31,7 +32,11 @@ public override string GetStageName()

public override string? GetCurrentStepName()
{
return _modType?.FullName ?? "Waiting for vanilla to finish being slow...";
var result = _modType?.FullName ?? "Waiting for vanilla to finish being slow...";
if (HasError())
result = $"Exception occurred processing {result}!";

return result;
}

public override int GetCurrentProgress()
Expand All @@ -55,18 +60,31 @@ public override void DoPatching(HarmonyInstance instance)
// instance.Patch(AccessTools.Method(typeof(RuntimeHelpers), nameof(RuntimeHelpers.RunClassConstructor), new []{typeof(RuntimeTypeHandle)}), new HarmonyMethod(typeof(StageRunStaticCctors), nameof(PreRunClassConstructor)));
}

public override bool HasError()
{
return _encounteredException != null;
}

public static IEnumerator StaticConstructAll()
{
Log.Message("[BetterLoading] Starting Antifreeze(tm) StaticConstructorCaller. Synchronizing retransmission chronicity...");
Application.runInBackground = true;
Log.Message("[BetterLoading] Contact Engaged. Here goes nothing...");
foreach (var type in _toRun)
{
_modType = type;
try
{
_modType = type;

RuntimeHelpers.RunClassConstructor(type.TypeHandle);
RuntimeHelpers.RunClassConstructor(type.TypeHandle);

_numRun++;
_numRun++;
}
catch (Exception e)
{
Log.Error("[BetterLoading] Exception occurred processing mod finalize events! Details: " + e);
_encounteredException = e;
}

yield return null;
}
Expand Down
20 changes: 20 additions & 0 deletions Source/Stage/LoadingStage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ public virtual bool IsCompleted()
public virtual void BecomeActive()
{
}

/// <summary>
/// Returns whether this stage has encountered any warnings while processing (e.g. non-critical exceptions, etc).
/// If this is the case and there is no error, the progress bar will be drawn in yellow for this stage.
/// </summary>
/// <returns>True to draw the progress bar in yellow, indicating a warning to the user.</returns>
public virtual bool HasWarning()
{
return false;
}

/// <summary>
/// Returns whether this stage has encountered any critical errors while processing, such that the process of loading the game cannot continue.
/// If this is the case, the progress bar will be drawn in red.
/// </summary>
/// <returns>True to draw the progress bar in red, indicating a load failure.</returns>
public virtual bool HasError()
{
return false;
}

/// <summary>
/// Called when a stage is no longer active (e.g. it finishes), to allow it to do cleanup.
Expand Down

0 comments on commit 5367e5f

Please sign in to comment.