From ec19c85d66a10bbd41c7fabfd9ea858fe659e747 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 May 2017 18:22:21 -0400 Subject: [PATCH] reimplement event deprecation warnings to fix "unknown mod" warnings --- release-notes.md | 1 + src/StardewModdingAPI/Events/ContentEvents.cs | 37 +++---- src/StardewModdingAPI/Events/GameEvents.cs | 98 +++++++++++-------- src/StardewModdingAPI/Events/PlayerEvents.cs | 48 +++++---- src/StardewModdingAPI/Events/TimeEvents.cs | 25 +++-- .../Framework/InternalExtensions.cs | 38 ------- .../Framework/ModRegistry.cs | 10 -- src/StardewModdingAPI/Program.cs | 1 - 8 files changed, 125 insertions(+), 133 deletions(-) diff --git a/release-notes.md b/release-notes.md index 679699a3..b9aabbf5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -17,6 +17,7 @@ For players: * SMAPI now shows a friendly error when it can't detect the game. * SMAPI now shows a friendly error when you have Stardew Valley 1.11 or earlier (which aren't compatible). * SMAPI now shows a friendly error if a mod dependency is missing (if it's listed in the mod's manifest). +* Fixed "unknown mod" deprecation warnings by improving how SMAPI detects the mod using the event. For modders: * You can now list mod dependencies in the `manifest.json`. SMAPI will make sure your dependencies are loaded before your mod, and will show a friendly error if a dependency is missing. diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index 0dcd2cc6..8fa9ae3c 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Framework; namespace StardewModdingAPI.Events @@ -19,6 +20,9 @@ namespace StardewModdingAPI.Events /// The mods using the experimental API for which a warning has been raised. private static readonly HashSet WarnedMods = new HashSet(); + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _AfterAssetLoaded; /********* ** Events @@ -32,7 +36,15 @@ namespace StardewModdingAPI.Events #else internal #endif - static event EventHandler AfterAssetLoaded; + static event EventHandler AfterAssetLoaded + { + add + { + ContentEvents.RaiseContentExperimentalWarning(); + ContentEvents._AfterAssetLoaded += value; + } + remove => ContentEvents._AfterAssetLoaded -= value; + } /********* @@ -61,30 +73,21 @@ namespace StardewModdingAPI.Events /// Encapsulates access and changes to content being read from a data file. internal static void InvokeAfterAssetLoaded(IMonitor monitor, IContentEventHelper contentHelper) { - if (ContentEvents.AfterAssetLoaded != null) - { - Delegate[] handlers = ContentEvents.AfterAssetLoaded.GetInvocationList(); - ContentEvents.RaiseDeprecationWarning(handlers); - monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterAssetLoaded)}", handlers, null, contentHelper); - } + monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterAssetLoaded)}", ContentEvents._AfterAssetLoaded?.GetInvocationList(), null, contentHelper); } /********* ** Private methods *********/ - /// Raise a 'experimental API' warning for each mod using the content API. - /// The event handlers. - private static void RaiseDeprecationWarning(Delegate[] handlers) + /// Raise an 'experimental API' warning for a mod using the content API. + private static void RaiseContentExperimentalWarning() { - foreach (Delegate handler in handlers) + string modName = ContentEvents.ModRegistry.GetModFromStack() ?? "An unknown mod"; + if (!ContentEvents.WarnedMods.Contains(modName)) { - string modName = ContentEvents.ModRegistry.GetModFrom(handler) ?? "An unknown mod"; - if (!ContentEvents.WarnedMods.Contains(modName)) - { - ContentEvents.WarnedMods.Add(modName); - ContentEvents.Monitor.Log($"{modName} used the undocumented and experimental content API, which may change or be removed without warning.", LogLevel.Warn); - } + ContentEvents.WarnedMods.Add(modName); + ContentEvents.Monitor.Log($"{modName} used the undocumented and experimental content API, which may change or be removed without warning.", LogLevel.Warn); } } } diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs index 4f9ce7a7..8e3cf662 100644 --- a/src/StardewModdingAPI/Events/GameEvents.cs +++ b/src/StardewModdingAPI/Events/GameEvents.cs @@ -1,6 +1,8 @@ using System; +using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Framework; +#pragma warning disable 618 // Suppress obsolete-symbol errors in this file. Since several events are marked obsolete, this produces unnecessary warnings. namespace StardewModdingAPI.Events { /// Events raised when the game changes state. @@ -12,6 +14,22 @@ namespace StardewModdingAPI.Events /// Manages deprecation warnings. private static DeprecationManager DeprecationManager; + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _Initialize; + + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _LoadContent; + + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _GameLoaded; + + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _FirstUpdateTick; + /********* ** Events @@ -24,19 +42,51 @@ namespace StardewModdingAPI.Events /// Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after . [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] - public static event EventHandler Initialize; + public static event EventHandler Initialize + { + add + { + GameEvents.DeprecationManager.Warn($"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}", "1.10", DeprecationLevel.Info); + GameEvents._Initialize += value; + } + remove => GameEvents._Initialize -= value; + } /// Raised before XNA loads or reloads graphics resources. Called during . [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.LoadContent) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] - public static event EventHandler LoadContent; + public static event EventHandler LoadContent + { + add + { + GameEvents.DeprecationManager.Warn($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", "1.10", DeprecationLevel.Info); + GameEvents._LoadContent += value; + } + remove => GameEvents._LoadContent -= value; + } /// Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point. [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the game loads, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] - public static event EventHandler GameLoaded; + public static event EventHandler GameLoaded + { + add + { + GameEvents.DeprecationManager.Warn($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", "1.12", DeprecationLevel.Info); + GameEvents._GameLoaded += value; + } + remove => GameEvents._GameLoaded -= value; + } /// Raised during the first game update tick. [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the game loads, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] - public static event EventHandler FirstUpdateTick; + public static event EventHandler FirstUpdateTick + { + add + { + GameEvents.DeprecationManager.Warn($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", "1.12", DeprecationLevel.Info); + GameEvents._FirstUpdateTick += value; + } + remove => GameEvents._FirstUpdateTick -= value; + } /// Raised when the game updates its state (≈60 times per second). public static event EventHandler UpdateTick; @@ -74,62 +124,30 @@ namespace StardewModdingAPI.Events /// Encapsulates logging and monitoring. internal static void InvokeInitialize(IMonitor monitor) { - // notify SMAPI monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.InitializeInternal)}", GameEvents.InitializeInternal?.GetInvocationList()); - - // notify mods - if (GameEvents.Initialize == null) - return; - string name = $"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}"; - Delegate[] handlers = GameEvents.Initialize.GetInvocationList(); - GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.10", DeprecationLevel.Info); - monitor.SafelyRaisePlainEvent(name, handlers); + monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}", GameEvents._Initialize?.GetInvocationList()); } /// Raise a event. /// Encapsulates logging and monitoring. internal static void InvokeLoadContent(IMonitor monitor) { - if (GameEvents.LoadContent == null) - return; - - string name = $"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}"; - Delegate[] handlers = GameEvents.LoadContent.GetInvocationList(); - - GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.10", DeprecationLevel.Info); - monitor.SafelyRaisePlainEvent(name, handlers); + monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", GameEvents._LoadContent?.GetInvocationList()); } /// Raise a event. /// Encapsulates monitoring and logging. internal static void InvokeGameLoaded(IMonitor monitor) { - // notify SMAPI monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoadedInternal)}", GameEvents.GameLoadedInternal?.GetInvocationList()); - - // notify mods - if (GameEvents.GameLoaded == null) - return; - - string name = $"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}"; - Delegate[] handlers = GameEvents.GameLoaded.GetInvocationList(); - - GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.12", DeprecationLevel.Info); - monitor.SafelyRaisePlainEvent(name, handlers); + monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents._GameLoaded?.GetInvocationList()); } /// Raise a event. /// Encapsulates monitoring and logging. internal static void InvokeFirstUpdateTick(IMonitor monitor) { - if (GameEvents.FirstUpdateTick == null) - return; - - string name = $"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}"; - Delegate[] handlers = GameEvents.FirstUpdateTick.GetInvocationList(); - - GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.12", DeprecationLevel.Info); - monitor.SafelyRaisePlainEvent(name, handlers); + monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents._FirstUpdateTick?.GetInvocationList()); } /// Raise an event. diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs index b02ebfec..37649fee 100644 --- a/src/StardewModdingAPI/Events/PlayerEvents.cs +++ b/src/StardewModdingAPI/Events/PlayerEvents.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework; using StardewValley; using SFarmer = StardewValley.Farmer; +#pragma warning disable 618 // Suppress obsolete-symbol errors in this file. Since several events are marked obsolete, this produces unnecessary warnings. namespace StardewModdingAPI.Events { /// Events raised when the player data changes. @@ -16,17 +18,41 @@ namespace StardewModdingAPI.Events /// Manages deprecation warnings. private static DeprecationManager DeprecationManager; + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _LoadedGame; + + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _FarmerChanged; + /********* ** Events *********/ /// Raised after the player loads a saved game. [Obsolete("Use " + nameof(SaveEvents) + "." + nameof(SaveEvents.AfterLoad) + " instead")] - public static event EventHandler LoadedGame; + public static event EventHandler LoadedGame + { + add + { + PlayerEvents.DeprecationManager.Warn($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}", "1.6", DeprecationLevel.Info); + PlayerEvents._LoadedGame += value; + } + remove => PlayerEvents._LoadedGame -= value; + } /// Raised after the game assigns a new player character. This happens just before ; it's unclear how this would happen any other time. [Obsolete("should no longer be used")] - public static event EventHandler FarmerChanged; + public static event EventHandler FarmerChanged + { + add + { + PlayerEvents.DeprecationManager.Warn($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}", "1.6", DeprecationLevel.Info); + PlayerEvents._FarmerChanged += value; + } + remove => PlayerEvents._FarmerChanged -= value; + } /// Raised after the player's inventory changes in any way (added or removed item, sorted, etc). public static event EventHandler InventoryChanged; @@ -50,14 +76,7 @@ namespace StardewModdingAPI.Events /// Whether the save has been loaded. This is always true. internal static void InvokeLoadedGame(IMonitor monitor, EventArgsLoadedGameChanged loaded) { - if (PlayerEvents.LoadedGame == null) - return; - - string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}"; - Delegate[] handlers = PlayerEvents.LoadedGame.GetInvocationList(); - - PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); - monitor.SafelyRaiseGenericEvent(name, handlers, null, loaded); + monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}", PlayerEvents._LoadedGame?.GetInvocationList(), null, loaded); } /// Raise a event. @@ -66,14 +85,7 @@ namespace StardewModdingAPI.Events /// The new player character. internal static void InvokeFarmerChanged(IMonitor monitor, SFarmer priorFarmer, SFarmer newFarmer) { - if (PlayerEvents.FarmerChanged == null) - return; - - string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}"; - Delegate[] handlers = PlayerEvents.FarmerChanged.GetInvocationList(); - - PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); - monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsFarmerChanged(priorFarmer, newFarmer)); + monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}", PlayerEvents._FarmerChanged?.GetInvocationList(), null, new EventArgsFarmerChanged(priorFarmer, newFarmer)); } /// Raise an event. diff --git a/src/StardewModdingAPI/Events/TimeEvents.cs b/src/StardewModdingAPI/Events/TimeEvents.cs index 3f06a46b..572898c7 100644 --- a/src/StardewModdingAPI/Events/TimeEvents.cs +++ b/src/StardewModdingAPI/Events/TimeEvents.cs @@ -1,6 +1,8 @@ using System; +using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Framework; +#pragma warning disable 618 // Suppress obsolete-symbol errors in this file. Since several events are marked obsolete, this produces unnecessary warnings. namespace StardewModdingAPI.Events { /// Events raised when the in-game date or time changes. @@ -12,6 +14,10 @@ namespace StardewModdingAPI.Events /// Manages deprecation warnings. private static DeprecationManager DeprecationManager; + /// The backing field for . + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static event EventHandler _OnNewDay; + /********* ** Events @@ -33,7 +39,15 @@ namespace StardewModdingAPI.Events /// Raised when the player is transitioning to a new day and the game is performing its day update logic. This event is triggered twice: once after the game starts transitioning, and again after it finishes. [Obsolete("Use " + nameof(TimeEvents) + "." + nameof(TimeEvents.AfterDayStarted) + " or " + nameof(SaveEvents) + " instead")] - public static event EventHandler OnNewDay; + public static event EventHandler OnNewDay + { + add + { + TimeEvents.DeprecationManager.Warn($"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}", "1.6", DeprecationLevel.Info); + TimeEvents._OnNewDay += value; + } + remove => TimeEvents._OnNewDay -= value; + } /********* @@ -96,14 +110,7 @@ namespace StardewModdingAPI.Events /// Whether the game just started the transition (true) or finished it (false). internal static void InvokeOnNewDay(IMonitor monitor, int priorDay, int newDay, bool isTransitioning) { - if (TimeEvents.OnNewDay == null) - return; - - string name = $"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}"; - Delegate[] handlers = TimeEvents.OnNewDay.GetInvocationList(); - - TimeEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); - monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsNewDay(priorDay, newDay, isTransitioning)); + monitor.SafelyRaiseGenericEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}", TimeEvents._OnNewDay?.GetInvocationList(), null, new EventArgsNewDay(priorDay, newDay, isTransitioning)); } } } diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index cadf6598..b99d3798 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -10,23 +10,6 @@ namespace StardewModdingAPI.Framework /// Provides extension methods for SMAPI's internal use. internal static class InternalExtensions { - /********* - ** Properties - *********/ - /// Tracks the installed mods. - private static ModRegistry ModRegistry; - - - /********* - ** Public methods - *********/ - /// Injects types required for backwards compatibility. - /// Tracks the installed mods. - internal static void Shim(ModRegistry modRegistry) - { - InternalExtensions.ModRegistry = modRegistry; - } - /**** ** IMonitor ****/ @@ -110,27 +93,6 @@ namespace StardewModdingAPI.Framework } } - /**** - ** Deprecation - ****/ - /// Log a deprecation warning for mods using an event. - /// The deprecation manager to extend. - /// The event handlers. - /// A noun phrase describing what is deprecated. - /// The SMAPI version which deprecated it. - /// How deprecated the code is. - public static void WarnForEvent(this DeprecationManager deprecationManager, Delegate[] handlers, string nounPhrase, string version, DeprecationLevel severity) - { - if (handlers == null || !handlers.Any()) - return; - - foreach (Delegate handler in handlers) - { - string modName = InternalExtensions.ModRegistry.GetModFrom(handler) ?? "an unknown mod"; // suppress stack trace for unknown mods, not helpful here - deprecationManager.Warn(modName, nounPhrase, version, severity); - } - } - /**** ** Sprite batch ****/ diff --git a/src/StardewModdingAPI/Framework/ModRegistry.cs b/src/StardewModdingAPI/Framework/ModRegistry.cs index 62063fbd..f9d3cfbf 100644 --- a/src/StardewModdingAPI/Framework/ModRegistry.cs +++ b/src/StardewModdingAPI/Framework/ModRegistry.cs @@ -63,16 +63,6 @@ namespace StardewModdingAPI.Framework return (from mod in this.Mods select mod); } - /// Get the friendly mod name which handles a delegate. - /// The delegate to follow. - /// Returns the mod name, or null if the delegate isn't implemented by a known mod. - public string GetModFrom(Delegate @delegate) - { - return @delegate?.Target != null - ? this.GetModFrom(@delegate.Target.GetType()) - : null; - } - /// Get the friendly mod name which defines a type. /// The type to check. /// Returns the mod name, or null if the type isn't part of a known mod. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 4a4b2ca7..b92108c3 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -322,7 +322,6 @@ namespace StardewModdingAPI #pragma warning disable 618 Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry); Config.Shim(this.DeprecationManager); - InternalExtensions.Shim(this.ModRegistry); Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry); Mod.Shim(this.DeprecationManager); ContentEvents.Shim(this.ModRegistry, this.Monitor);