From 2e3c42130358734a6fcf547745324dd272176f9c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 2 Jun 2018 17:43:51 -0400 Subject: tweak SGame update logic to avoid some edge cases (#310) --- src/SMAPI/Framework/SGame.cs | 103 +++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index ae80f680..a4d149f3 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -73,6 +73,15 @@ namespace StardewModdingAPI.Framework /// Whether the game is creating the save file and SMAPI has already raised . private bool IsBetweenCreateEvents; + /// A callback to invoke after the game finishes initialising. + private readonly Action OnGameInitialised; + + /// A callback to invoke when the game exits. + private readonly Action OnGameExiting; + + /// Simplifies access to private game code. + private readonly Reflector Reflection; + /**** ** Game state ****/ @@ -80,28 +89,28 @@ namespace StardewModdingAPI.Framework private readonly List Watchers = new List(); /// Tracks changes to the window size. - private readonly IValueWatcher WindowSizeWatcher; + private IValueWatcher WindowSizeWatcher; /// Tracks changes to the current player. private PlayerTracker CurrentPlayerTracker; /// Tracks changes to the time of day (in 24-hour military format). - private readonly IValueWatcher TimeWatcher; + private IValueWatcher TimeWatcher; /// Tracks changes to the save ID. - private readonly IValueWatcher SaveIdWatcher; + private IValueWatcher SaveIdWatcher; /// Tracks changes to the game's locations. - private readonly WorldLocationsTracker LocationsWatcher; + private WorldLocationsTracker LocationsWatcher; /// Tracks changes to . - private readonly IValueWatcher ActiveMenuWatcher; + private IValueWatcher ActiveMenuWatcher; /// Tracks changes to the cursor position. - private readonly IValueWatcher CursorWatcher; + private IValueWatcher CursorWatcher; /// Tracks changes to the mouse wheel scroll. - private readonly IValueWatcher MouseWheelScrollWatcher; + private IValueWatcher MouseWheelScrollWatcher; /// The previous content locale. private LocalizedContentManager.LanguageCode? PreviousLocale; @@ -112,18 +121,12 @@ namespace StardewModdingAPI.Framework /// An index incremented on every tick and reset every 60th tick (0–59). private int CurrentUpdateTick; + /// Whether post-game-startup initialisation has been performed. + private bool IsInitialised; + /// Whether this is the very first update tick since the game started. private bool FirstUpdate; - /// A callback to invoke after the game finishes initialising. - private readonly Action OnGameInitialised; - - /// A callback to invoke when the game exits. - private readonly Action OnGameExiting; - - /// Simplifies access to private game code. - private readonly Reflector Reflection; - /// Whether the next content manager requested by the game will be for . private bool NextContentManagerIsMain; @@ -172,8 +175,17 @@ namespace StardewModdingAPI.Framework Game1.input = new SInputState(); Game1.multiplayer = new SMultiplayer(monitor, eventManager); - // init watchers + // init observables Game1.locations = new ObservableCollection(); + } + + /// Initialise just before the game's first update tick. + private void InitialiseAfterGameStarted() + { + // set initial state + this.Input.TrueUpdate(); + + // init watchers this.CursorWatcher = WatcherFactory.ForEquatable(() => this.Input.CursorPosition.ScreenPixels); this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => this.Input.RealMouse.ScrollWheelValue); this.SaveIdWatcher = WatcherFactory.ForEquatable(() => Game1.hasLoadedGame ? Game1.uniqueIDForThisGame : 0); @@ -191,6 +203,9 @@ namespace StardewModdingAPI.Framework this.ActiveMenuWatcher, this.LocationsWatcher }); + + // raise callback + this.OnGameInitialised(); } /// Perform cleanup logic when the game exits. @@ -239,19 +254,24 @@ namespace StardewModdingAPI.Framework try { /********* - ** Update input + ** Special cases *********/ - // This should *always* run, even when suppressing mod events, since the game uses - // this too. For example, doing this after mod event suppression would prevent the - // user from doing anything on the overnight shipping screen. - SInputState previousInputState = this.Input.Clone(); - SInputState inputState = this.Input; - if (this.IsActive) - inputState.TrueUpdate(); + // Perform first-tick initialisation. + if (!this.IsInitialised) + { + this.IsInitialised = true; + this.InitialiseAfterGameStarted(); + } - /********* - ** Load game synchronously - *********/ + // Abort if SMAPI is exiting. + if (this.Monitor.IsExiting) + { + this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace); + return; + } + + // Load saves synchronously to avoid issues due to mod events triggering + // concurrently with game code. if (Game1.gameMode == Game1.loadingMode) { this.Monitor.Log("Running game loader...", LogLevel.Trace); @@ -263,16 +283,6 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Game loader OK.", LogLevel.Trace); } - /********* - ** Skip conditions - *********/ - // SMAPI exiting, stop processing game updates - if (this.Monitor.IsExiting) - { - this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace); - return; - } - // While a background task is in progress, the game may make changes to the game // state while mods are running their code. This is risky, because data changes can // conflict (e.g. collection changed during enumeration errors) and data may change @@ -289,6 +299,17 @@ namespace StardewModdingAPI.Framework return; } + /********* + ** Update input + *********/ + // This should *always* run, even when suppressing mod events, since the game uses + // this too. For example, doing this after mod event suppression would prevent the + // user from doing anything on the overnight shipping screen. + SInputState previousInputState = this.Input.Clone(); + SInputState inputState = this.Input; + if (this.IsActive) + inputState.TrueUpdate(); + /********* ** Save events + suppress events during save *********/ @@ -336,12 +357,6 @@ namespace StardewModdingAPI.Framework this.Events.Time_AfterDayStarted.Raise(); } - /********* - ** Notify SMAPI that game is initialised - *********/ - if (this.FirstUpdate) - this.OnGameInitialised(); - /********* ** Update context *********/ -- cgit