Skip to content

Commit 0502dc0

Browse files
committed
results pages: parse and compute off the dispatcher with spinner, skip empty stats with a notice
1 parent ca30d76 commit 0502dc0

6 files changed

Lines changed: 497 additions & 196 deletions

File tree

WebGUI/Components/Pages/ToolsFolder/BackendSpeed.razor

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<div class="tool-subtitle">NPS and EPS by piece count from PGN games</div>
1717

1818
<div class="tool-controls">
19-
<button class="tool-upload-btn" @onclick="BrowsePgnFile">Load pgn file</button>
19+
<button class="tool-upload-btn" @onclick="BrowsePgnFile" disabled="@isProcessing">Load pgn file</button>
2020
<div class="tool-input-group" style="margin-bottom:0">
2121
<label>Engine filter</label>
2222
<select class="tool-input" style="width:220px" @bind="selectedEngine" @bind:after="OnEngineChanged">
@@ -31,6 +31,19 @@
3131

3232
<div class="file-name">@fileOpenName</div>
3333

34+
@if (isProcessing)
35+
{
36+
<div class="file-name" style="display:flex; align-items:center; gap:10px;">
37+
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
38+
<span>@processingStatus</span>
39+
</div>
40+
}
41+
42+
@if (!string.IsNullOrEmpty(noStatsMessage))
43+
{
44+
<div class="file-name" style="color:#d0a060;">@noStatsMessage</div>
45+
}
46+
3447
<div class="tool-table-container" style="max-width:900px">
3548
<table class="tool-table sortable">
3649
<thead>
@@ -192,8 +205,13 @@
192205
PGNStatistics.printPieceCountDataPerPgnFile(dummyOutputFilePath, pieceCountDataPerPgnFile);
193206
}
194207

208+
private bool isProcessing;
209+
private string processingStatus = "";
210+
private string noStatsMessage = "";
211+
195212
private async Task BrowsePgnFile()
196213
{
214+
if (isProcessing) return;
197215
var initialDir = SettingsService.Settings.PgnOutputFolder;
198216
var parameters = new DialogParameters<WebGUI.Components.Layout.ExperimentalLayout.FileBrowserDialog>
199217
{
@@ -211,28 +229,59 @@
211229
fileOpenName = pgnName;
212230
logger.LogInformation("PGN file opened {File}", pgnName);
213231

214-
List<ChessLibrary.PGNTypes.PgnGame> games;
232+
isProcessing = true;
233+
processingStatus = "Parsing PGN…";
234+
noStatsMessage = "";
235+
StateHasChanged();
215236
try
216237
{
217-
games = FullPGNParser.parsePgnFile(filePath).ToList();
238+
List<ChessLibrary.PGNTypes.PgnGame> games;
239+
try
240+
{
241+
games = await Task.Run(() => FullPGNParser.parsePgnFile(filePath).ToList());
242+
}
243+
catch (Exception ex)
244+
{
245+
logger.LogError(ex, "Failed to parse PGN file {File}", pgnName);
246+
return;
247+
}
248+
if (games.Count == 0)
249+
{
250+
logger.LogWarning("No games found in {File} — not a PGN file?", pgnName);
251+
return;
252+
}
253+
logger.LogInformation("PGN file parsed {File}", pgnName);
254+
255+
processingStatus = $"Computing backend metrics for {games.Count:N0} games…";
256+
StateHasChanged();
257+
// keep the grouped data (F# shape) and notify UI; table reads FlattenedRows
258+
pieceCountDataPerPgnFile = await Task.Run(() => PGNStatistics.calculatePieceCountDataPerPgnFile(games));
259+
logger.LogInformation("Groups: {Count}", pieceCountDataPerPgnFile.Length);
260+
if (pieceCountDataPerPgnFile.Length == 0)
261+
{
262+
// pcs= is only written with MoveAnnotation=Full, and only engines that
263+
// report EPS (Ceres with LogLiveStats) produce nonzero eps= values
264+
noStatsMessage = "No piece-count/EPS data found in this PGN — backend metrics needs a tournament played with MoveAnnotation=Full and an engine reporting EPS (e.g. Ceres).";
265+
logger.LogWarning("No piece-count/EPS data in {File} — requires MoveAnnotation=Full and EPS-reporting engines", pgnName);
266+
currentPage = 0;
267+
return;
268+
}
269+
currentPage = 0;
270+
StateHasChanged();
271+
272+
// attempt to plot automatically after load (filtered by current selection)
273+
await PlotPieceCountChart();
218274
}
219275
catch (Exception ex)
220276
{
221-
logger.LogError(ex, "Failed to parse PGN file {File}", pgnName);
222-
return;
277+
logger.LogError(ex, "Backend metrics calculation failed for {File}", pgnName);
278+
}
279+
finally
280+
{
281+
isProcessing = false;
282+
processingStatus = "";
283+
StateHasChanged();
223284
}
224-
logger.LogInformation("PGN file parsed {File}", pgnName);
225-
226-
// keep the grouped data (F# shape) and notify UI; table reads FlattenedRows
227-
pieceCountDataPerPgnFile = PGNStatistics.calculatePieceCountDataPerPgnFile(games);
228-
logger.LogInformation("Groups: {Count}", pieceCountDataPerPgnFile.Length);
229-
currentPage = 0;
230-
StateHasChanged();
231-
232-
// attempt to plot automatically after load (filtered by current selection)
233-
await PlotPieceCountChart();
234-
235-
StateHasChanged();
236285
}
237286

238287

WebGUI/Components/Pages/ToolsFolder/DeviationFinder.razor

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<div class="tool-subtitle">Find and visualize move deviations across PGN games</div>
1919

2020
<div class="tool-controls">
21-
<button class="tool-upload-btn" @onclick="BrowsePgnFile">Load PGN file</button>
21+
<button class="tool-upload-btn" @onclick="BrowsePgnFile" disabled="@isProcessing">Load PGN file</button>
2222
<div class="tool-input-group" style="margin-bottom:0; min-width:220px;">
2323
<label>Reference player name</label>
2424
<input type="text" class="tool-input" style="width:100%" @bind="refPlayer" />
@@ -39,6 +39,14 @@
3939

4040
<div class="file-name">@fileName</div>
4141

42+
@if (isProcessing)
43+
{
44+
<div class="file-name" style="display:flex; align-items:center; gap:10px;">
45+
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
46+
<span>@processingStatus</span>
47+
</div>
48+
}
49+
4250
@if (!string.IsNullOrEmpty(resultDescription1))
4351
{
4452
<h2>@(isFindMoveAllDifferencesEnabled ? "Critical Move Differences Between Engines" : "Move Deviations By Same Engine")</h2>
@@ -257,8 +265,12 @@
257265
return deviatorColor == "w" ? res : -res;
258266
}
259267

268+
private bool isProcessing;
269+
private string processingStatus = "";
270+
260271
private async Task BrowsePgnFile()
261272
{
273+
if (isProcessing) return;
262274
var initialDir = SettingsService.Settings.PgnOutputFolder;
263275
var parameters = new DialogParameters<WebGUI.Components.Layout.ExperimentalLayout.FileBrowserDialog>
264276
{
@@ -280,48 +292,78 @@
280292
resultDescription1 = "";
281293
resultDescription2 = "";
282294

283-
List<ChessLibrary.PGNTypes.PgnGame> games;
295+
isProcessing = true;
296+
processingStatus = "Parsing PGN…";
297+
StateHasChanged();
284298
try
285299
{
286-
games = FullPGNParser.parsePgnFile(filePath).ToList();
287-
}
288-
catch (Exception ex)
289-
{
300+
List<ChessLibrary.PGNTypes.PgnGame> games;
301+
try
302+
{
303+
games = await Task.Run(() => FullPGNParser.parsePgnFile(filePath).ToList());
304+
}
305+
catch (Exception ex)
306+
{
290307
logger.LogError(ex, "Failed to parse PGN file {File}", pgnName);
291308
return;
309+
}
310+
if (games.Count == 0)
311+
{
312+
logger.LogWarning("No games found in {File} — not a PGN file?", pgnName);
313+
return;
314+
}
315+
316+
processingStatus = $"Analyzing move deviations in {games.Count:N0} games…";
317+
StateHasChanged();
318+
var comparList = ConvertCsvStringToList(comparePlayers);
319+
// Deviation analysis compares games pairwise — quadratic-ish work that must not
320+
// run on the dispatcher. Materialize the lazy sequences once inside the task
321+
// instead of re-running the whole analysis on every LINQ enumeration below.
322+
var (deviations, critical, desc1, desc2) = await Task.Run(() =>
323+
{
324+
var devs =
325+
(isFindMoveAllDifferencesEnabled ?
326+
DeviationAnalysis.findMoveDifferencesInPGN(games, refPlayer, comparList) :
327+
DeviationAnalysis.findAllDeviationsForAllPlayers(games))
328+
.ToList();
329+
var crit = devs.Where(e => e.Result != e.DevRes).ToList();
330+
331+
var sb = new StringBuilder();
332+
foreach (var deviator in crit.Select(e => e.PlayerToDeviate).Distinct())
333+
{
334+
var playerDeviations = crit.Where(e => e.PlayerToDeviate == deviator).ToList();
335+
var playerScore = playerDeviations.Sum(e => GetScore(e));
336+
sb.AppendLine($"Player: {deviator} did deviate {playerDeviations.Count} times and resulted in a calculated net score of: {playerScore}");
337+
}
338+
var sb1 = new StringBuilder();
339+
foreach (var deviator in devs.Select(e => e.PlayerToDeviate).Distinct())
340+
{
341+
var playerDeviations = devs.Where(e => e.PlayerToDeviate == deviator).ToList();
342+
var playerScore = playerDeviations.Sum(e => GetScore(e));
343+
sb1.AppendLine($"Player: {deviator} did deviate {playerDeviations.Count} times and resulted in a calculated net score of: {playerScore}");
344+
}
345+
return (devs, crit, sb.ToString(), sb1.ToString());
346+
});
347+
348+
deviationListAll.AddRange(deviations);
349+
deviationListCritical.AddRange(critical);
350+
resultDescription1 = desc1;
351+
resultDescription2 = desc2;
352+
logger.LogInformation(resultDescription1);
353+
logger.LogInformation(resultDescription2);
354+
logger.LogInformation("Done with calculation");
355+
currentPage = 0;
292356
}
293-
var comparList = ConvertCsvStringToList(comparePlayers);
294-
var deviations =
295-
isFindMoveAllDifferencesEnabled ?
296-
DeviationAnalysis.findMoveDifferencesInPGN(games, refPlayer, comparList) :
297-
DeviationAnalysis.findAllDeviationsForAllPlayers(games);
298-
299-
deviationListAll.AddRange(deviations);
300-
var critical = deviations.Where(e => e.Result != e.DevRes);
301-
deviationListCritical.AddRange(critical);
302-
303-
var distinctCritical = critical.Select(e => e.PlayerToDeviate).Distinct();
304-
var distinctAll = deviations.Select(e => e.PlayerToDeviate).Distinct();
305-
var sb = new StringBuilder();
306-
foreach (var deviator in distinctCritical)
357+
catch (Exception ex)
307358
{
308-
var playerDeviations = critical.Where(e => e.PlayerToDeviate == deviator);
309-
var playerScore = playerDeviations.Sum(e => GetScore(e));
310-
sb.AppendLine($"Player: {deviator} did deviate {playerDeviations.Count()} times and resulted in a calculated net score of: {playerScore}");
359+
logger.LogError(ex, "Deviation analysis failed for {File}", pgnName);
311360
}
312-
resultDescription1 = sb.ToString();
313-
var sb1 = new StringBuilder();
314-
foreach (var deviator in distinctAll)
361+
finally
315362
{
316-
var playerDeviations = deviations.Where(e => e.PlayerToDeviate == deviator);
317-
var playerScore = playerDeviations.Sum(e => GetScore(e));
318-
sb1.AppendLine($"Player: {deviator} did deviate {playerDeviations.Count()} times and resulted in a calculated net score of: {playerScore}");
363+
isProcessing = false;
364+
processingStatus = "";
365+
StateHasChanged();
319366
}
320-
resultDescription2 = sb1.ToString();
321-
logger.LogInformation(resultDescription1);
322-
logger.LogInformation(resultDescription2);
323-
logger.LogInformation("Done with calculation");
324-
currentPage = 0;
325367
}
326368

327369

0 commit comments

Comments
 (0)