diff --git a/Dalamud/Configuration/Internal/DevPluginSettings.cs b/Dalamud/Configuration/Internal/DevPluginSettings.cs
index 361632a14..64327e658 100644
--- a/Dalamud/Configuration/Internal/DevPluginSettings.cs
+++ b/Dalamud/Configuration/Internal/DevPluginSettings.cs
@@ -12,16 +12,22 @@ internal sealed class DevPluginSettings
///
public bool StartOnBoot { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating whether we should show notifications for errors this plugin
+ /// is creating.
+ ///
+ public bool NotifyForErrors { get; set; } = true;
+
///
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
///
public bool AutomaticReloading { get; set; } = false;
-
+
///
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
///
public Guid WorkingPluginId { get; set; } = Guid.Empty;
-
+
///
/// Gets or sets a list of validation problems that have been dismissed by the user.
///
diff --git a/Dalamud/Console/ConsoleManagerPluginScoped.cs b/Dalamud/Console/ConsoleManagerPluginScoped.cs
index eb1f6fffc..41949c7d7 100644
--- a/Dalamud/Console/ConsoleManagerPluginScoped.cs
+++ b/Dalamud/Console/ConsoleManagerPluginScoped.cs
@@ -11,6 +11,47 @@ namespace Dalamud.Console;
#pragma warning disable Dalamud001
+///
+/// Utility functions for the console manager.
+///
+internal static partial class ConsoleManagerPluginUtil
+{
+ private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
+
+ ///
+ /// Get a sanitized namespace name from a plugin's internal name.
+ ///
+ /// The plugin's internal name.
+ /// A sanitized namespace.
+ public static string GetSanitizedNamespaceName(string pluginInternalName)
+ {
+ // Must be lowercase
+ pluginInternalName = pluginInternalName.ToLowerInvariant();
+
+ // Remove all non-alphabetic characters
+ pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
+
+ // Remove reserved namespaces from the start or end
+ foreach (var reservedNamespace in ReservedNamespaces)
+ {
+ if (pluginInternalName.StartsWith(reservedNamespace))
+ {
+ pluginInternalName = pluginInternalName[reservedNamespace.Length..];
+ }
+
+ if (pluginInternalName.EndsWith(reservedNamespace))
+ {
+ pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
+ }
+ }
+
+ return pluginInternalName;
+ }
+
+ [GeneratedRegex(@"[^a-z]")]
+ private static partial Regex NonAlphaRegex();
+}
+
///
/// Plugin-scoped version of the console service.
///
@@ -130,44 +171,3 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
return command;
}
}
-
-///
-/// Utility functions for the console manager.
-///
-internal static partial class ConsoleManagerPluginUtil
-{
- private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
-
- ///
- /// Get a sanitized namespace name from a plugin's internal name.
- ///
- /// The plugin's internal name.
- /// A sanitized namespace.
- public static string GetSanitizedNamespaceName(string pluginInternalName)
- {
- // Must be lowercase
- pluginInternalName = pluginInternalName.ToLowerInvariant();
-
- // Remove all non-alphabetic characters
- pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
-
- // Remove reserved namespaces from the start or end
- foreach (var reservedNamespace in ReservedNamespaces)
- {
- if (pluginInternalName.StartsWith(reservedNamespace))
- {
- pluginInternalName = pluginInternalName[reservedNamespace.Length..];
- }
-
- if (pluginInternalName.EndsWith(reservedNamespace))
- {
- pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
- }
- }
-
- return pluginInternalName;
- }
-
- [GeneratedRegex(@"[^a-z]")]
- private static partial Regex NonAlphaRegex();
-}
diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs
index 82c7f5f6c..88f9d0bb6 100644
--- a/Dalamud/Game/Framework.cs
+++ b/Dalamud/Game/Framework.cs
@@ -12,6 +12,7 @@ using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -47,7 +48,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
private readonly ConcurrentDictionary
tickDelayedTaskCompletionSources = new();
- private ulong tickCounter;
+ private ulong tickCounter;
[ServiceManager.ServiceConstructor]
private unsafe Framework()
@@ -504,14 +505,18 @@ internal sealed class Framework : IInternalDisposableService, IFramework
#pragma warning restore SA1015
internal class FrameworkPluginScoped : IInternalDisposableService, IFramework
{
+ private readonly PluginErrorHandler pluginErrorHandler;
+
[ServiceManager.ServiceDependency]
private readonly Framework frameworkService = Service.Get();
///
/// Initializes a new instance of the class.
///
- internal FrameworkPluginScoped()
+ /// Error handler instance.
+ internal FrameworkPluginScoped(PluginErrorHandler pluginErrorHandler)
{
+ this.pluginErrorHandler = pluginErrorHandler;
this.frameworkService.Update += this.OnUpdateForward;
}
@@ -604,7 +609,7 @@ internal class FrameworkPluginScoped : IInternalDisposableService, IFramework
}
else
{
- this.Update?.Invoke(framework);
+ this.pluginErrorHandler.InvokeAndCatch(this.Update, $"{nameof(IFramework)}::{nameof(IFramework.Update)}", framework);
}
}
}
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index 8ba579d17..9760b601d 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -308,8 +308,14 @@ internal class DalamudInterface : IInternalDisposableService
///
/// Opens the .
///
- public void OpenLogWindow()
+ /// The filter to set, if not null.
+ public void OpenLogWindow(string? textFilter = "")
{
+ if (textFilter != null)
+ {
+ this.consoleWindow.TextFilter = textFilter;
+ }
+
this.consoleWindow.IsOpen = true;
this.consoleWindow.BringToFront();
}
diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
index f7ce5d145..8ef49fffc 100644
--- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
@@ -41,9 +41,9 @@ internal class ConsoleWindow : Window, IDisposable
// Fields below should be touched only from the main thread.
private readonly RollingList logText;
private readonly RollingList filteredLogEntries;
-
+
private readonly List pluginFilters = new();
-
+
private readonly DalamudConfiguration configuration;
private int newRolledLines;
@@ -87,14 +87,14 @@ internal class ConsoleWindow : Window, IDisposable
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
{
this.configuration = configuration;
-
+
this.autoScroll = configuration.LogAutoScroll;
this.autoOpen = configuration.LogOpenAtStartup;
Service.GetAsync().ContinueWith(r => r.Result.Update += this.FrameworkOnUpdate);
-
+
var cm = Service.Get();
- cm.AddCommand("clear", "Clear the console log", () =>
+ cm.AddCommand("clear", "Clear the console log", () =>
{
this.QueueClear();
return true;
@@ -123,6 +123,19 @@ internal class ConsoleWindow : Window, IDisposable
/// Gets the queue where log entries that are not processed yet are stored.
public static ConcurrentQueue<(string Line, LogEvent LogEvent)> NewLogEntries { get; } = new();
+ ///
+ /// Gets or sets the current text filter.
+ ///
+ public string TextFilter
+ {
+ get => this.textFilter;
+ set
+ {
+ this.textFilter = value;
+ this.RecompileLogFilter();
+ }
+ }
+
///
public override void OnOpen()
{
@@ -578,7 +591,7 @@ internal class ConsoleWindow : Window, IDisposable
inputWidth = ImGui.GetWindowWidth() - (ImGui.GetStyle().WindowPadding.X * 2);
if (!breakInputLines)
- inputWidth = (inputWidth - ImGui.GetStyle().ItemSpacing.X) / 2;
+ inputWidth = (inputWidth - ImGui.GetStyle().ItemSpacing.X) / 2;
}
else
{
@@ -622,24 +635,29 @@ internal class ConsoleWindow : Window, IDisposable
ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll)
|| ImGui.IsItemDeactivatedAfterEdit())
{
- this.compiledLogFilter = null;
- this.exceptionLogFilter = null;
- try
- {
- this.compiledLogFilter = new(this.textFilter, RegexOptions.IgnoreCase);
-
- this.QueueRefilter();
- }
- catch (Exception e)
- {
- this.exceptionLogFilter = e;
- }
-
- foreach (var log in this.logText)
- log.HighlightMatches = null;
+ this.RecompileLogFilter();
}
}
+ private void RecompileLogFilter()
+ {
+ this.compiledLogFilter = null;
+ this.exceptionLogFilter = null;
+ try
+ {
+ this.compiledLogFilter = new(this.textFilter, RegexOptions.IgnoreCase);
+
+ this.QueueRefilter();
+ }
+ catch (Exception e)
+ {
+ this.exceptionLogFilter = e;
+ }
+
+ foreach (var log in this.logText)
+ log.HighlightMatches = null;
+ }
+
private void DrawSettingsPopup()
{
if (ImGui.Checkbox("Open at startup", ref this.autoOpen))
@@ -799,15 +817,15 @@ internal class ConsoleWindow : Window, IDisposable
{
if (string.IsNullOrEmpty(this.commandText))
return;
-
+
this.historyPos = -1;
-
+
if (this.commandText != this.configuration.LogCommandHistory.LastOrDefault())
this.configuration.LogCommandHistory.Add(this.commandText);
-
+
if (this.configuration.LogCommandHistory.Count > HistorySize)
this.configuration.LogCommandHistory.RemoveAt(0);
-
+
this.configuration.QueueSave();
this.lastCmdSuccess = Service.Get().ProcessCommand(this.commandText);
@@ -832,7 +850,7 @@ internal class ConsoleWindow : Window, IDisposable
this.completionZipText = null;
this.completionTabIdx = 0;
break;
-
+
case ImGuiInputTextFlags.CallbackCompletion:
var textBytes = new byte[data->BufTextLen];
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
@@ -843,11 +861,11 @@ internal class ConsoleWindow : Window, IDisposable
// We can't do any completion for parameters at the moment since it just calls into CommandHandler
if (words.Length > 1)
return 0;
-
+
var wordToComplete = words[0];
if (wordToComplete.IsNullOrWhitespace())
return 0;
-
+
if (this.completionZipText is not null)
wordToComplete = this.completionZipText;
@@ -878,7 +896,7 @@ internal class ConsoleWindow : Window, IDisposable
toComplete = candidates.ElementAt(this.completionTabIdx);
this.completionTabIdx = (this.completionTabIdx + 1) % candidates.Count();
}
-
+
if (toComplete != null)
{
ptr.DeleteChars(0, ptr.BufTextLen);
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
index cfcad2ff4..c1bd64447 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
@@ -3569,6 +3569,24 @@ internal class PluginInstallerWindow : Window, IDisposable
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading);
}
+
+ // Error Notifications
+ ImGui.PushStyleColor(ImGuiCol.Button, plugin.NotifyForErrors ? greenColor : redColor);
+ ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.NotifyForErrors ? greenColor : redColor);
+
+ ImGui.SameLine();
+ if (ImGuiComponents.IconButton(FontAwesomeIcon.Bolt))
+ {
+ plugin.NotifyForErrors ^= true;
+ configuration.QueueSave();
+ }
+
+ ImGui.PopStyleColor(2);
+
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.SetTooltip(Locs.PluginButtonToolTip_NotifyForErrors);
+ }
}
}
@@ -4239,6 +4257,8 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading");
+ public static string PluginButtonToolTip_NotifyForErrors => Loc.Localize("InstallerNotifyForErrors", "Show Dalamud notifications when this plugin is creating errors");
+
public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin");
public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game.");
diff --git a/Dalamud/Logging/ScopedPluginLogService.cs b/Dalamud/Logging/ScopedPluginLogService.cs
index 7305aa87b..5b0ca15e5 100644
--- a/Dalamud/Logging/ScopedPluginLogService.cs
+++ b/Dalamud/Logging/ScopedPluginLogService.cs
@@ -1,5 +1,6 @@
using Dalamud.IoC;
using Dalamud.IoC.Internal;
+using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
@@ -20,6 +21,7 @@ namespace Dalamud.Logging;
internal class ScopedPluginLogService : IServiceType, IPluginLog
{
private readonly LocalPlugin localPlugin;
+ private readonly PluginErrorHandler errorHandler;
private readonly LoggingLevelSwitch levelSwitch;
@@ -27,10 +29,12 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
/// Initializes a new instance of the class.
///
/// The plugin that owns this service.
- internal ScopedPluginLogService(LocalPlugin localPlugin)
+ /// Error notifier service.
+ internal ScopedPluginLogService(LocalPlugin localPlugin, PluginErrorHandler errorHandler)
{
this.localPlugin = localPlugin;
-
+ this.errorHandler = errorHandler;
+
this.levelSwitch = new LoggingLevelSwitch(this.GetDefaultLevel());
var loggerConfiguration = new LoggerConfiguration()
@@ -40,7 +44,7 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
this.Logger = loggerConfiguration.CreateLogger();
}
-
+
///
public ILogger Logger { get; }
@@ -50,7 +54,7 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
get => this.levelSwitch.MinimumLevel;
set => this.levelSwitch.MinimumLevel = value;
}
-
+
///
public void Fatal(string messageTemplate, params object[] values) =>
this.Write(LogEventLevel.Fatal, null, messageTemplate, values);
@@ -82,11 +86,11 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
///
public void Information(Exception? exception, string messageTemplate, params object[] values) =>
this.Write(LogEventLevel.Information, exception, messageTemplate, values);
-
+
///
public void Info(string messageTemplate, params object[] values) =>
this.Information(messageTemplate, values);
-
+
///
public void Info(Exception? exception, string messageTemplate, params object[] values) =>
this.Information(exception, messageTemplate, values);
@@ -106,10 +110,13 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
///
public void Verbose(Exception? exception, string messageTemplate, params object[] values) =>
this.Write(LogEventLevel.Verbose, exception, messageTemplate, values);
-
+
///
public void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values)
{
+ if (level == LogEventLevel.Error)
+ this.errorHandler.NotifyError();
+
this.Logger.Write(
level,
exception: exception,
@@ -124,7 +131,7 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog
private LogEventLevel GetDefaultLevel()
{
// TODO: Add some way to save log levels to a config. Or let plugins handle it?
-
+
return this.localPlugin.IsDev ? LogEventLevel.Verbose : LogEventLevel.Debug;
}
}
diff --git a/Dalamud/Plugin/Internal/PluginErrorHandler.cs b/Dalamud/Plugin/Internal/PluginErrorHandler.cs
new file mode 100644
index 000000000..54589595c
--- /dev/null
+++ b/Dalamud/Plugin/Internal/PluginErrorHandler.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+using Dalamud.Interface;
+using Dalamud.Interface.ImGuiNotification;
+using Dalamud.Interface.ImGuiNotification.Internal;
+using Dalamud.Interface.Internal;
+using Dalamud.Plugin.Internal.Types;
+
+using ImGuiNET;
+
+using Serilog;
+
+namespace Dalamud.Plugin.Internal;
+
+///
+/// Service responsible for notifying the user when a plugin is creating errors.
+///
+[ServiceManager.ScopedService]
+internal class PluginErrorHandler : IServiceType
+{
+ private readonly LocalPlugin plugin;
+ private readonly NotificationManager notificationManager;
+ private readonly DalamudInterface di;
+
+ private readonly Dictionary invokerCache = new();
+
+ private DateTime lastErrorTime = DateTime.MinValue;
+ private IActiveNotification? activeNotification;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The plugin we are notifying for.
+ /// The notification manager.
+ /// The dalamud interface class.
+ [ServiceManager.ServiceConstructor]
+ public PluginErrorHandler(LocalPlugin plugin, NotificationManager notificationManager, DalamudInterface di)
+ {
+ this.plugin = plugin;
+ this.notificationManager = notificationManager;
+ this.di = di;
+ }
+
+ ///
+ /// Invoke the specified delegate and catch any exceptions that occur.
+ /// Writes an error message to the log if an exception occurs and shows
+ /// a notification if the plugin is a dev plugin and the user has enabled error notifications.
+ ///
+ /// The delegate to invoke.
+ /// A hint to show about the origin of the exception if an error occurs.
+ /// Arguments to the event handler.
+ /// The type of the delegate.
+ /// Whether invocation was successful/did not throw an exception.
+ public bool InvokeAndCatch(
+ TDelegate? eventHandler,
+ string hint,
+ params object[] args)
+ where TDelegate : Delegate
+ {
+ if (eventHandler == null)
+ return true;
+
+ try
+ {
+ var invoker = this.GetInvoker();
+ invoker(eventHandler, args);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, $"[{this.plugin.InternalName}] Exception in event handler {{EventHandlerName}}", hint);
+ this.NotifyError();
+ return false;
+ }
+ }
+
+ ///
+ /// Show a notification, if the plugin is a dev plugin and the user has enabled error notifications.
+ /// This function has a cooldown built-in.
+ ///
+ public void NotifyError()
+ {
+ if (this.plugin is not LocalDevPlugin devPlugin)
+ return;
+
+ if (!devPlugin.NotifyForErrors)
+ return;
+
+ // If the notification is already active, we don't need to show it again.
+ if (this.activeNotification is { DismissReason: null })
+ return;
+
+ var now = DateTime.UtcNow;
+ if (now - this.lastErrorTime < TimeSpan.FromMinutes(2))
+ return;
+
+ this.lastErrorTime = now;
+
+ var creatingErrorsText = $"{devPlugin.Name} is creating errors";
+ var notification = new Notification()
+ {
+ Title = creatingErrorsText,
+ Icon = INotificationIcon.From(FontAwesomeIcon.Bolt),
+ Type = NotificationType.Error,
+ InitialDuration = TimeSpan.FromSeconds(15),
+ MinimizedText = creatingErrorsText,
+ Content = $"The plugin '{devPlugin.Name}' is creating errors. Click 'Show console' to learn more.",
+ RespectUiHidden = false,
+ };
+
+ this.activeNotification = this.notificationManager.AddNotification(notification);
+ this.activeNotification.DrawActions += _ =>
+ {
+ if (ImGui.Button("Show console"))
+ {
+ this.di.OpenLogWindow(this.plugin.InternalName);
+ this.activeNotification.DismissNow();
+ }
+
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.SetTooltip("Show the console filtered to this plugin");
+ }
+
+ ImGui.SameLine();
+
+ if (ImGui.Button("Disable notifications"))
+ {
+ devPlugin.NotifyForErrors = false;
+ this.activeNotification.DismissNow();
+ }
+
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.SetTooltip("Disable error notifications for this plugin");
+ }
+ };
+ }
+
+ private static Action CreateInvoker() where TDelegate : Delegate
+ {
+ var delegateType = typeof(TDelegate);
+ var method = delegateType.GetMethod("Invoke");
+ if (method == null)
+ throw new InvalidOperationException($"Delegate {delegateType} does not have an Invoke method.");
+
+ var parameters = method.GetParameters();
+
+ // Create parameters for the lambda
+ var delegateParam = Expression.Parameter(delegateType, "d");
+ var argsParam = Expression.Parameter(typeof(object[]), "args");
+
+ // Create expressions to convert array elements to parameter types
+ var callArgs = new Expression[parameters.Length];
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ var paramType = parameters[i].ParameterType;
+ var arrayAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
+ callArgs[i] = Expression.Convert(arrayAccess, paramType);
+ }
+
+ // Create the delegate invocation expression
+ var callExpr = Expression.Call(delegateParam, method, callArgs);
+
+ // If return type is not void, discard the result
+ Expression bodyExpr;
+ if (method.ReturnType != typeof(void))
+ {
+ // Create a block that executes the call and then returns void
+ bodyExpr = Expression.Block(
+ Expression.Call(delegateParam, method, callArgs),
+ Expression.Empty());
+ }
+ else
+ {
+ bodyExpr = callExpr;
+ }
+
+ // Compile and return the lambda
+ var lambda = Expression.Lambda>(
+ bodyExpr, delegateParam, argsParam);
+ return lambda.Compile();
+ }
+
+ private Action GetInvoker() where TDelegate : Delegate
+ {
+ var delegateType = typeof(TDelegate);
+
+ if (!this.invokerCache.TryGetValue(delegateType, out var cachedInvoker))
+ {
+ cachedInvoker = CreateInvoker();
+ this.invokerCache[delegateType] = cachedInvoker;
+ }
+
+ return (Action)cachedInvoker;
+ }
+}
diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
index b8f2b2708..34b54163a 100644
--- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
@@ -86,6 +86,16 @@ internal sealed class LocalDevPlugin : LocalPlugin
}
}
+ ///
+ /// Gets or sets a value indicating whether users should be notified when this plugin
+ /// is causing errors.
+ ///
+ public bool NotifyForErrors
+ {
+ get => this.devSettings.NotifyForErrors;
+ set => this.devSettings.NotifyForErrors = value;
+ }
+
///
/// Gets an ID uniquely identifying this specific instance of a devPlugin.
///
diff --git a/Dalamud/Service/ServiceManager.cs b/Dalamud/Service/ServiceManager.cs
index 92fe5ae41..9847f7147 100644
--- a/Dalamud/Service/ServiceManager.cs
+++ b/Dalamud/Service/ServiceManager.cs
@@ -152,7 +152,7 @@ internal static class ServiceManager
#if DEBUG
lock (LoadedServices)
{
- ProvideAllServices()
+ ProvideAllServices();
}
return;
diff --git a/Dalamud/Service/Service{T}.cs b/Dalamud/Service/Service{T}.cs
index c92c8baff..1f5558893 100644
--- a/Dalamud/Service/Service{T}.cs
+++ b/Dalamud/Service/Service{T}.cs
@@ -23,6 +23,9 @@ namespace Dalamud;
[SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "Service container static type")]
internal static class Service where T : IServiceType
{
+ // TODO: Service should only work with singleton services. Trying to call Service.Get() on a scoped service should
+ // be a compile-time error.
+
private static readonly ServiceManager.ServiceAttribute ServiceAttribute;
private static TaskCompletionSource instanceTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
private static List? dependencyServices;