Keep the Check window's lists and columns between key presses

Tracing key presses with a busy feed showed the Check window rebuilding
the distinct call lists of the log, the call history and every spot,
and all five columns of controls, on each press.

- ContestLog.Revision goes up with every change to the contacts. The
  Check sources keep each call list until its source changes.
- The Check window builds a column again only when what it shows
  differs.

UI thread time while typing with eight windows open went from 14.6% to
9.3%.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 13:42:37 +00:00
co-authored by Claude Sonnet 5.5
parent 5f9d186150
commit aa54f8dcbe
3 changed files with 84 additions and 19 deletions
+34 -10
View File
@@ -1,4 +1,4 @@
using Nonemm.App.Configuration;
using Nonemm.App.Configuration;
using Nonemm.App.Theming;
using Avalonia.Controls;
using Avalonia.Layout;
@@ -67,25 +67,49 @@ public sealed partial class CheckWindow : RefreshableWindow
{
if (session.Check is null)
{
shown.Clear();
Columns.Children.Clear();
return;
}
IReadOnlyList<CheckColumn> columns = await session.Check.ColumnsAsync();
Columns.Children.Clear();
Columns.ColumnDefinitions.Clear();
foreach (CheckColumn _ in columns)
int fontSize = session.Settings.CheckFontSize;
// a column is built again only when what it shows is different: most
// key presses change one or two of the five
if (shown.Count != columns.Count || Columns.Children.Count != columns.Count)
{
Columns.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
shown.Clear();
Columns.Children.Clear();
Columns.ColumnDefinitions.Clear();
foreach (CheckColumn _ in columns)
{
Columns.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
shown.Add("");
Columns.Children.Add(new Panel());
}
}
int at = 0;
foreach (CheckColumn column in columns)
for (int at = 0; at < columns.Count; at++)
{
Control panel = BuildColumn(column, session.Settings.CheckFontSize);
Grid.SetColumn(panel, at++);
Columns.Children.Add(panel);
string signature = Signature(columns[at], fontSize);
if (signature == shown[at])
{
continue;
}
shown[at] = signature;
Control panel = BuildColumn(columns[at], fontSize);
Grid.SetColumn(panel, at);
Columns.Children[at] = panel;
}
}
/// What the column shows, as text.
private readonly List<string> shown = [];
private static string Signature(CheckColumn column, int fontSize) =>
$"{fontSize}|{column.Source}|{column.Held}|" + string.Join(
';',
column.Candidates.Select(c =>
$"{c.Text}:{c.Quality}:{c.Verdict?.IsDupe}:{c.Verdict?.Points}:{c.Verdict?.NewMultipliers.Count}"));
private static Control BuildColumn(CheckColumn column, int fontSize)
{
StackPanel panel = new() { Margin = new Avalonia.Thickness(0, 0, 8, 0) };
+6
View File
@@ -50,6 +50,10 @@ public sealed class ContestLog
public Contest Contest => contest;
/// Goes up with every change to the contacts, so something worked out
/// from the log can be kept until this changes.
public int Revision { get; private set; }
public IReadOnlyList<Qso> Qsos => qsos;
/// The contacts alone. In WAE the log also holds QTC traffic, and a QTC
@@ -123,6 +127,7 @@ public sealed class ContestLog
Qso scored = ApplyVerdict(qso, verdict);
qsos.Add(scored);
Index(scored, verdict);
Revision++;
return scored;
}
}
@@ -342,6 +347,7 @@ public sealed class ContestLog
private void Rebuild()
{
Revision++;
workedKeys.Clear();
trafficWith.Clear();
claimedMultipliers.Clear();
+44 -9
View File
@@ -145,15 +145,50 @@ public sealed class CheckWindowSources
new(match.Call, match.Quality, session.JudgeCall(match.Call));
private IReadOnlyList<string> LoggedCalls() =>
session.Log.Qsos.Select(q => q.Call.Text).Distinct(StringComparer.Ordinal).ToList();
// The three lists below are asked for on every key press and change far
// less often, so each is kept until what it is made from changes.
private int loggedRevision = -1;
private IReadOnlyList<string> logged = [];
private CallHistory? historyOf;
private IReadOnlyList<string> history = [];
private IReadOnlyList<Spot>? spottedOf;
private IReadOnlyList<string> spotted = [];
private IReadOnlyList<string> HistoryCalls() => [.. session.Session.History.Calls];
private IReadOnlyList<string> LoggedCalls()
{
int revision = session.Log.Revision;
if (revision != loggedRevision)
{
logged = session.Log.Qsos.Select(q => q.Call.Text).Distinct(StringComparer.Ordinal).ToList();
loggedRevision = revision;
}
return logged;
}
private IReadOnlyList<string> SpottedCalls() =>
bandmap.All()
.Where(s => s.IsStation)
.Select(s => s.Call.Text)
.Distinct(StringComparer.Ordinal)
.ToList();
private IReadOnlyList<string> HistoryCalls()
{
CallHistory current = session.Session.History;
if (!ReferenceEquals(current, historyOf))
{
history = [.. current.Calls];
historyOf = current;
}
return history;
}
private IReadOnlyList<string> SpottedCalls()
{
// the bandmap hands out the same list until its spots change
IReadOnlyList<Spot> all = bandmap.All();
if (!ReferenceEquals(all, spottedOf))
{
spotted = all
.Where(s => s.IsStation)
.Select(s => s.Call.Text)
.Distinct(StringComparer.Ordinal)
.ToList();
spottedOf = all;
}
return spotted;
}
}