From 7542d489833e2c93964ff2479f2af5504267aba6 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 14 May 2026 11:48:40 +0200 Subject: [PATCH] perf(dbviewer): dispatch FTS filter to worker thread FullTextSearch + LoadByGuids could stall the draw thread for 100-300 ms on large databases with a popular search term. The two hot trigger sites (FTS toggle, search input) now route via TriggerFilterRefresh, which dispatches the FTS path to Task.Run; the in-memory page-filter path stays inline because it is sub-ms on the loaded page array. _ftsFilterSeq is bumped per trigger so a late worker recognises itself as stale and drops its result instead of overwriting a newer one. The date/channel and history workers already lived on Task.Run and are untouched. Surfaced during the v1.4.8 pre-tag review. --- HellionChat/Ui/DbViewer.cs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/HellionChat/Ui/DbViewer.cs b/HellionChat/Ui/DbViewer.cs index 66eecb7..c033e9f 100644 --- a/HellionChat/Ui/DbViewer.cs +++ b/HellionChat/Ui/DbViewer.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Numerics; using System.Text; +using System.Threading; using Dalamud.Bindings.ImGui; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; @@ -44,6 +45,10 @@ public class DbViewer : Window private bool IsProcessing; private long ProcessingStart = Environment.TickCount64; + + // Bumped per trigger so a late worker drops itself instead of overwriting + // a newer result. + private long _ftsFilterSeq; private (DateTime Min, DateTime Max, int Page, bool Local, int ChannelCount) LastProcessed; private string MinDateString = ""; @@ -249,7 +254,7 @@ public class DbViewer : Window using (ImRaii.Disabled(!ftsReady)) { if (ImGui.Checkbox(HellionStrings.DbViewer_FullTextToggle, ref UseFullTextSearch)) - Filtered = Filter(Messages); + TriggerFilterRefresh(); } ImGuiUtil.HelpMarker( ftsReady @@ -267,7 +272,7 @@ public class DbViewer : Window 30 ) ) - Filtered = Filter(Messages); + TriggerFilterRefresh(); // Third row @@ -471,6 +476,34 @@ public class DbViewer : Window } } + // FTS path hits SQLite per keystroke -- dispatch to a worker, drop stale + // results via _ftsFilterSeq. Page-filter path is in-memory LINQ, stays + // inline. + private void TriggerFilterRefresh() + { + if (!UseFullTextSearch || !Plugin.MessageManager.Store.IsFtsIndexBuilt) + { + Filtered = Filter(Messages); + return; + } + + var snapshot = Messages; + var mySeq = Interlocked.Increment(ref _ftsFilterSeq); + Task.Run(() => + { + try + { + var result = Filter(snapshot); + if (Interlocked.Read(ref _ftsFilterSeq) == mySeq) + Filtered = result; + } + catch (Exception ex) + { + Plugin.LogProxy.Error(ex, "FTS filter worker failed"); + } + }); + } + private ConcurrentStack Filter(Message[] messages) { if (SimpleSearchTerm == "")