reimplement event deprecation warnings to fix "unknown mod" warnings

This commit is contained in:
Jesse Plamondon-Willard 2017-05-21 18:22:21 -04:00
parent bf3ed26a8b
commit ec19c85d66
8 changed files with 125 additions and 133 deletions

View File

@ -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.

View File

@ -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
/// <summary>The mods using the experimental API for which a warning has been raised.</summary>
private static readonly HashSet<string> WarnedMods = new HashSet<string>();
/// <summary>The backing field for <see cref="AfterAssetLoaded"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<IContentEventHelper> _AfterAssetLoaded;
/*********
** Events
@ -32,7 +36,15 @@ namespace StardewModdingAPI.Events
#else
internal
#endif
static event EventHandler<IContentEventHelper> AfterAssetLoaded;
static event EventHandler<IContentEventHelper> AfterAssetLoaded
{
add
{
ContentEvents.RaiseContentExperimentalWarning();
ContentEvents._AfterAssetLoaded += value;
}
remove => ContentEvents._AfterAssetLoaded -= value;
}
/*********
@ -61,30 +73,21 @@ namespace StardewModdingAPI.Events
/// <param name="contentHelper">Encapsulates access and changes to content being read from a data file.</param>
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
*********/
/// <summary>Raise a 'experimental API' warning for each mod using the content API.</summary>
/// <param name="handlers">The event handlers.</param>
private static void RaiseDeprecationWarning(Delegate[] handlers)
/// <summary>Raise an 'experimental API' warning for a mod using the content API.</summary>
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);
}
}
}

View File

@ -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
{
/// <summary>Events raised when the game changes state.</summary>
@ -12,6 +14,22 @@ namespace StardewModdingAPI.Events
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
/// <summary>The backing field for <see cref="Initialize"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler _Initialize;
/// <summary>The backing field for <see cref="LoadContent"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler _LoadContent;
/// <summary>The backing field for <see cref="GameLoaded"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler _GameLoaded;
/// <summary>The backing field for <see cref="FirstUpdateTick"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler _FirstUpdateTick;
/*********
** Events
@ -24,19 +42,51 @@ namespace StardewModdingAPI.Events
/// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
[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;
}
/// <summary>Raised before XNA loads or reloads graphics resources. Called during <see cref="Microsoft.Xna.Framework.Game.LoadContent"/>.</summary>
[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;
}
/// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary>
[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;
}
/// <summary>Raised during the first game update tick.</summary>
[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;
}
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public static event EventHandler UpdateTick;
@ -74,62 +124,30 @@ namespace StardewModdingAPI.Events
/// <param name="monitor">Encapsulates logging and monitoring.</param>
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());
}
/// <summary>Raise a <see cref="LoadContent"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
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());
}
/// <summary>Raise a <see cref="GameLoaded"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
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());
}
/// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
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());
}
/// <summary>Raise an <see cref="UpdateTick"/> event.</summary>

View File

@ -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
{
/// <summary>Events raised when the player data changes.</summary>
@ -16,17 +18,41 @@ namespace StardewModdingAPI.Events
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
/// <summary>The backing field for <see cref="LoadedGame"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<EventArgsLoadedGameChanged> _LoadedGame;
/// <summary>The backing field for <see cref="FarmerChanged"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<EventArgsFarmerChanged> _FarmerChanged;
/*********
** Events
*********/
/// <summary>Raised after the player loads a saved game.</summary>
[Obsolete("Use " + nameof(SaveEvents) + "." + nameof(SaveEvents.AfterLoad) + " instead")]
public static event EventHandler<EventArgsLoadedGameChanged> LoadedGame;
public static event EventHandler<EventArgsLoadedGameChanged> LoadedGame
{
add
{
PlayerEvents.DeprecationManager.Warn($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}", "1.6", DeprecationLevel.Info);
PlayerEvents._LoadedGame += value;
}
remove => PlayerEvents._LoadedGame -= value;
}
/// <summary>Raised after the game assigns a new player character. This happens just before <see cref="LoadedGame"/>; it's unclear how this would happen any other time.</summary>
[Obsolete("should no longer be used")]
public static event EventHandler<EventArgsFarmerChanged> FarmerChanged;
public static event EventHandler<EventArgsFarmerChanged> FarmerChanged
{
add
{
PlayerEvents.DeprecationManager.Warn($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}", "1.6", DeprecationLevel.Info);
PlayerEvents._FarmerChanged += value;
}
remove => PlayerEvents._FarmerChanged -= value;
}
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public static event EventHandler<EventArgsInventoryChanged> InventoryChanged;
@ -50,14 +76,7 @@ namespace StardewModdingAPI.Events
/// <param name="loaded">Whether the save has been loaded. This is always true.</param>
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);
}
/// <summary>Raise a <see cref="FarmerChanged"/> event.</summary>
@ -66,14 +85,7 @@ namespace StardewModdingAPI.Events
/// <param name="newFarmer">The new player character.</param>
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));
}
/// <summary>Raise an <see cref="InventoryChanged"/> event.</summary>

View File

@ -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
{
/// <summary>Events raised when the in-game date or time changes.</summary>
@ -12,6 +14,10 @@ namespace StardewModdingAPI.Events
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
/// <summary>The backing field for <see cref="OnNewDay"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<EventArgsNewDay> _OnNewDay;
/*********
** Events
@ -33,7 +39,15 @@ namespace StardewModdingAPI.Events
/// <summary>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.</summary>
[Obsolete("Use " + nameof(TimeEvents) + "." + nameof(TimeEvents.AfterDayStarted) + " or " + nameof(SaveEvents) + " instead")]
public static event EventHandler<EventArgsNewDay> OnNewDay;
public static event EventHandler<EventArgsNewDay> 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
/// <param name="isTransitioning">Whether the game just started the transition (<c>true</c>) or finished it (<c>false</c>).</param>
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));
}
}
}

View File

@ -10,23 +10,6 @@ namespace StardewModdingAPI.Framework
/// <summary>Provides extension methods for SMAPI's internal use.</summary>
internal static class InternalExtensions
{
/*********
** Properties
*********/
/// <summary>Tracks the installed mods.</summary>
private static ModRegistry ModRegistry;
/*********
** Public methods
*********/
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="modRegistry">Tracks the installed mods.</param>
internal static void Shim(ModRegistry modRegistry)
{
InternalExtensions.ModRegistry = modRegistry;
}
/****
** IMonitor
****/
@ -110,27 +93,6 @@ namespace StardewModdingAPI.Framework
}
}
/****
** Deprecation
****/
/// <summary>Log a deprecation warning for mods using an event.</summary>
/// <param name="deprecationManager">The deprecation manager to extend.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
/// <param name="version">The SMAPI version which deprecated it.</param>
/// <param name="severity">How deprecated the code is.</param>
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
****/

View File

@ -63,16 +63,6 @@ namespace StardewModdingAPI.Framework
return (from mod in this.Mods select mod);
}
/// <summary>Get the friendly mod name which handles a delegate.</summary>
/// <param name="delegate">The delegate to follow.</param>
/// <returns>Returns the mod name, or <c>null</c> if the delegate isn't implemented by a known mod.</returns>
public string GetModFrom(Delegate @delegate)
{
return @delegate?.Target != null
? this.GetModFrom(@delegate.Target.GetType())
: null;
}
/// <summary>Get the friendly mod name which defines a type.</summary>
/// <param name="type">The type to check.</param>
/// <returns>Returns the mod name, or <c>null</c> if the type isn't part of a known mod.</returns>

View File

@ -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);