summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/CommandManager.cs67
-rw-r--r--src/SMAPI/Framework/Commands/HelpCommand.cs18
-rw-r--r--src/SMAPI/Framework/Content/TilesheetReference.cs33
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs43
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs36
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs6
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs44
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs34
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs38
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs35
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs124
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterCollection.cs205
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs30
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceMonitor.cs184
-rw-r--r--src/SMAPI/Framework/SCore.cs77
-rw-r--r--src/SMAPI/Framework/SGame.cs9
16 files changed, 222 insertions, 761 deletions
diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs
index 4a99fd4d..ff540ad8 100644
--- a/src/SMAPI/Framework/CommandManager.cs
+++ b/src/SMAPI/Framework/CommandManager.cs
@@ -15,10 +15,20 @@ namespace StardewModdingAPI.Framework
/// <summary>The commands registered with SMAPI.</summary>
private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.OrdinalIgnoreCase);
+ /// <summary>Writes messages to the console.</summary>
+ private readonly IMonitor Monitor;
+
/*********
** Public methods
*********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitor">Writes messages to the console.</param>
+ public CommandManager(IMonitor monitor)
+ {
+ this.Monitor = monitor;
+ }
+
/// <summary>Add a console command.</summary>
/// <param name="mod">The mod adding the command (or <c>null</c> for a SMAPI command).</param>
/// <param name="name">The command name, which the user must type to trigger it.</param>
@@ -81,8 +91,9 @@ namespace StardewModdingAPI.Framework
/// <param name="name">The parsed command name.</param>
/// <param name="args">The parsed command arguments.</param>
/// <param name="command">The command which can handle the input.</param>
+ /// <param name="screenId">The screen ID on which to run the command.</param>
/// <returns>Returns true if the input was successfully parsed and matched to a command; else false.</returns>
- public bool TryParse(string input, out string name, out string[] args, out Command command)
+ public bool TryParse(string input, out string name, out string[] args, out Command command, out int screenId)
{
// ignore if blank
if (string.IsNullOrWhiteSpace(input))
@@ -90,6 +101,7 @@ namespace StardewModdingAPI.Framework
name = null;
args = null;
command = null;
+ screenId = 0;
return false;
}
@@ -98,6 +110,27 @@ namespace StardewModdingAPI.Framework
name = this.GetNormalizedName(args[0]);
args = args.Skip(1).ToArray();
+ // get screen ID argument
+ screenId = 0;
+ for (int i = 0; i < args.Length; i++)
+ {
+ // consume arg & set screen ID
+ if (this.TryParseScreenId(args[i], out int rawScreenId, out string error))
+ {
+ args = args.Take(i).Concat(args.Skip(i + 1)).ToArray();
+ screenId = rawScreenId;
+ continue;
+ }
+
+ // invalid screen arg
+ if (error != null)
+ {
+ this.Monitor.Log(error, LogLevel.Error);
+ command = null;
+ return false;
+ }
+ }
+
// get command
return this.Commands.TryGetValue(name, out command);
}
@@ -152,6 +185,38 @@ namespace StardewModdingAPI.Framework
return args.Where(item => !string.IsNullOrWhiteSpace(item)).ToArray();
}
+ /// <summary>Try to parse a 'screen=X' command argument, which specifies the screen that should receive the command.</summary>
+ /// <param name="arg">The raw argument to parse.</param>
+ /// <param name="screen">The parsed screen ID, if any.</param>
+ /// <param name="error">The error which indicates an invalid screen ID, if applicable.</param>
+ /// <returns>Returns whether the screen ID was parsed successfully.</returns>
+ private bool TryParseScreenId(string arg, out int screen, out string error)
+ {
+ screen = -1;
+ error = null;
+
+ // skip non-screen arg
+ if (!arg.StartsWith("screen="))
+ return false;
+
+ // get screen ID
+ string rawScreen = arg.Substring("screen=".Length);
+ if (!int.TryParse(rawScreen, out screen))
+ {
+ error = $"invalid screen ID format: {rawScreen}";
+ return false;
+ }
+
+ // validate ID
+ if (!Context.HasScreenId(screen))
+ {
+ error = $"there's no active screen with ID {screen}. Active screen IDs: {string.Join(", ", Context.ActiveScreenIds)}.";
+ return false;
+ }
+
+ return true;
+ }
+
/// <summary>Get a normalized command name.</summary>
/// <param name="name">The command name.</param>
private string GetNormalizedName(string name)
diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs
index b8730a00..baf3116e 100644
--- a/src/SMAPI/Framework/Commands/HelpCommand.cs
+++ b/src/SMAPI/Framework/Commands/HelpCommand.cs
@@ -41,13 +41,26 @@ namespace StardewModdingAPI.Framework.Commands
{
Command result = this.CommandManager.Get(args[0]);
if (result == null)
- monitor.Log("There's no command with that name.", LogLevel.Error);
+ monitor.Log("There's no command with that name. Type 'help' by itself for more info.", LogLevel.Error);
else
monitor.Log($"{result.Name}: {result.Documentation}{(result.Mod != null ? $"\n(Added by {result.Mod.DisplayName}.)" : "")}", LogLevel.Info);
}
else
{
- string message = "The following commands are registered:\n";
+ string message =
+ "\n\n"
+ + "Need help with a SMAPI or mod issue?\n"
+ + "------------------------------------\n"
+ + "See https://smapi.io/help for the best places to ask.\n\n\n"
+ + "How commands work\n"
+ + "-----------------\n"
+ + "Just enter a command directly to run it, just like you did for this help command. Commands may take optional arguments\n"
+ + "which change what they do; for example, type 'help help' to see help about the help command. When playing in split-screen\n"
+ + "mode, you can add screen=X to send the command to a specific screen instance.\n\n\n"
+ + "Valid commands\n"
+ + "--------------\n"
+ + "The following commands are registered. For more info about a command, type 'help command_name'.\n\n";
+
IGrouping<string, string>[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName).ToArray();
foreach (var group in groups)
{
@@ -55,7 +68,6 @@ namespace StardewModdingAPI.Framework.Commands
string[] commandNames = group.ToArray();
message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n";
}
- message += "For more information about a command, type 'help command_name'.";
monitor.Log(message, LogLevel.Info);
}
diff --git a/src/SMAPI/Framework/Content/TilesheetReference.cs b/src/SMAPI/Framework/Content/TilesheetReference.cs
new file mode 100644
index 00000000..2ea38430
--- /dev/null
+++ b/src/SMAPI/Framework/Content/TilesheetReference.cs
@@ -0,0 +1,33 @@
+namespace StardewModdingAPI.Framework.Content
+{
+ /// <summary>Basic metadata about a vanilla tilesheet.</summary>
+ internal class TilesheetReference
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The tilesheet's index in the list.</summary>
+ public readonly int Index;
+
+ /// <summary>The tilesheet's unique ID in the map.</summary>
+ public readonly string Id;
+
+ /// <summary>The asset path for the tilesheet texture.</summary>
+ public readonly string ImageSource;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="index">The tilesheet's index in the list.</param>
+ /// <param name="id">The tilesheet's unique ID in the map.</param>
+ /// <param name="imageSource">The asset path for the tilesheet texture.</param>
+ public TilesheetReference(int index, string id, string imageSource)
+ {
+ this.Index = index;
+ this.Id = id;
+ this.ImageSource = imageSource;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 3d5bb29d..27fb3dbb 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -54,6 +54,9 @@ namespace StardewModdingAPI.Framework
/// <remarks>The game may adds content managers in asynchronous threads (e.g. when populating the load screen).</remarks>
private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim();
+ /// <summary>A cache of ordered tilesheet IDs used by vanilla maps.</summary>
+ private readonly IDictionary<string, TilesheetReference[]> VanillaTilesheets = new Dictionary<string, TilesheetReference[]>(StringComparer.OrdinalIgnoreCase);
+
/// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary>
private readonly LocalizedContentManager VanillaContentManager;
@@ -293,21 +296,21 @@ namespace StardewModdingAPI.Framework
});
}
- /// <summary>Get a vanilla asset without interception.</summary>
- /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <summary>Get the tilesheet ID order used by the unmodified version of a map asset.</summary>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- public bool TryLoadVanillaAsset<T>(string assetName, out T asset)
+ public TilesheetReference[] GetVanillaTilesheetIds(string assetName)
{
- try
+ if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets))
{
- asset = this.VanillaContentManager.Load<T>(assetName);
- return true;
- }
- catch
- {
- asset = default;
- return false;
+ tilesheets = this.TryLoadVanillaAsset(assetName, out Map map)
+ ? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource)).ToArray()
+ : null;
+
+ this.VanillaTilesheets[assetName] = tilesheets;
+ this.VanillaContentManager.Unload();
}
+
+ return tilesheets ?? new TilesheetReference[0];
}
/// <summary>Dispose held resources.</summary>
@@ -341,5 +344,23 @@ namespace StardewModdingAPI.Framework
this.ContentManagers.Remove(contentManager)
);
}
+
+ /// <summary>Get a vanilla asset without interception.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="asset">The loaded asset data.</param>
+ private bool TryLoadVanillaAsset<T>(string assetName, out T asset)
+ {
+ try
+ {
+ asset = this.VanillaContentManager.Load<T>(assetName);
+ return true;
+ }
+ catch
+ {
+ asset = default;
+ return false;
+ }
+ }
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 424d6ff3..3db3856f 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -11,7 +11,6 @@ using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewValley;
using xTile;
-using xTile.Tiles;
namespace StardewModdingAPI.Framework.ContentManagers
{
@@ -398,14 +397,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
// when replacing a map, the vanilla tilesheets must have the same order and IDs
- if (data is Map loadedMap && this.Coordinator.TryLoadVanillaAsset(info.AssetName, out Map vanillaMap))
+ if (data is Map loadedMap)
{
- for (int i = 0; i < vanillaMap.TileSheets.Count; i++)
+ TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName);
+ foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs)
{
- // check for match
- TileSheet vanillaSheet = vanillaMap.TileSheets[i];
- bool found = this.TryFindTilesheet(loadedMap, vanillaSheet.Id, out int loadedIndex, out TileSheet loadedSheet);
- if (found && loadedIndex == i)
+ // skip if match
+ if (loadedMap.TileSheets.Count > vanillaSheet.Index && loadedMap.TileSheets[vanillaSheet.Index].Id == vanillaSheet.Id)
continue;
// handle mismatch
@@ -414,18 +412,18 @@ namespace StardewModdingAPI.Framework.ContentManagers
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining");
-
- string reason = found
- ? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\n\nTechnical details for mod author:\nExpected order [{string.Join(", ", vanillaMap.TileSheets.Select(p => $"'{p.ImageSource}' (id: {p.Id})"))}], but found tilesheet '{vanillaSheet.Id}' at index {loadedIndex} instead of {i}. Make sure custom tilesheet IDs are prefixed with 'z_' to avoid reordering tilesheets."
+ int loadedIndex = this.TryFindTilesheet(loadedMap, vanillaSheet.Id);
+ string reason = loadedIndex != -1
+ ? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewcommunitywiki.com/Modding:Maps#Tilesheet_order for help."
: $"mod has no tilesheet with ID '{vanillaSheet.Id}'. Map replacements must keep the original tilesheets to avoid errors or crashes.";
SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
if (isFarmMap)
{
- mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': {reason}", LogLevel.Error);
+ mod.LogAsMod($"SMAPI blocked '{info.AssetName}' map load: {reason}", LogLevel.Error);
return false;
}
- mod.LogAsMod($"SMAPI detected a potential issue with asset replacement for '{info.AssetName}' map: {reason}", LogLevel.Warn);
+ mod.LogAsMod($"SMAPI found an issue with '{info.AssetName}' map load: {reason}", LogLevel.Warn);
}
}
}
@@ -436,23 +434,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Find a map tilesheet by ID.</summary>
/// <param name="map">The map whose tilesheets to search.</param>
/// <param name="id">The tilesheet ID to match.</param>
- /// <param name="index">The matched tilesheet index, if any.</param>
- /// <param name="tilesheet">The matched tilesheet, if any.</param>
- private bool TryFindTilesheet(Map map, string id, out int index, out TileSheet tilesheet)
+ private int TryFindTilesheet(Map map, string id)
{
for (int i = 0; i < map.TileSheets.Count; i++)
{
if (map.TileSheets[i].Id == id)
- {
- index = i;
- tilesheet = map.TileSheets[i];
- return true;
- }
+ return i;
}
- index = -1;
- tilesheet = null;
- return false;
+ return -1;
}
}
}
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 9092669f..665dbfe3 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using StardewModdingAPI.Events;
-using StardewModdingAPI.Framework.PerformanceMonitoring;
namespace StardewModdingAPI.Framework.Events
{
@@ -178,13 +177,12 @@ namespace StardewModdingAPI.Framework.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
- /// <param name="performanceMonitor">Tracks performance metrics.</param>
- public EventManager(ModRegistry modRegistry, PerformanceMonitor performanceMonitor)
+ public EventManager(ModRegistry modRegistry)
{
// create shortcut initializers
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName, bool isPerformanceCritical = false)
{
- return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry, performanceMonitor, isPerformanceCritical);
+ return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry, isPerformanceCritical);
}
// init events (new)
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index f2dfb2ab..2204966c 100644
--- a/src/SMAPI/Framework/Events/ManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/ManagedEvent.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Events;
-using StardewModdingAPI.Framework.PerformanceMonitoring;
namespace StardewModdingAPI.Framework.Events
{
@@ -17,9 +16,6 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>The mod registry with which to identify mods.</summary>
protected readonly ModRegistry ModRegistry;
- /// <summary>Tracks performance metrics.</summary>
- private readonly PerformanceMonitor PerformanceMonitor;
-
/// <summary>The underlying event handlers.</summary>
private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new List<ManagedEventHandler<TEventArgs>>();
@@ -49,13 +45,11 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
- /// <param name="performanceMonitor">Tracks performance metrics.</param>
/// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
- public ManagedEvent(string eventName, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
+ public ManagedEvent(string eventName, ModRegistry modRegistry, bool isPerformanceCritical = false)
{
this.EventName = eventName;
this.ModRegistry = modRegistry;
- this.PerformanceMonitor = performanceMonitor;
this.IsPerformanceCritical = isPerformanceCritical;
}
@@ -126,40 +120,26 @@ namespace StardewModdingAPI.Framework.Events
}
// raise event
- this.PerformanceMonitor.Track(this.EventName, () =>
+ foreach (ManagedEventHandler<TEventArgs> handler in handlers)
{
- foreach (ManagedEventHandler<TEventArgs> handler in handlers)
- {
- if (match != null && !match(handler.SourceMod))
- continue;
+ if (match != null && !match(handler.SourceMod))
+ continue;
- try
- {
- this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args));
- }
- catch (Exception ex)
- {
- this.LogError(handler, ex);
- }
+ try
+ {
+ handler.Handler.Invoke(null, args);
+ }
+ catch (Exception ex)
+ {
+ this.LogError(handler, ex);
}
- });
+ }
}
/*********
** Private methods
*********/
- /// <summary>Get the mod name for a given event handler to display in performance monitoring reports.</summary>
- /// <param name="handler">The event handler.</param>
- private string GetModNameForPerformanceCounters(ManagedEventHandler<TEventArgs> handler)
- {
- IModMetadata mod = handler.SourceMod;
-
- return mod.HasManifest()
- ? mod.Manifest.UniqueID
- : mod.DisplayName;
- }
-
/// <summary>Log an exception from an event handler.</summary>
/// <param name="handler">The event handler instance.</param>
/// <param name="ex">The exception that was raised.</param>
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs b/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs
deleted file mode 100644
index af630055..00000000
--- a/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace StardewModdingAPI.Framework.PerformanceMonitoring
-{
- /// <summary>The context for an alert.</summary>
- internal readonly struct AlertContext
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The source which triggered the alert.</summary>
- public string Source { get; }
-
- /// <summary>The elapsed milliseconds.</summary>
- public double Elapsed { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="source">The source which triggered the alert.</param>
- /// <param name="elapsed">The elapsed milliseconds.</param>
- public AlertContext(string source, double elapsed)
- {
- this.Source = source;
- this.Elapsed = elapsed;
- }
-
- /// <summary>Get a human-readable text form of this instance.</summary>
- public override string ToString()
- {
- return $"{this.Source}: {this.Elapsed:F2}ms";
- }
- }
-}
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs b/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs
deleted file mode 100644
index d5a0b343..00000000
--- a/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace StardewModdingAPI.Framework.PerformanceMonitoring
-{
- /// <summary>A single alert entry.</summary>
- internal readonly struct AlertEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The collection in which the alert occurred.</summary>
- public PerformanceCounterCollection Collection { get; }
-
- /// <summary>The actual execution time in milliseconds.</summary>
- public double ExecutionTimeMilliseconds { get; }
-
- /// <summary>The configured alert threshold in milliseconds.</summary>
- public double ThresholdMilliseconds { get; }
-
- /// <summary>The sources involved in exceeding the threshold.</summary>
- public AlertContext[] Context { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="collection">The collection in which the alert occurred.</param>
- /// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
- /// <param name="thresholdMilliseconds">The configured alert threshold in milliseconds.</param>
- /// <param name="context">The sources involved in exceeding the threshold.</param>
- public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext[] context)
- {
- this.Collection = collection;
- this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
- this.ThresholdMilliseconds = thresholdMilliseconds;
- this.Context = context;
- }
- }
-}
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs b/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs
deleted file mode 100644
index 1746e358..00000000
--- a/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Framework.PerformanceMonitoring
-{
- /// <summary>A peak invocation time.</summary>
- internal readonly struct PeakEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The actual execution time in milliseconds.</summary>
- public double ExecutionTimeMilliseconds { get; }
-
- /// <summary>When the entry occurred.</summary>
- public DateTime EventTime { get; }
-
- /// <summary>The sources involved in exceeding the threshold.</summary>
- public AlertContext[] Context { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
- /// <param name="eventTime">When the entry occurred.</param>
- /// <param name="context">The sources involved in exceeding the threshold.</param>
- public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, AlertContext[] context)
- {
- this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
- this.EventTime = eventTime;
- this.Context = context;
- }
- }
-}
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs
deleted file mode 100644
index 42825999..00000000
--- a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace StardewModdingAPI.Framework.PerformanceMonitoring
-{
- /// <summary>Tracks metadata about a particular code event.</summary>
- internal class PerformanceCounter
- {
- /*********
- ** Fields
- *********/
- /// <summary>The size of the ring buffer.</summary>
- private readonly int MaxEntries = 16384;
-
- /// <summary>The collection to which this performance counter belongs.</summary>
- private readonly PerformanceCounterCollection ParentCollection;
-
- /// <summary>The performance counter entries.</summary>
- private readonly Stack<PerformanceCounterEntry> Entries;
-
- /// <summary>The entry with the highest execution time.</summary>
- private PerformanceCounterEntry? PeakPerformanceCounterEntry;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>The name of the source.</summary>
- public string Source { get; }
-
- /// <summary>The alert threshold in milliseconds</summary>
- public double AlertThresholdMilliseconds { get; set; }
-
- /// <summary>If alerting is enabled or not</summary>
- public bool EnableAlerts { get; set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="parentCollection">The collection to which this performance counter belongs.</param>
- /// <param name="source">The name of the source.</param>
- public PerformanceCounter(PerformanceCounterCollection parentCollection, string source)
- {
- this.ParentCollection = parentCollection;
- this.Source = source;
- this.Entries = new Stack<PerformanceCounterEntry>(this.MaxEntries);
- }
-
- /// <summary>Add a performance counter entry to the list, update monitoring, and raise alerts if needed.</summary>
- /// <param name="entry">The entry to add.</param>
- public void Add(PerformanceCounterEntry entry)
- {
- // add entry
- if (this.Entries.Count > this.MaxEntries)
- this.Entries.Pop();
- this.Entries.Push(entry);
-
- // update metrics
- if (this.PeakPerformanceCounterEntry == null || entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds)
- this.PeakPerformanceCounterEntry = entry;
-
- // raise alert
- if (this.EnableAlerts && entry.ElapsedMilliseconds > this.AlertThresholdMilliseconds)
- this.ParentCollection.AddAlert(entry.ElapsedMilliseconds, this.AlertThresholdMilliseconds, new AlertContext(this.Source, entry.ElapsedMilliseconds));
- }
-
- /// <summary>Clear all performance counter entries and monitoring.</summary>
- public void Reset()
- {
- this.Entries.Clear();
- this.PeakPerformanceCounterEntry = null;
- }
-
- /// <summary>Get the peak entry.</summary>
- public PerformanceCounterEntry? GetPeak()
- {
- return this.PeakPerformanceCounterEntry;
- }
-
- /// <summary>Get the entry with the highest execution time.</summary>
- /// <param name="range">The time range to search.</param>
- /// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
- public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? endTime = null)
- {
- endTime ??= DateTime.UtcNow;
- DateTime startTime = endTime.Value.Subtract(range);
-
- return this.Entries
- .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
- .OrderByDescending(x => x.ElapsedMilliseconds)
- .FirstOrDefault();
- }
-
- /// <summary>Get the last entry added to the list.</summary>
- public PerformanceCounterEntry? GetLastEntry()
- {
- if (this.Entries.Count == 0)
- return null;
-
- return this.Entries.Peek();
- }
-
- /// <summary>Get the average over a given time span.</summary>
- /// <param name="range">The time range to search.</param>
- /// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
- public double GetAverage(TimeSpan range, DateTime? endTime = null)
- {
- endTime ??= DateTime.UtcNow;
- DateTime startTime = endTime.Value.Subtract(range);
-
- double[] entries = this.Entries
- .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
- .Select(p => p.ElapsedMilliseconds)
- .ToArray();
-
- return entries.Length > 0
- ? entries.Average()
- : 0;
- }