fix errors during overridden update loop immediately crashing the game with no log entry
This commit is contained in:
parent
b977a8e48f
commit
8439594b10
|
@ -15,6 +15,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.12...1.13).
|
||||||
|
|
||||||
For players:
|
For players:
|
||||||
* SMAPI now recovers better from mod draw errors and detects when the error is irrecoverable.
|
* SMAPI now recovers better from mod draw errors and detects when the error is irrecoverable.
|
||||||
|
* SMAPI now recovers automatically from update errors if possible.
|
||||||
* SMAPI now remembers if your game crashed and offers help next time you launch it.
|
* SMAPI now remembers if your game crashed and offers help next time you launch it.
|
||||||
* Fixed installer finding redundant game paths on Linux.
|
* Fixed installer finding redundant game paths on Linux.
|
||||||
* Fixed save events not being raised after the first day on Linux/Mac.
|
* Fixed save events not being raised after the first day on Linux/Mac.
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
namespace StardewModdingAPI.Framework
|
||||||
|
{
|
||||||
|
/// <summary>Counts down from a baseline value.</summary>
|
||||||
|
internal class Countdown
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The initial value from which to count down.</summary>
|
||||||
|
public int Initial { get; }
|
||||||
|
|
||||||
|
/// <summary>The current value.</summary>
|
||||||
|
public int Current { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="initial">The initial value from which to count down.</param>
|
||||||
|
public Countdown(int initial)
|
||||||
|
{
|
||||||
|
this.Initial = initial;
|
||||||
|
this.Current = initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reduce the current value by one.</summary>
|
||||||
|
/// <returns>Returns whether the value was decremented (i.e. wasn't already zero).</returns>
|
||||||
|
public bool Decrement()
|
||||||
|
{
|
||||||
|
if (this.Current <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.Current--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Restart the countdown.</summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.Current = this.Initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,15 +30,15 @@ namespace StardewModdingAPI.Framework
|
||||||
** SMAPI state
|
** SMAPI state
|
||||||
****/
|
****/
|
||||||
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
|
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
|
||||||
private readonly int MaxFailedDraws = 60; // roughly one second
|
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
|
||||||
|
|
||||||
|
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from an update error.</summary>
|
||||||
|
private readonly Countdown UpdateCrashTimer = new Countdown(60); // 60 ticks = roughly one second
|
||||||
|
|
||||||
/// <summary>The number of ticks until SMAPI should notify mods that the game has loaded.</summary>
|
/// <summary>The number of ticks until SMAPI should notify mods that the game has loaded.</summary>
|
||||||
/// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks>
|
/// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks>
|
||||||
private int AfterLoadTimer = 5;
|
private int AfterLoadTimer = 5;
|
||||||
|
|
||||||
/// <summary>The number of consecutive failed draws.</summary>
|
|
||||||
private int FailedDraws;
|
|
||||||
|
|
||||||
/// <summary>Whether the game is returning to the menu.</summary>
|
/// <summary>Whether the game is returning to the menu.</summary>
|
||||||
private bool IsExiting;
|
private bool IsExiting;
|
||||||
|
|
||||||
|
@ -233,6 +233,8 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>The method called when the game is updating its state. This happens roughly 60 times per second.</summary>
|
/// <summary>The method called when the game is updating its state. This happens roughly 60 times per second.</summary>
|
||||||
/// <param name="gameTime">A snapshot of the game timing state.</param>
|
/// <param name="gameTime">A snapshot of the game timing state.</param>
|
||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Skip conditions
|
** Skip conditions
|
||||||
|
@ -316,7 +318,7 @@ namespace StardewModdingAPI.Framework
|
||||||
/*********
|
/*********
|
||||||
** After load events
|
** After load events
|
||||||
*********/
|
*********/
|
||||||
if (Context.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0)
|
if (Context.IsSaveLoaded && !SaveGame.IsProcessing /*still loading save*/ && this.AfterLoadTimer >= 0)
|
||||||
{
|
{
|
||||||
if (this.AfterLoadTimer == 0)
|
if (this.AfterLoadTimer == 0)
|
||||||
{
|
{
|
||||||
|
@ -567,6 +569,18 @@ namespace StardewModdingAPI.Framework
|
||||||
this.KStatePrior = this.KStateNow;
|
this.KStatePrior = this.KStateNow;
|
||||||
for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
|
for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
|
||||||
this.PreviouslyPressedButtons[(int)i] = this.GetButtonsDown(i);
|
this.PreviouslyPressedButtons[(int)i] = this.GetButtonsDown(i);
|
||||||
|
|
||||||
|
this.UpdateCrashTimer.Reset();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// log error
|
||||||
|
this.Monitor.Log($"An error occured in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error);
|
||||||
|
|
||||||
|
// exit if irrecoverable
|
||||||
|
if (!this.UpdateCrashTimer.Decrement())
|
||||||
|
this.Monitor.ExitGameImmediately("the game crashed when updating, and SMAPI was unable to recover the game.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The method called to draw everything to the screen.</summary>
|
/// <summary>The method called to draw everything to the screen.</summary>
|
||||||
|
@ -577,7 +591,7 @@ namespace StardewModdingAPI.Framework
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.DrawImpl(gameTime);
|
this.DrawImpl(gameTime);
|
||||||
this.FailedDraws = 0;
|
this.DrawCrashTimer.Reset();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -585,12 +599,11 @@ namespace StardewModdingAPI.Framework
|
||||||
this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error);
|
this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error);
|
||||||
|
|
||||||
// exit if irrecoverable
|
// exit if irrecoverable
|
||||||
if (this.FailedDraws >= this.MaxFailedDraws)
|
if (!this.DrawCrashTimer.Decrement())
|
||||||
{
|
{
|
||||||
this.Monitor.ExitGameImmediately("the game crashed when drawing, and SMAPI was unable to recover the game.");
|
this.Monitor.ExitGameImmediately("the game crashed when drawing, and SMAPI was unable to recover the game.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.FailedDraws++;
|
|
||||||
|
|
||||||
// abort in known unrecoverable cases
|
// abort in known unrecoverable cases
|
||||||
if (Game1.toolSpriteSheet?.IsDisposed == true)
|
if (Game1.toolSpriteSheet?.IsDisposed == true)
|
||||||
|
|
|
@ -121,6 +121,7 @@
|
||||||
<Compile Include="Events\EventArgsStringChanged.cs" />
|
<Compile Include="Events\EventArgsStringChanged.cs" />
|
||||||
<Compile Include="Events\GameEvents.cs" />
|
<Compile Include="Events\GameEvents.cs" />
|
||||||
<Compile Include="Events\GraphicsEvents.cs" />
|
<Compile Include="Events\GraphicsEvents.cs" />
|
||||||
|
<Compile Include="Framework\Countdown.cs" />
|
||||||
<Compile Include="Framework\ModLoading\IModMetadata.cs" />
|
<Compile Include="Framework\ModLoading\IModMetadata.cs" />
|
||||||
<Compile Include="Framework\ModLoading\InvalidModStateException.cs" />
|
<Compile Include="Framework\ModLoading\InvalidModStateException.cs" />
|
||||||
<Compile Include="Framework\ModLoading\ModDependencyStatus.cs" />
|
<Compile Include="Framework\ModLoading\ModDependencyStatus.cs" />
|
||||||
|
|
Loading…
Reference in New Issue