Add retained run recap to coach dashboard

This commit is contained in:
axiomlogicnexus 2026-04-27 14:51:22 +02:00
parent 840672e714
commit 8895f8b48a
2 changed files with 128 additions and 1 deletions

View file

@ -140,6 +140,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
{
RefreshLiveAttemptClock();
}
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
UpdateDashboardPresentation();
@ -161,7 +162,9 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::ExecutePrimaryCoach
RunState = StartCoachRecommendedRun(DefaultCoachMaxCases);
}
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
UpdateDashboardPresentation();
return RunState;
}
@ -249,7 +252,9 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedQueueE
{
FHyperTwistTrainingRunState RunState =
StartCoachQueueEntry(SelectedEntry->EntryId, DefaultCoachMaxCases);
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
UpdateDashboardPresentation();
return RunState;
}
@ -333,7 +338,10 @@ FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitSyntheti
Attempt.ReplayId = CachedRunState.ReplayPacket.ReplayId;
Attempt.CompletedAtUtc = FDateTime::UtcNow().ToIso8601();
return SubmitTrainingAttempt(Attempt, TimeMs);
const FHyperTwistTrainingRunStepResult StepResult = SubmitTrainingAttempt(Attempt, TimeMs);
SyncRetainedRunRecap();
SyncSelectedAttempt();
return StepResult;
}
bool UHyperTwistCoachDashboardWidget::ContinueCurrentRunFlow()
@ -463,6 +471,8 @@ FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitLiveTime
Attempt,
FMath::Max(LiveAttemptSolveElapsedMs, 0)
);
SyncRetainedRunRecap();
SyncSelectedAttempt();
CancelLiveAttemptTimer();
return StepResult;
}
@ -542,7 +552,9 @@ void UHyperTwistCoachDashboardWidget::HandlePromoteClicked()
void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked()
{
CompleteActiveTrainingRun(false);
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
UpdateDashboardPresentation();
}
@ -554,7 +566,9 @@ void UHyperTwistCoachDashboardWidget::HandleContinueRunClicked()
void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
{
ClearActiveTrainingRun();
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
UpdateDashboardPresentation();
}
@ -748,6 +762,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachActiveRunSummary"),
2
);
RunRecapHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachRunRecapHeader"),
2
);
RunRecapStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachRunRecapStatus"),
2
);
RunRecapDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachRunRecapDetail"),
2
);
LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -1410,6 +1442,17 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedAttempt()
LastObservedAttemptCount = CachedRunState.Attempts.Num();
}
void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
{
if (CachedRunState.Session.IsStructurallyValid()
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted))
{
RetainedRunRecapSummary = CachedRunSummary;
bHasRetainedRunRecap = !RetainedRunRecapSummary.TrainingSessionId.IsEmpty();
}
}
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
@ -1611,6 +1654,17 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
: (bCanContinueCurrentRun
? (CachedRunSummary.AttemptCount > 0 ? TEXT("Start Next Attempt") : TEXT("Start First Attempt"))
: TransitionLaunchLabel));
const bool bHasCurrentRunRecap = bHasActiveRun
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted);
const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = bHasCurrentRunRecap
? &CachedRunSummary
: (bHasRetainedRunRecap ? &RetainedRunRecapSummary : nullptr);
const bool bHasDisplayedRunRecap = DisplayedRunRecap != nullptr
&& !DisplayedRunRecap->TrainingSessionId.IsEmpty();
const FString RunRecapSourceLabel = bHasCurrentRunRecap
? TEXT("current run")
: TEXT("last completed run");
if (HeadlineTextBlock != nullptr)
{
@ -1663,6 +1717,67 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
CachedRunSummary.LastCaseId.IsEmpty() ? TEXT("n/a") : *CachedRunSummary.LastCaseId
)));
}
if (RunRecapHeaderTextBlock != nullptr)
{
RunRecapHeaderTextBlock->SetText(FText::FromString(TEXT("[Run Recap]")));
}
if (RunRecapStatusTextBlock != nullptr)
{
if (bHasDisplayedRunRecap)
{
RunRecapStatusTextBlock->SetText(FText::FromString(FString::Printf(
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s"),
*RunRecapSourceLabel,
*DisplayedRunRecap->TrainingSessionId,
DisplayedRunRecap->DeckId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->DeckId,
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecap->SessionState),
DisplayedRunRecap->LastCompletedAtUtc.IsEmpty()
? TEXT("n/a")
: *DisplayedRunRecap->LastCompletedAtUtc
)));
}
else if (bHasActiveRun)
{
RunRecapStatusTextBlock->SetText(FText::FromString(
TEXT("Source: active run | recap will stabilize here when the run completes or aborts.")
));
}
else
{
RunRecapStatusTextBlock->SetText(FText::FromString(
TEXT("Source: none yet | complete a run to retain a session recap on this dashboard.")
));
}
}
if (RunRecapDetailTextBlock != nullptr)
{
if (bHasDisplayedRunRecap)
{
RunRecapDetailTextBlock->SetText(FText::FromString(FString::Printf(
TEXT("Attempts %d/%d | Success %d | Failure %d | Timeout %d | DNF %d | Aborted %d | Success rate %.2f | Best %d ms | Avg %d ms | Avg raw %d ms | Avg inspection %d ms | Mistakes %d | Last case %s"),
DisplayedRunRecap->CompletedAttemptCount,
DisplayedRunRecap->AttemptCount,
DisplayedRunRecap->SuccessCount,
DisplayedRunRecap->FailureCount,
DisplayedRunRecap->TimeoutCount,
DisplayedRunRecap->DNFCount,
DisplayedRunRecap->AbortedCount,
DisplayedRunRecap->SuccessRate,
DisplayedRunRecap->BestTotalTimeMs,
DisplayedRunRecap->AverageTotalTimeMs,
DisplayedRunRecap->AverageRawSolveTimeMs,
DisplayedRunRecap->AverageInspectionTimeMs,
DisplayedRunRecap->TotalMistakes,
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId
)));
}
else
{
RunRecapDetailTextBlock->SetText(FText::FromString(
TEXT("Recap detail: unavailable until a run has been completed or explicitly aborted.")
));
}
}
if (LastAttemptTextBlock != nullptr)
{
if (ReviewedAttempt != nullptr)

View file

@ -148,6 +148,15 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ActiveRunSummaryTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RunRecapHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RunRecapStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RunRecapDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LastAttemptTextBlock = nullptr;
@ -431,6 +440,7 @@ private:
void EnsureDefaultDashboardBuilt();
void SyncSelectedQueueEntry();
void SyncSelectedAttempt();
void SyncRetainedRunRecap();
void UpdateDashboardPresentation();
void RefreshLiveAttemptClock();
FString BuildDefaultDeferredUntilUtc() const;
@ -449,4 +459,6 @@ private:
double LiveAttemptSolveStartedAtSeconds = 0.0;
int32 LiveAttemptStoredInspectionElapsedMs = 0;
int32 LastObservedAttemptCount = INDEX_NONE;
bool bHasRetainedRunRecap = false;
FHyperTwistTrainingSessionSummary RetainedRunRecapSummary;
};