Move migrations and service allocations from Phase-1 ctor to LoadAsync
Phase-1 was still doing 7 schema migrations and 25+ service allocations synchronously, blocking the ctor return. Move all of that to LoadAsync, keeping only bootstrap-essentials in the ctor: conflict detection, config load, language init, ImGui init, WindowSystem skeleton. Decouple the font task from the LoadAsync await — font-build runs fire-and-forget, so first frames render with Dalamud's default font until the Hellion-Exo2/NotoSans atlas rebuild completes (visible "font-pop"). Mirrors ChatTwo's pattern; the perceived-load win comes from "Finished loading" landing earlier, not from a faster atlas build.
This commit is contained in:
+70
-62
@@ -108,10 +108,11 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
public Plugin()
|
||||
{
|
||||
// v1.4.3 Phase 1 — sync ctor: only work that doesn't touch
|
||||
// Theme/Font/MessageManager/UI-windows lives here. Async Phase 2
|
||||
// (LoadAsync) owns those. Hooks subscribe at the END of LoadAsync
|
||||
// so Dalamud's first Draw tick never sees null services (B1).
|
||||
// Phase-1 ctor stays minimal: bootstrap-essentials only (conflict
|
||||
// gate, config load, language + ImGui init, WindowSystem skeleton).
|
||||
// Schema migrations and every service / window allocation moved to
|
||||
// LoadAsync so the sync ctor returns fast. On failure here nothing
|
||||
// is initialized yet, so just throw — there is nothing to clean up.
|
||||
|
||||
// Refuse to start if upstream Chat 2 is loaded — prevents IPC
|
||||
// channel collisions and double-replacement of the in-game chat
|
||||
@@ -134,6 +135,26 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// Drop them on load to guarantee the session-only invariant.
|
||||
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
ImGuiUtil.Initialize(this);
|
||||
|
||||
DeferredSaveFrames = -1;
|
||||
|
||||
// WindowSystem skeleton is initialised by the readonly field above —
|
||||
// no AddWindow yet; window construction lives in LoadAsync.
|
||||
}
|
||||
|
||||
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
// Schema migrations live in LoadAsync so the sync ctor returns fast.
|
||||
// Each block is self-contained and runs sequentially before any
|
||||
// service consumes Config; order matters because later versions
|
||||
// build on earlier ones (e.g. v16 reads opacity that v14 wrote).
|
||||
|
||||
// Hellion Chat v9 → v10 — wipes the configuration so the new 8-tab
|
||||
// layout starts from defaults instead of mapping every previous setting
|
||||
// to its new position. Backup-Failure ist non-fatal, der Wipe läuft
|
||||
@@ -448,83 +469,58 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
||||
}
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
ImGuiUtil.Initialize(this);
|
||||
|
||||
FileDialogManager = new FileDialogManager();
|
||||
|
||||
// Phase-1 services: pure allocation, no Theme/Font/UI touch.
|
||||
Commands = new Commands();
|
||||
Functions = new GameFunctions.GameFunctions(this);
|
||||
Ipc = new IpcManager();
|
||||
TypingIpc = new TypingIpc(this);
|
||||
ExtraChat = new ExtraChat();
|
||||
|
||||
// Plugin integrations register their IPC subscribers up-front so
|
||||
// Ready/Disposing events from the target plugins are caught from
|
||||
// the very first frame, even if the user's Honorific reloads
|
||||
// mid-session. See HellionChat/Integrations/HonorificService.cs.
|
||||
HonorificService = new Integrations.HonorificService(Interface, Log, Framework);
|
||||
|
||||
StatusBar = new Ui.StatusBar();
|
||||
|
||||
Interface.UiBuilder.DisableCutsceneUiHide = true;
|
||||
Interface.UiBuilder.DisableGposeUiHide = true;
|
||||
|
||||
if (Config.ShowEmotes)
|
||||
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||
}
|
||||
|
||||
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
// Group A: Font + Theme parallel — both CPU-bound, independent, and
|
||||
// dominate the load-time profile. Everything else stays sequential to
|
||||
// keep ordering simple.
|
||||
var fontTask = Task.Run(async () =>
|
||||
// Font-build runs fire-and-forget. The atlas rebuild lands a few
|
||||
// hundred ms after LoadAsync returns; first frames draw with
|
||||
// Dalamud's default font until the Hellion-Exo2 / NotoSans handles
|
||||
// are ready, then ImGui switches to the custom fonts (visible
|
||||
// "font-pop"). Mirrors ChatTwo's pattern — perceived-load win
|
||||
// comes from "Finished loading" landing earlier, not from a faster
|
||||
// atlas build.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
FontManager = new FontManager();
|
||||
await FontManager.BuildFontsAsync(cancellationToken).ConfigureAwait(false);
|
||||
}, cancellationToken);
|
||||
|
||||
var themeTask = Task.Run(() =>
|
||||
{
|
||||
// v1.1.0 — Theme-Engine init. Custom-Themes liegen in
|
||||
// pluginConfigs/HellionChat/themes/, lazy geladen beim ersten Get.
|
||||
// Theme init stays sync on the LoadAsync continuation — cheap,
|
||||
// and Active is read every Draw frame, so the registry must be
|
||||
// wired before the first hook fires.
|
||||
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(customThemesDir);
|
||||
SeedExampleThemeIfEmpty(customThemesDir);
|
||||
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
||||
ThemeRegistry.Switch(Config.Theme);
|
||||
}, cancellationToken);
|
||||
|
||||
await Task.WhenAll(fontTask, themeTask).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// SelfTest hooks live alongside the live registry — the steps
|
||||
// poll Active per frame and need the registry already wired.
|
||||
// Service allocations: order encodes dependencies. Commands is
|
||||
// alloc-only here; Initialise() runs after windows exist so the
|
||||
// slash-commands can toggle their visibility. HonorificService
|
||||
// registers IPC subscribers up-front so Ready/Disposing events
|
||||
// are caught from the very first frame.
|
||||
FileDialogManager = new FileDialogManager();
|
||||
Commands = new Commands();
|
||||
Functions = new GameFunctions.GameFunctions(this);
|
||||
Ipc = new IpcManager();
|
||||
TypingIpc = new TypingIpc(this);
|
||||
ExtraChat = new ExtraChat();
|
||||
HonorificService = new Integrations.HonorificService(Interface, Log, Framework);
|
||||
StatusBar = new Ui.StatusBar();
|
||||
MessageManager = new MessageManager(this);
|
||||
|
||||
// Auto-Tell-Tabs subscribes to MessageManager.MessageProcessed for
|
||||
// live tells and to ClientState.Logout for cleanup; needs the live
|
||||
// store handed in at construction.
|
||||
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
|
||||
AutoTellTabsService.Initialize();
|
||||
|
||||
// SelfTest steps poll Active per frame and need the registry wired.
|
||||
SelfTestRegistry.RegisterTestSteps([
|
||||
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||
]);
|
||||
|
||||
MessageManager = new MessageManager(this);
|
||||
|
||||
// Daily retention sweep, fire-and-forget. Lives in Phase 2 because
|
||||
// it dereferences MessageManager.Store. Skips itself when disabled
|
||||
// or when it already ran within the past 24 hours.
|
||||
RunRetentionSweepIfDue();
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs service. Subscribes to the
|
||||
// MessageManager's MessageProcessed event for live tells and
|
||||
// to ClientState.Logout for the cleanup pass. Created after
|
||||
// MessageManager so the constructor can hand off the live
|
||||
// store and event source.
|
||||
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
|
||||
AutoTellTabsService.Initialize();
|
||||
|
||||
ChatLogWindow = new ChatLogWindow(this);
|
||||
SettingsWindow = new SettingsWindow(this);
|
||||
DbViewer = new DbViewer(this);
|
||||
@@ -548,12 +544,24 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
if (!Config.FirstRunCompleted)
|
||||
FirstRunWizard.IsOpen = true;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// let all the other components register, then initialize commands
|
||||
Commands.Initialise();
|
||||
|
||||
// Daily retention sweep, fire-and-forget. Skips itself when
|
||||
// disabled or when it already ran within the past 24 hours.
|
||||
RunRetentionSweepIfDue();
|
||||
|
||||
if (Config.ShowEmotes)
|
||||
_ = EmoteCache.LoadData(); // Fire-and-forget, exceptions caught inside
|
||||
|
||||
if (Interface.Reason is not PluginLoadReason.Boot)
|
||||
MessageManager.FilterAllTabsAsync();
|
||||
|
||||
Interface.UiBuilder.DisableCutsceneUiHide = true;
|
||||
Interface.UiBuilder.DisableGposeUiHide = true;
|
||||
|
||||
#if !DEBUG
|
||||
// Avoid 300ms hitch when sending first message by preloading the
|
||||
// auto-translate cache. Don't do this in debug because it makes
|
||||
|
||||
Reference in New Issue
Block a user