fe84fd558e
Code comments were drifting into plan-internal shorthand (DI-2a, Slice B, "see plan §9") that nobody outside the cycle authors can decode. They also tended toward AI-generated paragraph blocks where a two-line WHY would have done. This commit tightens the comment surface from the v1.5.0 work: - IPluginLogProxy header lists the consumer buckets without naming the cycle items that decided them. - DalamudLogger / DalamudLoggingProvider provenance markers explain themselves in two lines each; the long EUPL-rationale paragraph moves to the commit message. - PluginHostFactory block headers shrink to one line each, ASCII dividers come out, plan-internal codes go. - Plugin.cs field doc and Phase-1 / DisposeAsync comments lose the cycle-name references; the file gains nothing from "C3 surfaced X" in code. - FontManager / GameFunctions static-method notes shrink to one sentence each. - InitHostedServices class header keeps the eager-resolve WHY in three lines, drops the constraint label. Csharpier reformatted the .csproj layout (long PackageReference multi-lined). No functional change, no behavior change.
144 lines
4.0 KiB
C#
144 lines
4.0 KiB
C#
using System.Runtime.ExceptionServices;
|
|
using Dalamud.Plugin.Services;
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
namespace HellionChat;
|
|
|
|
// Orchestrates Host.StartAsync / StopAsync and the framework-thread dispose.
|
|
// Plugin.ctor builds the host and assigns it via the Host property, so
|
|
// PluginLifecycle never constructs the host itself.
|
|
internal sealed class PluginLifecycle : IAsyncDisposable
|
|
{
|
|
private readonly IFramework _framework;
|
|
private readonly Plugin _plugin;
|
|
|
|
private int _disposeStarted;
|
|
private bool _hostStartRequested;
|
|
|
|
public PluginLifecycle(IFramework framework, Plugin plugin)
|
|
{
|
|
_framework = framework;
|
|
_plugin = plugin;
|
|
}
|
|
|
|
// Plugin.ctor fills this immediately after PluginHostFactory.Build and
|
|
// before invoking LoadAsync; LoadAsync may NRE-suppress on Host! safely.
|
|
public IHost? Host { get; set; }
|
|
|
|
public async Task LoadAsync(CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
try
|
|
{
|
|
_hostStartRequested = true;
|
|
await Host!.StartAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
// WindowSystem.AddWindow mutates an internal List<>; v1.4.9 Stage-2
|
|
// verified the list is non-thread-safe, so we marshal the entire
|
|
// registration block to the framework thread.
|
|
await _framework
|
|
.RunOnFrameworkThread(() => RegisterWindows(_plugin))
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch
|
|
{
|
|
try
|
|
{
|
|
await DisposeAsync().ConfigureAwait(false);
|
|
}
|
|
catch
|
|
{
|
|
// Swallow secondary dispose failure so the original load throw wins.
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static void RegisterWindows(Plugin plugin)
|
|
{
|
|
plugin.WindowSystem.AddWindow(plugin.ChatLogWindow);
|
|
plugin.WindowSystem.AddWindow(plugin.SettingsWindow);
|
|
plugin.WindowSystem.AddWindow(plugin.DbViewer);
|
|
plugin.WindowSystem.AddWindow(plugin.InputPreview);
|
|
plugin.WindowSystem.AddWindow(plugin.CommandHelpWindow);
|
|
plugin.WindowSystem.AddWindow(plugin.SeStringDebugger);
|
|
plugin.WindowSystem.AddWindow(plugin.DebuggerWindow);
|
|
plugin.WindowSystem.AddWindow(plugin.FirstRunWizard);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
// Idempotency guard — Dalamud may fire DisposeAsync twice in a reload race.
|
|
if (Interlocked.Exchange(ref _disposeStarted, 1) != 0)
|
|
return;
|
|
|
|
Exception? failure = null;
|
|
|
|
if (_hostStartRequested && Host is not null)
|
|
failure = await CaptureFailureAsync(failure, () => Host.StopAsync())
|
|
.ConfigureAwait(false);
|
|
|
|
failure = await DisposeHostOnFrameworkThreadAsync(failure).ConfigureAwait(false);
|
|
|
|
ThrowIfFailed(failure);
|
|
}
|
|
|
|
private async Task<Exception?> DisposeHostOnFrameworkThreadAsync(Exception? failure)
|
|
{
|
|
try
|
|
{
|
|
await _framework
|
|
.RunOnFrameworkThread(() =>
|
|
{
|
|
failure = CaptureFailure(failure, () => Host?.Dispose());
|
|
})
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure ??= ex;
|
|
}
|
|
|
|
return failure;
|
|
}
|
|
|
|
private static Exception? CaptureFailure(Exception? failure, Action action)
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure ??= ex;
|
|
}
|
|
|
|
return failure;
|
|
}
|
|
|
|
private static async ValueTask<Exception?> CaptureFailureAsync(
|
|
Exception? failure,
|
|
Func<Task> action
|
|
)
|
|
{
|
|
try
|
|
{
|
|
await action().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure ??= ex;
|
|
}
|
|
|
|
return failure;
|
|
}
|
|
|
|
private static void ThrowIfFailed(Exception? failure)
|
|
{
|
|
if (failure is not null)
|
|
ExceptionDispatchInfo.Capture(failure).Throw();
|
|
}
|
|
}
|