diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-06-02 17:43:51 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-06-02 17:43:51 -0400 |
commit | 2e3c42130358734a6fcf547745324dd272176f9c (patch) | |
tree | f352874151db1b374645d90d67fab46a9fa7241f /src/SMAPI/Framework/SGame.cs | |
parent | c0ba24456ba741ad5b072c033f9b0537ee74ad04 (diff) | |
download | SMAPI-2e3c42130358734a6fcf547745324dd272176f9c.tar.gz SMAPI-2e3c42130358734a6fcf547745324dd272176f9c.tar.bz2 SMAPI-2e3c42130358734a6fcf547745324dd272176f9c.zip |
tweak SGame update logic to avoid some edge cases (#310)
Diffstat (limited to 'src/SMAPI/Framework/SGame.cs')
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 103 |
1 files changed, 59 insertions, 44 deletions
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 /// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="SaveEvents.BeforeCreate"/>.</summary> private bool IsBetweenCreateEvents; + /// <summary>A callback to invoke after the game finishes initialising.</summary> + private readonly Action OnGameInitialised; + + /// <summary>A callback to invoke when the game exits.</summary> + private readonly Action OnGameExiting; + + /// <summary>Simplifies access to private game code.</summary> + private readonly Reflector Reflection; + /**** ** Game state ****/ @@ -80,28 +89,28 @@ namespace StardewModdingAPI.Framework private readonly List<IWatcher> Watchers = new List<IWatcher>(); /// <summary>Tracks changes to the window size.</summary> - private readonly IValueWatcher<Point> WindowSizeWatcher; + private IValueWatcher<Point> WindowSizeWatcher; /// <summary>Tracks changes to the current player.</summary> private PlayerTracker CurrentPlayerTracker; /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> - private readonly IValueWatcher<int> TimeWatcher; + private IValueWatcher<int> TimeWatcher; /// <summary>Tracks changes to the save ID.</summary> - private readonly IValueWatcher<ulong> SaveIdWatcher; + private IValueWatcher<ulong> SaveIdWatcher; /// <summary>Tracks changes to the game's locations.</summary> - private readonly WorldLocationsTracker LocationsWatcher; + private WorldLocationsTracker LocationsWatcher; /// <summary>Tracks changes to <see cref="Game1.activeClickableMenu"/>.</summary> - private readonly IValueWatcher<IClickableMenu> ActiveMenuWatcher; + private IValueWatcher<IClickableMenu> ActiveMenuWatcher; /// <summary>Tracks changes to the cursor position.</summary> - private readonly IValueWatcher<Vector2> CursorWatcher; + private IValueWatcher<Vector2> CursorWatcher; /// <summary>Tracks changes to the mouse wheel scroll.</summary> - private readonly IValueWatcher<int> MouseWheelScrollWatcher; + private IValueWatcher<int> MouseWheelScrollWatcher; /// <summary>The previous content locale.</summary> private LocalizedContentManager.LanguageCode? PreviousLocale; @@ -112,18 +121,12 @@ namespace StardewModdingAPI.Framework /// <summary>An index incremented on every tick and reset every 60th tick (0–59).</summary> private int CurrentUpdateTick; + /// <summary>Whether post-game-startup initialisation has been performed.</summary> + private bool IsInitialised; + /// <summary>Whether this is the very first update tick since the game started.</summary> private bool FirstUpdate; - /// <summary>A callback to invoke after the game finishes initialising.</summary> - private readonly Action OnGameInitialised; - - /// <summary>A callback to invoke when the game exits.</summary> - private readonly Action OnGameExiting; - - /// <summary>Simplifies access to private game code.</summary> - private readonly Reflector Reflection; - /// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary> 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<GameLocation>(); + } + + /// <summary>Initialise just before the game's first update tick.</summary> + 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(); } /// <summary>Perform cleanup logic when the game exits.</summary> @@ -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 @@ -290,6 +300,17 @@ namespace StardewModdingAPI.Framework } /********* + ** 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 *********/ // While the game is writing to the save file in the background, mods can unexpectedly @@ -337,12 +358,6 @@ namespace StardewModdingAPI.Framework } /********* - ** Notify SMAPI that game is initialised - *********/ - if (this.FirstUpdate) - this.OnGameInitialised(); - - /********* ** Update context *********/ if (Context.IsWorldReady && !Context.IsSaveLoaded) |