Cut CPU use and move heavy work off the UI thread
Redraws: - Merge bursts of session and bandmap events into one redraw (Coalesced). Closed Bandmap and Available windows now unsubscribe. - Grayline and Voice Keyer no longer subscribe to the session twice. - Available summary, Score table and Log rows redraw only when their contents change. Telnet lines are added in batches. - Bandmap view reuses its layout, shaped text and brushes. Grayline draws the coastlines as one cached shape. Threads: - Available, Bandmap colours, Check and Statistics do their judging and counting on the thread pool and draw the result on the UI thread. - ContestLog takes a lock in Judge and in each method that changes the log, so a background Judge cannot read the indexes mid-edit. Refresh rate: - Statistics refreshes every 30 seconds and when the QSO count changes. - Grayline refreshes at most every 15 seconds on session events. Publish with ReadyToRun to cut startup JIT time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,44 @@ public sealed class CheckWindowSources
|
||||
];
|
||||
}
|
||||
|
||||
/// `Columns`, with the searching done on another thread. The master
|
||||
/// database holds tens of thousands of calls, and searching it between two
|
||||
/// key presses is what makes typing stutter. The log, the history and the
|
||||
/// bandmap are read here on the caller's thread, because the log is
|
||||
/// changed there; the caller's thread also judges the few calls that are
|
||||
/// shown, because that reads the log too.
|
||||
public async Task<IReadOnlyList<CheckColumn>> ColumnsAsync(int limit = 24)
|
||||
{
|
||||
string query = (Elsewhere ?? session.Entry.Call).Trim().ToUpperInvariant();
|
||||
IReadOnlyList<string> logged = LoggedCalls();
|
||||
IReadOnlyList<string> history = HistoryCalls();
|
||||
IReadOnlyList<string> spotted = SpottedCalls();
|
||||
CheckColumn exchange = ExchangeColumn(limit);
|
||||
bool searching = query.Length >= PartialMatching.ShortestUsefulQuery;
|
||||
List<PartialMatch> none = [];
|
||||
(List<PartialMatch> Log, List<PartialMatch> Master, List<PartialMatch> History, List<PartialMatch> Bandmap) found =
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (!searching)
|
||||
{
|
||||
return (none, none, none, none);
|
||||
}
|
||||
return (
|
||||
Best(logged, query, limit),
|
||||
database.Matches(query, limit).Select(m => m).ToList(),
|
||||
Best(history, query, limit),
|
||||
Best(spotted, query, limit));
|
||||
});
|
||||
return
|
||||
[
|
||||
new CheckColumn(CheckSource.Log, logged.Count, [.. found.Log.Select(Judged)]),
|
||||
new CheckColumn(CheckSource.Database, database.Count, [.. found.Master.Select(Judged)]),
|
||||
new CheckColumn(CheckSource.History, history.Count, [.. found.History.Select(Judged)]),
|
||||
new CheckColumn(CheckSource.Bandmap, spotted.Count, [.. found.Bandmap.Select(Judged)]),
|
||||
exchange,
|
||||
];
|
||||
}
|
||||
|
||||
/// What the exchange box with the cursor in it could hold: the ARRL
|
||||
/// sections, or the states and provinces. A serial number or a report has
|
||||
/// no list behind it, so the column stays empty.
|
||||
@@ -81,19 +119,26 @@ public sealed class CheckWindowSources
|
||||
{
|
||||
return new CheckColumn(source, calls.Count, []);
|
||||
}
|
||||
List<CheckCandidate> found = [];
|
||||
return new CheckColumn(source, calls.Count, [.. Best(calls, query, limit).Select(Judged)]);
|
||||
}
|
||||
|
||||
/// The calls that match the query, the closest first. It reads nothing but
|
||||
/// its arguments, so it can run on any thread.
|
||||
private static List<PartialMatch> Best(IReadOnlyList<string> calls, string query, int limit)
|
||||
{
|
||||
List<PartialMatch> found = [];
|
||||
foreach (string call in calls)
|
||||
{
|
||||
PartialMatchQuality? quality = PartialMatching.Judge(query, call);
|
||||
if (quality is not null)
|
||||
{
|
||||
found.Add(Judged(new PartialMatch(call, quality.Value)));
|
||||
found.Add(new PartialMatch(call, quality.Value));
|
||||
}
|
||||
}
|
||||
return new CheckColumn(
|
||||
source,
|
||||
calls.Count,
|
||||
found.OrderBy(c => c.Quality).ThenBy(c => c.Text, StringComparer.Ordinal).Take(limit).ToList());
|
||||
return [.. found
|
||||
.OrderBy(m => m.Quality)
|
||||
.ThenBy(m => m.Call, StringComparer.Ordinal)
|
||||
.Take(limit)];
|
||||
}
|
||||
|
||||
private CheckCandidate Judged(PartialMatch match) =>
|
||||
|
||||
Reference in New Issue
Block a user