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
+30 -6
View File
@@ -1,4 +1,4 @@
using Nonemm.App.Configuration; using Nonemm.App.Configuration;
using Nonemm.App.Theming; using Nonemm.App.Theming;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
@@ -67,25 +67,49 @@ public sealed partial class CheckWindow : RefreshableWindow
{ {
if (session.Check is null) if (session.Check is null)
{ {
shown.Clear();
Columns.Children.Clear(); Columns.Children.Clear();
return; return;
} }
IReadOnlyList<CheckColumn> columns = await session.Check.ColumnsAsync(); IReadOnlyList<CheckColumn> columns = await session.Check.ColumnsAsync();
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)
{
shown.Clear();
Columns.Children.Clear(); Columns.Children.Clear();
Columns.ColumnDefinitions.Clear(); Columns.ColumnDefinitions.Clear();
foreach (CheckColumn _ in columns) foreach (CheckColumn _ in columns)
{ {
Columns.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star)); 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); string signature = Signature(columns[at], fontSize);
Grid.SetColumn(panel, at++); if (signature == shown[at])
Columns.Children.Add(panel); {
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) private static Control BuildColumn(CheckColumn column, int fontSize)
{ {
StackPanel panel = new() { Margin = new Avalonia.Thickness(0, 0, 8, 0) }; 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; 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; public IReadOnlyList<Qso> Qsos => qsos;
/// The contacts alone. In WAE the log also holds QTC traffic, and a QTC /// 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); Qso scored = ApplyVerdict(qso, verdict);
qsos.Add(scored); qsos.Add(scored);
Index(scored, verdict); Index(scored, verdict);
Revision++;
return scored; return scored;
} }
} }
@@ -342,6 +347,7 @@ public sealed class ContestLog
private void Rebuild() private void Rebuild()
{ {
Revision++;
workedKeys.Clear(); workedKeys.Clear();
trafficWith.Clear(); trafficWith.Clear();
claimedMultipliers.Clear(); claimedMultipliers.Clear();
+40 -5
View File
@@ -145,15 +145,50 @@ public sealed class CheckWindowSources
new(match.Call, match.Quality, session.JudgeCall(match.Call)); new(match.Call, match.Quality, session.JudgeCall(match.Call));
private IReadOnlyList<string> LoggedCalls() => // The three lists below are asked for on every key press and change far
session.Log.Qsos.Select(q => q.Call.Text).Distinct(StringComparer.Ordinal).ToList(); // 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() => private IReadOnlyList<string> HistoryCalls()
bandmap.All() {
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) .Where(s => s.IsStation)
.Select(s => s.Call.Text) .Select(s => s.Call.Text)
.Distinct(StringComparer.Ordinal) .Distinct(StringComparer.Ordinal)
.ToList(); .ToList();
spottedOf = all;
}
return spotted;
}
} }