Drop schema migrations and move AutoTranslate.PreloadCache off the load path
Migrations: all current users are on schema v16, the v9 to v16 migration chain ran in v1.2.1 and earlier. Replace the seven in-LoadAsync migration blocks with a hard schema-gate in the Phase-1 ctor; older configs trigger a clear "install v1.4.2 first" error. Code-hygiene change, fast-path saving is negligible. Remove the now-unused TryReadPreV13ThemeOpacity helper that only served the v13 to v14 block. AutoTranslate.PreloadCache: was sync ~300 ms in LoadAsync. Move to Task.Run so plugin-load returns ~300 ms earlier. Trade-off: first auto-translate use of a session may have a sub-second hitch if the cache hasn't finished warming. Acceptable, it is first-use cost instead of every-load cost.
This commit is contained in:
+17
-340
@@ -129,6 +129,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
Config = Interface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
|
||||
// Schema-gate: v1.4.3 only supports config schema v16. Older configs
|
||||
// went through their migrations in v1.2.1 (v15→v16) and earlier; users
|
||||
// who skipped past those releases need to install v1.4.2 first to run
|
||||
// the migration chain, then upgrade to v1.4.3.
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. " +
|
||||
"Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3.");
|
||||
}
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
|
||||
// already strips temp tabs before persistence, but a previous
|
||||
// crash or external write could have left them in the JSON.
|
||||
@@ -150,308 +161,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
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
|
||||
// trotzdem; dem User fehlt dann nur das manuelle Restore-Sicherheitsnetz.
|
||||
if (Config.Version < 10)
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is not null)
|
||||
{
|
||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v10-backup");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
{
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v10 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
Config = new Configuration
|
||||
{
|
||||
Version = 10,
|
||||
FirstRunCompleted = true,
|
||||
};
|
||||
SaveConfig();
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion Chat v10 → v11 — adds the global Configuration.PopOutInputEnabled
|
||||
// master switch and SeenPopOutInputHint flag for the v0.6.0 pop-out
|
||||
// input feature. Lightweight migration: defaults both fields,
|
||||
// no user-facing notification because the change is opt-in only.
|
||||
if (Config.Version < 11)
|
||||
{
|
||||
Config.PopOutInputEnabled = false;
|
||||
Config.SeenPopOutInputHint = false;
|
||||
Config.Version = 11;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v10 → v11: PopOutInputEnabled added (global, default off), " +
|
||||
"SeenPopOutInputHint added (default false)");
|
||||
}
|
||||
|
||||
// Hellion Chat v11 → v12 — flips Configuration.PopOutInputEnabled from
|
||||
// the v0.6.0 opt-in default (false) to opt-out (true) per v0.6.1 UX
|
||||
// polish. Hard-flip is a deliberate design call (see Spec section 5.7);
|
||||
// users are notified via the v0.6.1 hint banner (SeenPopOutHeaderHint
|
||||
// reset). Re-toggle after migration is preserved because this block
|
||||
// only fires for Version < 12.
|
||||
if (Config.Version < 12)
|
||||
{
|
||||
Config.PopOutInputEnabled = true;
|
||||
Config.SeenPopOutHeaderHint = false;
|
||||
Config.Version = 12;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v11 → v12: PopOutInputEnabled hard-flipped to true (v0.6.1 default), " +
|
||||
"SeenPopOutHeaderHint reset to false (v0.6.1 banner re-armed)");
|
||||
}
|
||||
|
||||
// Hellion Chat v12 → v13 — hard-resets the tab layout to the
|
||||
// sharpened v1.0.0 defaults (5 thematic tabs, see TabsUtil and
|
||||
// the default-fill block below). Existing tab state is wiped
|
||||
// because per-channel mapping from the old General preset to
|
||||
// the new General/System split would be ambiguous and would
|
||||
// produce subtly wrong results for users who tweaked the old
|
||||
// layout. A timestamped backup of the live config is written
|
||||
// alongside it as a manual restore safety net. The wipe scope
|
||||
// is intentionally narrow: only Config.Tabs is reset; Privacy,
|
||||
// Retention, Theme and every other knob keeps its current value.
|
||||
if (Config.Version < 13)
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is not null)
|
||||
{
|
||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v13-backup");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v13 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
Config.Tabs.Clear();
|
||||
Config.Version = 13;
|
||||
SaveConfig();
|
||||
|
||||
Log.Information(
|
||||
"Migrated config v12 → v13: tab layout hard-reset to v1.0.0 defaults; " +
|
||||
"pre-v13 config backup written next to the live file. " +
|
||||
"Default tabs will be populated by the Tabs.Count == 0 block.");
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion Chat v13 → v14 — theme-engine migration. Alle User landen
|
||||
// auf "hellion-arctic" als neues Default-Theme; die alte
|
||||
// HellionThemeEnabled-Flag wird deprecated und nur noch ein Release
|
||||
// als Safety-Net im JSON behalten. Window-Opacity wandert von
|
||||
// HellionThemeWindowOpacity in das neue WindowOpacity-Feld.
|
||||
//
|
||||
// v1.4.0 (F5.4): Pre-v13-Backup wird gelesen, HellionThemeWindowOpacity
|
||||
// ins neue Feld gezogen. Override nur wenn WindowOpacity noch beim
|
||||
// Default sitzt — sonst hat der User in der Zwischenzeit (z.B. via
|
||||
// WindowAlpha → WindowOpacity in v15→v16) explizit etwas gesetzt.
|
||||
if (Config.Version < 14)
|
||||
{
|
||||
Config.Theme = "hellion-arctic";
|
||||
|
||||
var oldThemeOpacity = TryReadPreV13ThemeOpacity();
|
||||
if (oldThemeOpacity is { } legacy
|
||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
||||
{
|
||||
Config.WindowOpacity = Math.Clamp(legacy, 0.5f, 1.0f);
|
||||
Log.Information(
|
||||
$"Migrated pre-v13 HellionThemeWindowOpacity {legacy} to WindowOpacity {Config.WindowOpacity}");
|
||||
}
|
||||
|
||||
Config.ReduceMotion = false;
|
||||
Config.UseCompactDensity = false;
|
||||
Config.Version = 14;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v13 → v14: theme engine introduced, all users land on hellion-arctic; " +
|
||||
"pick chat2-classic in Settings → Themes for the upstream look");
|
||||
}
|
||||
|
||||
if (Config.Version < 15)
|
||||
{
|
||||
// v1.2.0 — keine Datenmigration nötig. Removal der deprecated
|
||||
// Theme-Felder ist reine Schema-Bereinigung (System.Text.Json
|
||||
// ignoriert unbekannte Felder im JSON, daher kein Crash bei
|
||||
// Configs die noch HellionThemeEnabled/HellionThemeWindowOpacity
|
||||
// serialisiert haben — die Werte verfallen einfach).
|
||||
Config.Version = 15;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v14 → v15: legacy theme fields removed " +
|
||||
"(HellionThemeEnabled, HellionThemeWindowOpacity)");
|
||||
}
|
||||
|
||||
// Hellion Chat v15 → v16 — Settings Cleanup. Re-Sortierung der
|
||||
// Tabs auf der UI-Seite (datenneutral). 4 tote Felder verfallen
|
||||
// beim System.Text.Json-Deserialize (OverrideStyle, ChosenStyle,
|
||||
// WindowAlpha, ShowThemeQuickPicker — sind alle nicht mehr im
|
||||
// Configuration-Schema definiert). WindowAlpha wird zuvor auf
|
||||
// WindowOpacity gemappt damit User die ihn gesetzt hatten ihre
|
||||
// Transparenz-Einstellung behalten.
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
var liveConfigPath = pluginConfigsDir is not null
|
||||
? Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json")
|
||||
: null;
|
||||
|
||||
// Backup-Datei neben der live Config — Pattern aus v13 Branch.
|
||||
if (pluginConfigsDir is not null && liveConfigPath is not null)
|
||||
{
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v16-backup");
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v16 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-v16 Felder einmalig roh aus dem JSON lesen, da sie nicht
|
||||
// mehr im Configuration-Schema sind (und damit aus Config nicht
|
||||
// mehr abrufbar). WindowAlpha → WindowOpacity Mapping nur wenn
|
||||
// User WindowOpacity noch nicht selbst angefasst hat (Default
|
||||
// 0.85), sonst gewinnt der User-Wert.
|
||||
float oldWindowAlpha = 100f;
|
||||
bool oldOverrideStyle = false;
|
||||
if (liveConfigPath is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
{
|
||||
var rawJson = File.ReadAllText(liveConfigPath);
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(rawJson);
|
||||
if (doc.RootElement.TryGetProperty("WindowAlpha", out var alphaProp)
|
||||
&& alphaProp.ValueKind == System.Text.Json.JsonValueKind.Number)
|
||||
{
|
||||
oldWindowAlpha = alphaProp.GetSingle();
|
||||
}
|
||||
if (doc.RootElement.TryGetProperty("OverrideStyle", out var ovProp)
|
||||
&& ovProp.ValueKind is System.Text.Json.JsonValueKind.True)
|
||||
{
|
||||
oldOverrideStyle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v16 legacy-field lookup failed, defaults assumed");
|
||||
}
|
||||
}
|
||||
|
||||
// TEST-MIRROR: Hellion Build test/_Helpers/MigrationLogic.cs (local-only repo)
|
||||
// If you change the formula here, change the mirror — tests assert the contract.
|
||||
if (oldWindowAlpha != 100f
|
||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
||||
{
|
||||
Config.WindowOpacity = Math.Clamp(oldWindowAlpha / 100f, 0.5f, 1.0f);
|
||||
Log.Information(
|
||||
$"Migrated WindowAlpha {oldWindowAlpha} to WindowOpacity {Config.WindowOpacity}");
|
||||
}
|
||||
else if (oldWindowAlpha != 100f)
|
||||
{
|
||||
Log.Information(
|
||||
$"Skipped WindowAlpha→WindowOpacity migration: WindowOpacity already user-set " +
|
||||
$"({Config.WindowOpacity}), legacy WindowAlpha value {oldWindowAlpha} dropped.");
|
||||
}
|
||||
|
||||
if (oldOverrideStyle)
|
||||
{
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = "Hellion Chat 1.2.1",
|
||||
Content = HellionStrings.Migration_v16_OverrideStyle_Toast,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// v1.2.1 Default-Bumps für UX-Verbesserungen. Pattern: nur
|
||||
// migrieren wenn der User noch auf dem alten Default ist.
|
||||
// Bei bool-Werten ist die Erkennung pragmatisch — wer den
|
||||
// alten Default aktiv ausgeschaltet hatte, erlebt das als
|
||||
// Regression und stellt es einmal in den Settings zurück.
|
||||
// Der Trade-Off ist akzeptabel weil die alten Defaults in
|
||||
// v1.2.0 erst neu eingeführt wurden und kaum jemand aktiv
|
||||
// umgeschaltet hat.
|
||||
if (!Config.UseCompactDensity)
|
||||
{
|
||||
Config.UseCompactDensity = true;
|
||||
Log.Information("v16 default-bump: UseCompactDensity false → true");
|
||||
}
|
||||
if (!Config.HideInNewGamePlusMenu)
|
||||
{
|
||||
Config.HideInNewGamePlusMenu = true;
|
||||
Log.Information("v16 default-bump: HideInNewGamePlusMenu false → true");
|
||||
}
|
||||
if (!Config.HideSameTimestamps)
|
||||
{
|
||||
Config.HideSameTimestamps = true;
|
||||
Log.Information("v16 default-bump: HideSameTimestamps false → true");
|
||||
}
|
||||
if (Config.MaxLinesToRender == 5000)
|
||||
{
|
||||
Config.MaxLinesToRender = 2500;
|
||||
Log.Information("v16 default-bump: MaxLinesToRender 5000 → 2500");
|
||||
}
|
||||
if (Config.ChatColours.Count == 0)
|
||||
{
|
||||
foreach (var (channel, colour) in Resources.ChatColourPresets.All["Hellion"].Colours)
|
||||
Config.ChatColours[channel] = colour;
|
||||
Log.Information("v16 default-bump: ChatColours empty → Hellion brand preset");
|
||||
}
|
||||
|
||||
Config.Version = 16;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v15 → v16: settings cleanup, " +
|
||||
"OverrideStyle/ChosenStyle/WindowAlpha/ShowThemeQuickPicker dropped from schema");
|
||||
}
|
||||
|
||||
// Hellion v1.0.0 default tab layout. Five thematically separated
|
||||
// tabs: General catches the immediate-surroundings public chat
|
||||
// (Say/Yell/Shout) only; System absorbs the rest of the technical
|
||||
@@ -560,10 +269,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
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
|
||||
// profiling difficult.
|
||||
AutoTranslate.PreloadCache();
|
||||
// Fire-and-forget on a worker thread. The first auto-translate use of
|
||||
// a session may have a sub-second hitch if the cache hasn't filled yet,
|
||||
// but that's preferable to making every user wait ~300 ms during
|
||||
// plugin load for a cache they may never touch. ChatTwo (upstream)
|
||||
// does this sync; we trade load-time for first-use latency.
|
||||
_ = Task.Run(AutoTranslate.PreloadCache, cancellationToken);
|
||||
#endif
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -702,40 +413,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
return failure;
|
||||
}
|
||||
|
||||
// Reads HellionThemeWindowOpacity from the pre-v13 backup the v12→v13
|
||||
// block writes alongside the live config. Null when absent, unreadable,
|
||||
// or schema-incompatible — all valid steady states (fresh install,
|
||||
// backup pruned, pre-v12 config). Errors log at Warning so a corrupted
|
||||
// backup stays visible in /xllog without breaking the migration.
|
||||
private static float? TryReadPreV13ThemeOpacity()
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is null)
|
||||
return null;
|
||||
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v13-backup");
|
||||
if (!File.Exists(backupPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(backupPath);
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(stream);
|
||||
if (doc.RootElement.TryGetProperty("HellionThemeWindowOpacity", out var prop)
|
||||
&& prop.ValueKind == System.Text.Json.JsonValueKind.Number
|
||||
&& prop.TryGetSingle(out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v13 backup lookup failed, defaulting WindowOpacity");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MigrateFromChatTwoLayout()
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
|
||||
Reference in New Issue
Block a user