Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c9b90c767 | |||
| b81894b859 | |||
| 655c903cb5 | |||
| 8c4afaac17 | |||
| c6a3780753 | |||
| d9f6704316 | |||
| 011490368b | |||
| 8ed10a536b | |||
| 6051e49307 |
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
subtitle: Plugin-Load Render Polish
|
||||||
|
versionsnatur: Performance-Patch
|
||||||
|
---
|
||||||
|
|
||||||
|
- First-Frame-HITCH unter 100 ms: der erste Render-Frame des Plugins liegt
|
||||||
|
jetzt bei ~76 ms Median (vorher ~127 ms), die Dalamud-Warnung
|
||||||
|
„UiBuilder(Hellion Chat) > 100ms" beim Plugin-Start ist damit weg.
|
||||||
|
Erreicht durch das Verlagern von sechs nicht-essentiellen Render-
|
||||||
|
Sektionen (Statusleiste, Kanalname-Chunks, Fenster-Bounds-Check,
|
||||||
|
Hinweis-Banner, Autocomplete, Input-Preview) auf den zweiten Frame.
|
||||||
|
Bei 60 fps sieht man die deferred-Sektionen ~17 ms später, was im
|
||||||
|
Atlas-Build-Fenster nach einem Reload unsichtbar bleibt.
|
||||||
|
- Slash-Commands zentral registriert: /hellion, /hellionView,
|
||||||
|
/hellionSeString und /hellionDebugger werden jetzt im Plugin-Load zentral
|
||||||
|
registriert statt erst beim ersten Öffnen ihres Ziel-Fensters. Heißt: die
|
||||||
|
Befehle funktionieren ab dem ersten Tick, auch wenn das jeweilige Fenster
|
||||||
|
nie geöffnet wurde. Der „Einstellungen"-Button im Plugin-Manager hängt am
|
||||||
|
selben Pfad.
|
||||||
|
- Plugin-Load-Diagnose-Logs als Tripwire: die Profiling-Logs für
|
||||||
|
MessageStore.Connect, MessageStore.Migrate, FilterAllTabs und den
|
||||||
|
Auto-Translate-Warmup bleiben auf Information-Level eingeschaltet. Falls
|
||||||
|
eine zukünftige Änderung die Lade-Zeit wieder über 100 ms drückt, taucht
|
||||||
|
der Mehrverbrauch direkt im /xllog auf, ohne dass jemand erst den
|
||||||
|
Debug-Filter einschalten muss.
|
||||||
|
- ChatTwo-IPC-Kompatibilitäts-Layer: HellionChat spiegelt jetzt die
|
||||||
|
komplette ChatTwo-IPC-Surface (`GetChatInputState`,
|
||||||
|
`ChatInputStateChanged`, `Register`, `Unregister`, `Available`,
|
||||||
|
`Invoke`) zusätzlich zu unseren eigenen `HellionChat.*`-Gates unter
|
||||||
|
dem `ChatTwo.*`-Namensraum. Drittseitige Integrationen die nur auf
|
||||||
|
ChatTwo's IPC reagieren, etwa die Kontextmenü-Hooks von Artisan und
|
||||||
|
AllaganTools, funktionieren damit weiter ohne Code-Änderung auf
|
||||||
|
ihrer Seite. Die Conflict-Detection blockiert das parallele Laden
|
||||||
|
von ChatTwo, daher kein Namensraum-Konflikt im Live-Betrieb.
|
||||||
|
- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-
|
||||||
|
Aufwand. Nach dem Update läuft das Plugin gegen die bestehende
|
||||||
|
v17-Datenbank weiter.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
|
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
|
||||||
<Version>1.4.8</Version>
|
<Version>1.4.9</Version>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<!-- Use lock file to pin exact versions -->
|
<!-- Use lock file to pin exact versions -->
|
||||||
|
|||||||
@@ -35,6 +35,44 @@ tags:
|
|||||||
- Replacement
|
- Replacement
|
||||||
- Privacy
|
- Privacy
|
||||||
changelog: |-
|
changelog: |-
|
||||||
|
**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**
|
||||||
|
|
||||||
|
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame
|
||||||
|
render cost drops from ~127 ms median to ~76 ms median,
|
||||||
|
comfortably under Dalamud's 100 ms HITCH warning threshold.
|
||||||
|
|
||||||
|
- First-frame defer: six non-essential rendering sections inside
|
||||||
|
ChatLogWindow skip their first Draw and run one frame later
|
||||||
|
(bottom status bar, channel-name SeString chunks, window bounds
|
||||||
|
check, v0.6.1 hint banner, autocomplete, input-preview
|
||||||
|
calculation). User-visible delay is ~17 ms at 60 fps, hidden
|
||||||
|
inside the post-reload font-atlas build window.
|
||||||
|
- Slash-command centralisation: /hellion, /hellionView,
|
||||||
|
/hellionSeString and /hellionDebugger are registered in
|
||||||
|
LoadAsync instead of inside the corresponding window
|
||||||
|
constructors. The plugin-manager Open and configuration buttons
|
||||||
|
hang on the same path.
|
||||||
|
- Plugin-load profiling logs stay on at Information level
|
||||||
|
(MessageStore connect/migrate, FilterAllTabs, auto-translate
|
||||||
|
warmup) as a regression tripwire — a future load past 100 ms
|
||||||
|
will show up in /xllog without a Debug filter.
|
||||||
|
- ChatTwo IPC compatibility layer: HellionChat now mirrors
|
||||||
|
ChatTwo's full IPC surface (GetChatInputState,
|
||||||
|
ChatInputStateChanged, Register, Unregister, Available,
|
||||||
|
Invoke) under the ChatTwo.* namespace in addition to our
|
||||||
|
existing HellionChat.* provider gates. Third-party
|
||||||
|
integrations that historically only subscribe to ChatTwo's
|
||||||
|
IPC — for example Artisan's and AllaganTools' context-menu
|
||||||
|
hooks — keep working without requiring a code change on their
|
||||||
|
side. Conflict detection prevents ChatTwo from loading in
|
||||||
|
parallel with HellionChat, so there is no slot-collision risk
|
||||||
|
at runtime.
|
||||||
|
- Migration v17 stays (no schema bump).
|
||||||
|
|
||||||
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**v1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)**
|
**v1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)**
|
||||||
|
|
||||||
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer
|
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer
|
||||||
@@ -151,27 +189,4 @@ changelog: |-
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**v1.4.5 — UX and Robustness (2026-05-12)**
|
|
||||||
|
|
||||||
Sixth sub-patch of the v1.4.x polish-sweep series. Chat-log draw
|
|
||||||
failures surface as a notification, the first-run wizard has an
|
|
||||||
explicit "Later" option, the input history clears on plugin reload,
|
|
||||||
and the status bar version slot stops clipping in narrow windows.
|
|
||||||
|
|
||||||
- Chat window draw errors now show a one-shot notification instead
|
|
||||||
of failing silently — stack trace stays in /xllog
|
|
||||||
- First-run wizard: explicit "Later — keep defaults" button.
|
|
||||||
Closing the X no longer silently accepts the defaults; the wizard
|
|
||||||
reopens on the next plugin load if nothing was picked
|
|
||||||
- InputHistoryService clears on plugin dispose so the previous
|
|
||||||
session's typed commands don't bleed into the next load
|
|
||||||
- Status bar hides the version slot when the chat window is too
|
|
||||||
narrow to fit all five slots without overlap
|
|
||||||
- Internal: explicit session-only Auto-Tell-Tab invariant in
|
|
||||||
Plugin.cs plus a pinning test in the Build-Suite
|
|
||||||
- Internal: FontManager falls back to the system font if the
|
|
||||||
embedded Hellion font resource is missing — logs a Warning
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Full history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
|
Full history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ internal sealed class TypingIpc : IDisposable
|
|||||||
private ICallGateProvider<ChatInputState> StateQueryGate { get; }
|
private ICallGateProvider<ChatInputState> StateQueryGate { get; }
|
||||||
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
|
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
|
||||||
|
|
||||||
|
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Some third-party plugins
|
||||||
|
// have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
|
||||||
|
// gates. HellionChat replaces ChatTwo (conflict detection prevents
|
||||||
|
// parallel loading), so mirroring the ChatTwo provider slots lets those
|
||||||
|
// plugins keep working without code changes on their side. The tuple
|
||||||
|
// shape is textually identical to ChatTwo's IPC surface (same member
|
||||||
|
// order, same underlying types — ChatType is `ushort` in both repos)
|
||||||
|
// so Dalamud's IPC marshalling matches across plugin boundaries.
|
||||||
|
private ICallGateProvider<ChatInputState> ChatTwoStateQueryGate { get; }
|
||||||
|
private ICallGateProvider<ChatInputState, object?> ChatTwoStateChangedGate { get; }
|
||||||
|
|
||||||
private ChatInputState LastState;
|
private ChatInputState LastState;
|
||||||
private bool HasState;
|
private bool HasState;
|
||||||
|
|
||||||
@@ -33,7 +44,16 @@ internal sealed class TypingIpc : IDisposable
|
|||||||
"HellionChat.ChatInputStateChanged"
|
"HellionChat.ChatInputStateChanged"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// v1.4.9 R4: ChatTwo-prefixed compatibility slots (see class-level comment).
|
||||||
|
ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
|
||||||
|
"ChatTwo.GetChatInputState"
|
||||||
|
);
|
||||||
|
ChatTwoStateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
|
||||||
|
"ChatTwo.ChatInputStateChanged"
|
||||||
|
);
|
||||||
|
|
||||||
StateQueryGate.RegisterFunc(GetState);
|
StateQueryGate.RegisterFunc(GetState);
|
||||||
|
ChatTwoStateQueryGate.RegisterFunc(GetState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChatInputState BuildState()
|
private ChatInputState BuildState()
|
||||||
@@ -67,10 +87,13 @@ internal sealed class TypingIpc : IDisposable
|
|||||||
HasState = true;
|
HasState = true;
|
||||||
LastState = state;
|
LastState = state;
|
||||||
StateChangedGate.SendMessage(state);
|
StateChangedGate.SendMessage(state);
|
||||||
|
// v1.4.9 R4: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
|
||||||
|
ChatTwoStateChangedGate.SendMessage(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
StateQueryGate.UnregisterFunc();
|
StateQueryGate.UnregisterFunc();
|
||||||
|
ChatTwoStateQueryGate.UnregisterFunc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,26 @@ internal sealed class IpcManager : IDisposable
|
|||||||
object?
|
object?
|
||||||
> InvokeGate { get; }
|
> InvokeGate { get; }
|
||||||
|
|
||||||
|
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Third-party plugins with
|
||||||
|
// a no-fork policy (e.g. Artisan, AllaganTools) only subscribe to the
|
||||||
|
// ChatTwo.*-prefixed context-menu integration gates. Mirroring all four
|
||||||
|
// provider slots under the ChatTwo namespace lets those plugins keep
|
||||||
|
// working without code changes on their side. Conflict detection
|
||||||
|
// prevents ChatTwo and HellionChat from loading in parallel, so no slot
|
||||||
|
// collision risk.
|
||||||
|
private ICallGateProvider<string> ChatTwoRegisterGate { get; }
|
||||||
|
private ICallGateProvider<string, object?> ChatTwoUnregisterGate { get; }
|
||||||
|
private ICallGateProvider<object?> ChatTwoAvailableGate { get; }
|
||||||
|
private ICallGateProvider<
|
||||||
|
string,
|
||||||
|
PlayerPayload?,
|
||||||
|
ulong,
|
||||||
|
Payload?,
|
||||||
|
SeString?,
|
||||||
|
SeString?,
|
||||||
|
object?
|
||||||
|
> ChatTwoInvokeGate { get; }
|
||||||
|
|
||||||
internal List<string> Registered { get; } = [];
|
internal List<string> Registered { get; } = [];
|
||||||
|
|
||||||
public IpcManager()
|
public IpcManager()
|
||||||
@@ -41,7 +61,32 @@ internal sealed class IpcManager : IDisposable
|
|||||||
object?
|
object?
|
||||||
>("HellionChat.Invoke");
|
>("HellionChat.Invoke");
|
||||||
|
|
||||||
|
// v1.4.9 R4: ChatTwo-prefixed mirrors of the four context-menu slots
|
||||||
|
// above. Share the same Register/Unregister backing methods so a
|
||||||
|
// plugin that subscribes via either namespace lands in the same
|
||||||
|
// Registered list. SendMessage on Invoke fans out to both gates.
|
||||||
|
ChatTwoRegisterGate = Plugin.Interface.GetIpcProvider<string>("ChatTwo.Register");
|
||||||
|
ChatTwoRegisterGate.RegisterFunc(Register);
|
||||||
|
|
||||||
|
ChatTwoAvailableGate = Plugin.Interface.GetIpcProvider<object?>("ChatTwo.Available");
|
||||||
|
|
||||||
|
ChatTwoUnregisterGate = Plugin.Interface.GetIpcProvider<string, object?>(
|
||||||
|
"ChatTwo.Unregister"
|
||||||
|
);
|
||||||
|
ChatTwoUnregisterGate.RegisterAction(Unregister);
|
||||||
|
|
||||||
|
ChatTwoInvokeGate = Plugin.Interface.GetIpcProvider<
|
||||||
|
string,
|
||||||
|
PlayerPayload?,
|
||||||
|
ulong,
|
||||||
|
Payload?,
|
||||||
|
SeString?,
|
||||||
|
SeString?,
|
||||||
|
object?
|
||||||
|
>("ChatTwo.Invoke");
|
||||||
|
|
||||||
AvailableGate.SendMessage();
|
AvailableGate.SendMessage();
|
||||||
|
ChatTwoAvailableGate.SendMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Invoke(
|
internal void Invoke(
|
||||||
@@ -54,6 +99,8 @@ internal sealed class IpcManager : IDisposable
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
|
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
|
||||||
|
// v1.4.9 R4: fan out the same event to plugins listening on ChatTwo.Invoke.
|
||||||
|
ChatTwoInvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Register()
|
private string Register()
|
||||||
@@ -72,6 +119,8 @@ internal sealed class IpcManager : IDisposable
|
|||||||
{
|
{
|
||||||
UnregisterGate.UnregisterAction();
|
UnregisterGate.UnregisterAction();
|
||||||
RegisterGate.UnregisterFunc();
|
RegisterGate.UnregisterFunc();
|
||||||
|
ChatTwoUnregisterGate.UnregisterAction();
|
||||||
|
ChatTwoRegisterGate.UnregisterFunc();
|
||||||
Registered.Clear();
|
Registered.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,10 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
Plugin.LogProxy.Error(ex, "Error in FilterAllTabs");
|
Plugin.LogProxy.Error(ex, "Error in FilterAllTabs");
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugin.LogProxy.Debug($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
||||||
|
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
||||||
|
// regressions; remains in place after Sub-Task 3.4 Befund.
|
||||||
|
Plugin.LogProxy.Information($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -237,14 +237,26 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private SqliteConnection Connect()
|
private SqliteConnection Connect()
|
||||||
{
|
{
|
||||||
|
// v1.4.9 R3 profiling: trace cost of SQLite open + pragma-apply. Paired
|
||||||
|
// with the Migrate-Stopwatch below — Connect alone is the cheap half
|
||||||
|
// (Open + a handful of PRAGMAs); the expensive half typically lives in
|
||||||
|
// Migrate, especially on a large DB after a schema bump.
|
||||||
|
var connectSw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
var conn = new SqliteConnection(BuildConnectionString(DbPath));
|
var conn = new SqliteConnection(BuildConnectionString(DbPath));
|
||||||
conn.Open();
|
conn.Open();
|
||||||
ApplyPragmas(conn);
|
ApplyPragmas(conn);
|
||||||
|
connectSw.Stop();
|
||||||
|
_logger.Information($"MessageStore.Connect took {connectSw.ElapsedMilliseconds}ms");
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Migrate()
|
private void Migrate()
|
||||||
{
|
{
|
||||||
|
// v1.4.9 R3 profiling: trace cost of the schema-migration chain. On a
|
||||||
|
// large DB after a fresh schema bump this is the dominant SQLite cost
|
||||||
|
// at plugin-load, not Connect.
|
||||||
|
var migrateSw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
|
||||||
using var cmd = Connection.CreateCommand();
|
using var cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = "PRAGMA user_version;";
|
cmd.CommandText = "PRAGMA user_version;";
|
||||||
var userVersion = Convert.ToInt32(cmd.ExecuteScalar());
|
var userVersion = Convert.ToInt32(cmd.ExecuteScalar());
|
||||||
@@ -276,6 +288,9 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
foreach (var migration in migrationsToDo)
|
foreach (var migration in migrationsToDo)
|
||||||
migration();
|
migration();
|
||||||
|
|
||||||
|
migrateSw.Stop();
|
||||||
|
_logger.Information($"MessageStore.Migrate took {migrateSw.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Migrate0()
|
private void Migrate0()
|
||||||
|
|||||||
+84
-6
@@ -189,8 +189,8 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
if (Config.Version < 16)
|
if (Config.Version < 16)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"HellionChat v1.4.8 requires config schema v16, got v{Config.Version}. "
|
$"HellionChat v1.4.9 requires config schema v16, got v{Config.Version}. "
|
||||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.8."
|
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.9."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Config.Version = 17;
|
Config.Version = 17;
|
||||||
@@ -289,6 +289,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// Populate the command dictionary + UiBuilder hooks BEFORE
|
||||||
|
// Commands.Initialise() walks the dictionary and registers each
|
||||||
|
// entry with Dalamud's CommandManager (Commands.cs:15-28). Adding
|
||||||
|
// wrappers after Initialise() would leak them — they'd live in
|
||||||
|
// the dictionary but never reach Dalamud.
|
||||||
|
SetupCommands();
|
||||||
Commands.Initialise();
|
Commands.Initialise();
|
||||||
|
|
||||||
// Daily retention sweep — fire-and-forget, skips when disabled
|
// Daily retention sweep — fire-and-forget, skips when disabled
|
||||||
@@ -424,7 +430,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
Framework.Update += FrameworkUpdate;
|
Framework.Update += FrameworkUpdate;
|
||||||
Interface.UiBuilder.Draw += Draw;
|
Interface.UiBuilder.Draw += Draw;
|
||||||
Interface.LanguageChanged += LanguageChanged;
|
Interface.LanguageChanged += LanguageChanged;
|
||||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -455,7 +460,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
Exception? failure = null;
|
Exception? failure = null;
|
||||||
|
|
||||||
// Unsubscribe hooks first — mirrors the hooks-last subscribe order in LoadAsync.
|
// Unsubscribe hooks first — mirrors the hooks-last subscribe order in LoadAsync.
|
||||||
failure = CaptureFailure(failure, () => Interface.UiBuilder.OpenMainUi -= OpenMainUi);
|
|
||||||
failure = CaptureFailure(failure, () => Interface.LanguageChanged -= LanguageChanged);
|
failure = CaptureFailure(failure, () => Interface.LanguageChanged -= LanguageChanged);
|
||||||
failure = CaptureFailure(failure, () => Interface.UiBuilder.Draw -= Draw);
|
failure = CaptureFailure(failure, () => Interface.UiBuilder.Draw -= Draw);
|
||||||
failure = CaptureFailure(failure, () => Framework.Update -= FrameworkUpdate);
|
failure = CaptureFailure(failure, () => Framework.Update -= FrameworkUpdate);
|
||||||
@@ -505,6 +509,11 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
await Framework
|
await Framework
|
||||||
.RunOnFrameworkThread(() =>
|
.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
|
// TearDown slash-commands + UiBuilder hooks before windows
|
||||||
|
// tear down. Slash-commands holding handlers that reach
|
||||||
|
// the windows would otherwise see a half-torn Plugin.
|
||||||
|
failure = CaptureFailure(failure, TearDownCommands);
|
||||||
|
|
||||||
failure = CaptureFailure(
|
failure = CaptureFailure(
|
||||||
failure,
|
failure,
|
||||||
() => GameFunctions.GameFunctions.SetChatInteractable(true)
|
() => GameFunctions.GameFunctions.SetChatInteractable(true)
|
||||||
@@ -683,11 +692,80 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenMainUi()
|
// Central slash-command + UiBuilder.OpenConfigUi/OpenMainUi subscribe so
|
||||||
|
// the four lazy windows (Settings, DbViewer, SeStringDebugger, Debugger)
|
||||||
|
// have working entry points before they're constructed.
|
||||||
|
private void SetupCommands()
|
||||||
{
|
{
|
||||||
SettingsWindow.IsOpen = !SettingsWindow.IsOpen;
|
// ChatLogWindow.cs:128 already registers /hellion (ToggleChat). The
|
||||||
|
// description-arg here keeps the Dalamud help list populated.
|
||||||
|
Commands.Register("/hellion", "Perform various actions with Hellion Chat.").Execute +=
|
||||||
|
OnHellionSettingsCommand;
|
||||||
|
Commands
|
||||||
|
.Register(
|
||||||
|
"/hellionView",
|
||||||
|
"Get access to your message history, with simple filter options.",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.Execute += OnHellionViewCommand;
|
||||||
|
Commands.Register("/hellionDebugger", showInHelp: false).Execute +=
|
||||||
|
OnHellionDebuggerCommand;
|
||||||
|
#if DEBUG
|
||||||
|
// SeStringDebugger.cs lives under #if DEBUG too; keep this out of release builds.
|
||||||
|
Commands.Register("/hellionSeString", showInHelp: false).Execute +=
|
||||||
|
OnHellionSeStringCommand;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Plugin-Manager "Settings" button. Was in Settings.cs:67 pre-v1.4.9.
|
||||||
|
Interface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
|
||||||
|
|
||||||
|
// Plugin-Manager "Open" button. Was in Plugin.cs LoadAsync pre-v1.4.9
|
||||||
|
// (separate OpenMainUi handler that flipped SettingsWindow.IsOpen).
|
||||||
|
Interface.UiBuilder.OpenMainUi += OnOpenMainUi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TearDownCommands()
|
||||||
|
{
|
||||||
|
Interface.UiBuilder.OpenMainUi -= OnOpenMainUi;
|
||||||
|
Interface.UiBuilder.OpenConfigUi -= OnOpenConfigUi;
|
||||||
|
|
||||||
|
Commands.Register("/hellion", "Perform various actions with Hellion Chat.").Execute -=
|
||||||
|
OnHellionSettingsCommand;
|
||||||
|
Commands
|
||||||
|
.Register(
|
||||||
|
"/hellionView",
|
||||||
|
"Get access to your message history, with simple filter options.",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.Execute -= OnHellionViewCommand;
|
||||||
|
Commands.Register("/hellionDebugger", showInHelp: false).Execute -=
|
||||||
|
OnHellionDebuggerCommand;
|
||||||
|
#if DEBUG
|
||||||
|
Commands.Register("/hellionSeString", showInHelp: false).Execute -=
|
||||||
|
OnHellionSeStringCommand;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHellionSettingsCommand(string command, string arguments)
|
||||||
|
{
|
||||||
|
// /hellion with args is intentionally a no-op (matches pre-v1.4.9
|
||||||
|
// Settings.cs:76-80 behaviour).
|
||||||
|
if (string.IsNullOrWhiteSpace(arguments))
|
||||||
|
SettingsWindow.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOpenConfigUi() => SettingsWindow.Toggle();
|
||||||
|
|
||||||
|
private void OnOpenMainUi() => SettingsWindow.Toggle();
|
||||||
|
|
||||||
|
private void OnHellionViewCommand(string _, string __) => DbViewer.Toggle();
|
||||||
|
|
||||||
|
private void OnHellionDebuggerCommand(string _, string __) => DebuggerWindow.Toggle();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private void OnHellionSeStringCommand(string _, string __) => SeStringDebugger.Toggle();
|
||||||
|
#endif
|
||||||
|
|
||||||
private void RunRetentionSweepIfDue()
|
private void RunRetentionSweepIfDue()
|
||||||
{
|
{
|
||||||
if (!Config.RetentionEnabled)
|
if (!Config.RetentionEnabled)
|
||||||
|
|||||||
@@ -636,6 +636,15 @@ public sealed class ChatLogWindow : Window
|
|||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v1.4.9 R2: defer non-essential rendering on the first Draw call so the
|
||||||
|
// plugin-load stays under Dalamud's 100ms HITCH warning threshold. First-
|
||||||
|
// frame ImGui layout cost on a populated ChatLog ~127ms — deferring six
|
||||||
|
// non-essential sections (StatusBar, ChannelName chunks, PositionReset/
|
||||||
|
// BoundsCheck, HintBanner, AutoComplete, InputPreview.CalculatePreview)
|
||||||
|
// shaves ~33ms down to ~94ms. User sees the deferred sections one frame
|
||||||
|
// (~17ms at 60fps) late, invisible inside the post-reload Atlas-Build.
|
||||||
|
private bool _firstFrameDone;
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
DrewThisFrame = true;
|
DrewThisFrame = true;
|
||||||
@@ -643,7 +652,11 @@ public sealed class ChatLogWindow : Window
|
|||||||
{
|
{
|
||||||
DrawChatLog();
|
DrawChatLog();
|
||||||
AddPopOutsToDraw();
|
AddPopOutsToDraw();
|
||||||
DrawAutoComplete();
|
|
||||||
|
// v1.4.9 R2: AutoComplete renders nothing until the user starts
|
||||||
|
// typing a command — safe to skip on the first frame. ~6ms.
|
||||||
|
if (_firstFrameDone)
|
||||||
|
DrawAutoComplete();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -665,6 +678,13 @@ public sealed class ChatLogWindow : Window
|
|||||||
// input focus, which breaks every other ImGui window.
|
// input focus, which breaks every other ImGui window.
|
||||||
Activate = false;
|
Activate = false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Flag flips after the first Draw completes (success or caught
|
||||||
|
// exception). Sub-methods read it to decide whether to render
|
||||||
|
// non-essential UI sections.
|
||||||
|
_firstFrameDone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsChatMode =>
|
private static bool IsChatMode =>
|
||||||
@@ -680,18 +700,25 @@ public sealed class ChatLogWindow : Window
|
|||||||
LastWindowSize = currentSize;
|
LastWindowSize = currentSize;
|
||||||
LastWindowPos = ImGui.GetWindowPos();
|
LastWindowPos = ImGui.GetWindowPos();
|
||||||
|
|
||||||
// Manual reset snaps unconditionally; on-load check only fires when the
|
// v1.4.9 R2: skip the bounds-check chain on the first frame. The
|
||||||
// stored position has no overlap with any visible viewport.
|
// EnsureWindowOnScreen viewport iteration is ~10ms first-frame and
|
||||||
if (RequestPositionReset)
|
// not user-visible — frame 1 catches the same check before the
|
||||||
|
// user notices a mispositioned window.
|
||||||
|
if (_firstFrameDone)
|
||||||
{
|
{
|
||||||
RequestPositionReset = false;
|
// Manual reset snaps unconditionally; on-load check only fires when the
|
||||||
DidOnLoadBoundsCheck = true;
|
// stored position has no overlap with any visible viewport.
|
||||||
ApplySafeDefaultPosition("manual-reset");
|
if (RequestPositionReset)
|
||||||
}
|
{
|
||||||
else if (!DidOnLoadBoundsCheck)
|
RequestPositionReset = false;
|
||||||
{
|
DidOnLoadBoundsCheck = true;
|
||||||
DidOnLoadBoundsCheck = true;
|
ApplySafeDefaultPosition("manual-reset");
|
||||||
EnsureWindowOnScreen("on-load");
|
}
|
||||||
|
else if (!DidOnLoadBoundsCheck)
|
||||||
|
{
|
||||||
|
DidOnLoadBoundsCheck = true;
|
||||||
|
EnsureWindowOnScreen("on-load");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resized)
|
if (resized)
|
||||||
@@ -700,12 +727,17 @@ public sealed class ChatLogWindow : Window
|
|||||||
LastViewport = ImGui.GetWindowViewport().Handle;
|
LastViewport = ImGui.GetWindowViewport().Handle;
|
||||||
WasDocked = ImGui.IsWindowDocked();
|
WasDocked = ImGui.IsWindowDocked();
|
||||||
|
|
||||||
if (IsChatMode && Plugin.InputPreview.IsDrawable)
|
// v1.4.9 R2: CalculatePreview triggers InputPreview's first-frame
|
||||||
|
// lazy init (~3-5ms). User-typing-driven, safe to defer one frame.
|
||||||
|
if (_firstFrameDone && IsChatMode && Plugin.InputPreview.IsDrawable)
|
||||||
Plugin.InputPreview.CalculatePreview();
|
Plugin.InputPreview.CalculatePreview();
|
||||||
|
|
||||||
// Render the hint banner first so it sits above the tab area at full
|
// Render the hint banner first so it sits above the tab area at full
|
||||||
// window width. ImGui accounts for its height automatically.
|
// window width. ImGui accounts for its height automatically.
|
||||||
DrawV061HintBannerIfNeeded();
|
// v1.4.9 R2: skip on first frame (~3-5ms layout cost). The banner
|
||||||
|
// is a v0.6.1 migration notice that returns the same result frame 1.
|
||||||
|
if (_firstFrameDone)
|
||||||
|
DrawV061HintBannerIfNeeded();
|
||||||
|
|
||||||
if (Plugin.Config.SidebarTabView)
|
if (Plugin.Config.SidebarTabView)
|
||||||
DrawTabSidebar();
|
DrawTabSidebar();
|
||||||
@@ -938,7 +970,11 @@ public sealed class ChatLogWindow : Window
|
|||||||
|
|
||||||
// v1.2.0 — Bottom-Status-Bar. Letzter Render-Step in DrawChatLog,
|
// v1.2.0 — Bottom-Status-Bar. Letzter Render-Step in DrawChatLog,
|
||||||
// damit alle Zeilen-Operationen davor keine Layout-Sprünge auslösen.
|
// damit alle Zeilen-Operationen davor keine Layout-Sprünge auslösen.
|
||||||
Plugin.StatusBar.Draw(Plugin);
|
// v1.4.9 R2: skip on the first frame; ~12ms of first-frame layout
|
||||||
|
// cost. User sees the StatusBar 1 frame (~17ms at 60fps) later
|
||||||
|
// which is hidden inside the post-reload Atlas-Build window.
|
||||||
|
if (_firstFrameDone)
|
||||||
|
Plugin.StatusBar.Draw(Plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Dictionary<string, InputChannel> GetValidChannels()
|
internal Dictionary<string, InputChannel> GetValidChannels()
|
||||||
@@ -989,6 +1025,16 @@ public sealed class ChatLogWindow : Window
|
|||||||
|
|
||||||
private void DrawChannelName(Tab activeTab)
|
private void DrawChannelName(Tab activeTab)
|
||||||
{
|
{
|
||||||
|
// v1.4.9 R2: plain-text fallback on the first frame. ReadChannelName
|
||||||
|
// builds SeString chunks and DrawChunks runs SeString-Renderer layout
|
||||||
|
// — together ~18ms first-frame. Frame 1 renders the real chunks; the
|
||||||
|
// user sees the tab name for ~17ms during the post-reload window.
|
||||||
|
if (!_firstFrameDone)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(activeTab.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var currentChannel = ReadChannelName(activeTab);
|
var currentChannel = ReadChannelName(activeTab);
|
||||||
if (!currentChannel.SequenceEqual(PreviousChannel))
|
if (!currentChannel.SequenceEqual(PreviousChannel))
|
||||||
PreviousChannel = currentChannel;
|
PreviousChannel = currentChannel;
|
||||||
|
|||||||
@@ -93,29 +93,13 @@ public class DbViewer : Window
|
|||||||
|
|
||||||
RespectCloseHotkey = false;
|
RespectCloseHotkey = false;
|
||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
|
|
||||||
Plugin
|
|
||||||
.Commands.Register(
|
|
||||||
"/hellionView",
|
|
||||||
"Get access to your message history, with simple filter options.",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
.Execute += Toggle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Plugin
|
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||||
.Commands.Register(
|
|
||||||
"/hellionView",
|
|
||||||
"Get access to your message history, with simple filter options.",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
.Execute -= Toggle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Toggle(string _, string __) => Toggle();
|
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
var totalPages = (int)Math.Ceiling((double)Count / RowPerPage);
|
var totalPages = (int)Math.Ceiling((double)Count / RowPerPage);
|
||||||
|
|||||||
@@ -28,17 +28,13 @@ public class DebuggerWindow : Window, IDisposable
|
|||||||
|
|
||||||
RespectCloseHotkey = false;
|
RespectCloseHotkey = false;
|
||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
|
|
||||||
Plugin.Commands.Register("/hellionDebugger", showInHelp: false).Execute += Toggle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Plugin.Commands.Register("/hellionDebugger", showInHelp: false).Execute -= Toggle;
|
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Toggle(string _, string __) => Toggle();
|
|
||||||
|
|
||||||
public override unsafe void Draw()
|
public override unsafe void Draw()
|
||||||
{
|
{
|
||||||
var agent = (nint)AgentItemDetail.Instance();
|
var agent = (nint)AgentItemDetail.Instance();
|
||||||
|
|||||||
@@ -29,21 +29,13 @@ public class SeStringDebugger : Window
|
|||||||
|
|
||||||
RespectCloseHotkey = false;
|
RespectCloseHotkey = false;
|
||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute += Toggle;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||||
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute -= Toggle;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Toggle(string _, string __) => Toggle();
|
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
if (Plugin.MessageManager.LastMessage.Sender == null)
|
if (Plugin.MessageManager.LastMessage.Sender == null)
|
||||||
|
|||||||
@@ -60,23 +60,11 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
|||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
|
|
||||||
Initialise();
|
Initialise();
|
||||||
|
|
||||||
Plugin
|
|
||||||
.Commands.Register("/hellion", "Perform various actions with Hellion Chat.")
|
|
||||||
.Execute += Command;
|
|
||||||
Plugin.Interface.UiBuilder.OpenConfigUi += Toggle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Plugin.Interface.UiBuilder.OpenConfigUi -= Toggle;
|
// Slash-command + OpenConfigUi tear-down moved to Plugin.TearDownCommands.
|
||||||
Plugin.Commands.Register("/hellion").Execute -= Command;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Command(string command, string args)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(args))
|
|
||||||
Toggle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialise()
|
private void Initialise()
|
||||||
|
|||||||
@@ -62,7 +62,12 @@ internal static class AutoTranslate
|
|||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
AllEntries();
|
AllEntries();
|
||||||
Plugin.LogProxy.Debug($"Warming up auto-translate took {sw.ElapsedMilliseconds}ms");
|
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
||||||
|
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
||||||
|
// regressions; remains in place after Sub-Task 3.4 Befund.
|
||||||
|
Plugin.LogProxy.Information(
|
||||||
|
$"Warming up auto-translate took {sw.ElapsedMilliseconds}ms"
|
||||||
|
);
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
IsBackground = true,
|
IsBackground = true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||||
[](https://github.com/goatcorp/Dalamud)
|
[](https://github.com/goatcorp/Dalamud)
|
||||||
[](https://dotnet.microsoft.com/)
|
[](https://dotnet.microsoft.com/)
|
||||||
[](https://www.finalfantasyxiv.com/)
|
[](https://www.finalfantasyxiv.com/)
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**Version 1.4.8** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
**Version 1.4.9** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
||||||
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
||||||
|
|
||||||
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine comes from Chat 2
|
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine comes from Chat 2
|
||||||
@@ -286,19 +286,24 @@ An optional submission to the Dalamud main plugin repo (in addition to the custo
|
|||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
**Version 1.4.8** — Hook-Layer and Polish Quick-Wins. The Database Viewer now has an optional FTS5 full-text search
|
**Version 1.4.9** — Plugin-Load Render Polish. First-frame render cost is now well under Dalamud's 100 ms HITCH
|
||||||
across the entire chat history. Toggle "Full-text search" next to the search bar; the index is built asynchronously on
|
warning threshold (~76 ms median, down from ~127 ms). The gain comes from deferring six non-essential rendering
|
||||||
first run after the update with a progress toast, and the toggle stays disabled until the build completes. Multi-word
|
sections on the very first Draw — bottom status bar, channel-name SeString chunks, window bounds check, hint
|
||||||
terms match as exact phrases by default; power users can opt into raw FTS5 `MATCH` syntax by wrapping their own
|
banner, autocomplete and input-preview calculation — so the initial ImGui layout cost is spread between frame 0
|
||||||
double-quotes. Custom theme files auto-reload when edited while the theme is active — save the JSON in your editor and
|
and frame 1 instead of all hitting at once. At 60 fps the user sees those sections one frame (~17 ms) later, which
|
||||||
the live render picks up the change within a second, no picker click. Retention sweep no longer blocks the framework
|
is invisible inside the post-reload font-atlas build window. Slash commands `/hellion`, `/hellionView`,
|
||||||
thread (`Framework.Run(...).Wait()` replaced by `Framework.RunOnTick(...)`), removing the ~194 ms hitch per sweep. Status
|
`/hellionSeString` and `/hellionDebugger` are now registered centrally during plugin load so they work before
|
||||||
bar height is now derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders correctly at
|
their target window is opened the first time. The configuration-button entry in Dalamud's plugin manager hangs on
|
||||||
Windows display scaling above 100 %. Receive-suppressed-tells routing is postponed to v1.5.x; the investigation in this
|
the same path. Three plugin-load profiling logs (auto-translate warm-up, message-store connect, tab filter) stay
|
||||||
cycle showed that the FFXIV `ContentIdResolverHook` does not fire when other plugins suppress tells via
|
on at Information level as a regression tripwire — if a future change pushes the load past 100 ms again, the cost
|
||||||
`CheckMessageHandled`, which means tell-partner identification breaks for AutoTellTab routing — the fix lives next to
|
is right there in `/xllog`. The release also ships a ChatTwo IPC compatibility layer: HellionChat now mirrors
|
||||||
the planned ad-block hook layer where the same `RaptureLogModule` patch surface comes up anyway. Migration v17 stays
|
ChatTwo's full IPC surface (`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`,
|
||||||
(no schema bump). Ninth sub-patch of the v1.4.x polish sweep series (as of 2026-05-14).
|
`Invoke`) under the `ChatTwo.*` namespace in addition to our existing `HellionChat.*` provider gates, so
|
||||||
|
third-party integrations that historically only subscribe to ChatTwo's IPC (Artisan's and AllaganTools' context-
|
||||||
|
menu hooks are the practical examples) keep working without requiring a code change on their side. Conflict
|
||||||
|
detection prevents ChatTwo from loading in parallel with HellionChat, so there is no slot-collision risk at
|
||||||
|
runtime. Migration v17 stays (no schema bump). Tenth sub-patch of the v1.4.x polish sweep series (as of
|
||||||
|
2026-05-15).
|
||||||
|
|
||||||
Hellion Chat is a standalone plugin, no longer a fork in the repository sense. Fully completed:
|
Hellion Chat is a standalone plugin, no longer a fork in the repository sense. Fully completed:
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,42 @@ to the release pages for details.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Hellion Chat 1.4.9 — Plugin-Load Render Polish (2026-05-15)
|
||||||
|
|
||||||
|
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median down to
|
||||||
|
~76 ms median — comfortably under Dalamud's 100 ms HITCH warning threshold. The remaining ~13 ms gap to ChatTwo
|
||||||
|
upstream (~63 ms median) is the cost of HellionChat-only features (sidebar tab view, custom status bar,
|
||||||
|
Honorific integration).
|
||||||
|
|
||||||
|
- First-frame defer: six non-essential rendering sections inside `ChatLogWindow` skip their first Draw and run
|
||||||
|
one frame later. Covered sections are the bottom status bar, channel-name SeString chunks, window bounds
|
||||||
|
check, v0.6.1 hint banner, autocomplete and input-preview calculation. At 60 fps the user sees those sections
|
||||||
|
~17 ms after plugin reload — invisible inside the ~2.5 s font-atlas build window every reload runs through
|
||||||
|
anyway. Frame 1 stays well under 100 ms too (~40 ms), so no secondary HITCH warning appears.
|
||||||
|
- Slash-command centralisation: `/hellion`, `/hellionView`, `/hellionSeString` and `/hellionDebugger` are now
|
||||||
|
registered during `LoadAsync` instead of inside the corresponding window constructors. The commands work
|
||||||
|
before their target window is opened the first time, and Dalamud's plugin-manager configuration / open
|
||||||
|
buttons (`UiBuilder.OpenConfigUi` / `OpenMainUi`) hang on the same path.
|
||||||
|
- Plugin-load profiling logs stay on: `MessageStore.Connect`, `MessageStore.Migrate`, `FilterAllTabs` and the
|
||||||
|
auto-translate warm-up timing log are now Information level rather than Debug. They serve as a tripwire so a
|
||||||
|
future regression past 100 ms shows up directly in `/xllog` without re-enabling Debug.
|
||||||
|
- ChatTwo IPC compatibility layer: HellionChat now mirrors ChatTwo's full IPC surface
|
||||||
|
(`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`, `Invoke`) under the
|
||||||
|
`ChatTwo.*` namespace in addition to our existing `HellionChat.*` provider gates. Third-party
|
||||||
|
integrations that historically only subscribe to ChatTwo's IPC — for example Artisan's and AllaganTools'
|
||||||
|
context-menu hooks — keep working without requiring a code change on their side. Conflict detection
|
||||||
|
prevents ChatTwo from loading in parallel with HellionChat, so there is no slot-collision risk at
|
||||||
|
runtime.
|
||||||
|
- Migration v17 stays (no schema bump).
|
||||||
|
- Internal: hypothesis-triage during the R2 cycle falsified three of the four candidate root causes
|
||||||
|
(font-atlas sync, theme-apply ABGR-cache init, multiple-window render). Actual cause is `DrawList` setup
|
||||||
|
cost distributed across ~10 ImGui sections inside ChatLogWindow (5-20 ms each). The six selective defers
|
||||||
|
above are the pragmatic fix — a clean structural rewrite would belong in the v1.5.x DI-container cycle.
|
||||||
|
|
||||||
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Hellion Chat 1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)
|
## Hellion Chat 1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)
|
||||||
|
|
||||||
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search, ad-block foundation
|
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search, ad-block foundation
|
||||||
|
|||||||
+30
-4
@@ -10,14 +10,40 @@ the plugin's privacy-first scope during brainstorming.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Cycle (v1.4.9)
|
## Next Cycle (v1.4.10)
|
||||||
|
|
||||||
**Plugin-Load Render Polish.** Erststart-Frame-Hitch (~110 ms UiBuilder) and the related Font-Atlas + Auto-Translate
|
**Render Clipper, Symbol-Picker and Final-Cleanup.** Reserve items inherited from the v1.4.9 plan that did not need to
|
||||||
warmup costs surface every load and are reproducible in `/xlstats`. The cycle also unblocks the lazy-window refactor
|
land in the HITCH-cut: an `ImGuiListClipper` for variable-height messages in `DrawMessages` (the OtterGui `ImGuiClip.cs`
|
||||||
sketched in `feedback_lazy_window_dalamud` and the slash-command centralisation that comes with it.
|
wrapper is the idiom anchor), a Symbol Picker popup for the chat input (`imgui_demo.cpp` Popups & Modal Windows section
|
||||||
|
is the pattern reference), plus the carry-over from v1.4.9: structural First-Frame-Layout rewrite if the v1.4.9 selective
|
||||||
|
defers turn out to be too narrow once user-side regressions surface. Lazy-Window-Init naive is **not** in scope — the
|
||||||
|
v1.4.9 Stage-2 diagnose falsified that path (`WindowSystem.windows` is non-thread-safe, Game-Freeze under reload stress,
|
||||||
|
no measurable HITCH delta). A clean DI-container adoption (Lightless `PluginHostFactory` pattern) belongs in v1.5.x and
|
||||||
|
will revisit the question with the right threading model.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v1.4.9 — Plugin-Load Render Polish (released 2026-05-15)
|
||||||
|
|
||||||
|
Tenth sub-patch of the v1.4.x Polish Sweep series. First-frame HITCH drops from ~127 ms median to ~76 ms median (4-reload
|
||||||
|
sample), comfortably under Dalamud's 100 ms warning threshold. Mechanism: a single `_firstFrameDone` flag inside
|
||||||
|
`ChatLogWindow` defers six non-essential rendering sections (bottom status bar, channel-name SeString chunks, window
|
||||||
|
bounds check, v0.6.1 hint banner, autocomplete, input-preview calculation) from frame 0 to frame 1. User sees those
|
||||||
|
sections ~17 ms (60 fps) later, invisible inside the ~2.5 s font-atlas build window after every reload. Slash-command
|
||||||
|
registration moved from individual window constructors to a central `SetupCommands` / `TearDownCommands` pair in
|
||||||
|
`Plugin.cs` — `/hellion`, `/hellionView`, `/hellionSeString` and `/hellionDebugger` work before their target windows are
|
||||||
|
opened the first time, and Dalamud's plugin-manager `OpenConfigUi` / `OpenMainUi` buttons hang on the same path.
|
||||||
|
Plugin-load profiling logs (auto-translate warmup, `MessageStore.Connect`, `MessageStore.Migrate`, `FilterAllTabs`) stay
|
||||||
|
on at Information level as a regression tripwire. The release also ships a ChatTwo IPC compatibility layer: HellionChat
|
||||||
|
mirrors ChatTwo's full IPC surface (`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`,
|
||||||
|
`Invoke`) under the `ChatTwo.*` namespace in addition to our existing `HellionChat.*` provider gates, so third-party
|
||||||
|
integrations that only subscribe to ChatTwo's IPC (Artisan, AllaganTools) keep working without a code change on their
|
||||||
|
side. Conflict detection prevents ChatTwo from loading in parallel, so there is no slot-collision risk at runtime.
|
||||||
|
Migration v17 stays (no schema bump). Hypothesis-triage falsified
|
||||||
|
three of four candidate root causes (font-atlas sync fallback, theme-apply ABGR-cache init, multiple-window render via
|
||||||
|
lazy-init) — actual cost distributes evenly across ~10 ImGui sections inside ChatLogWindow, so structural rewrite is
|
||||||
|
deferred to v1.5.x DI-container cycle.
|
||||||
|
|
||||||
## v1.4.8 — Hook-Layer and Polish Quick-Wins (released 2026-05-14)
|
## v1.4.8 — Hook-Layer and Polish Quick-Wins (released 2026-05-14)
|
||||||
|
|
||||||
Ninth sub-patch of the v1.4.x Polish Sweep series. Database Viewer gains an optional FTS5 full-text search across the
|
Ninth sub-patch of the v1.4.x Polish Sweep series. Database Viewer gains an optional FTS5 full-text search across the
|
||||||
|
|||||||
Reference in New Issue
Block a user