Add persisted timing-aware training backend
This commit is contained in:
parent
d382f48048
commit
4c38fcf536
15 changed files with 6107 additions and 20 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistContractLibraryInternal
|
||||
|
|
@ -331,14 +332,13 @@ FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingR
|
|||
Attempt.TrainingSessionId = RunState.Session.TrainingSessionId;
|
||||
Attempt.CaseId = RunState.Session.CurrentCaseId;
|
||||
Attempt.Result = EHyperTwistTrainingAttemptResult::Success;
|
||||
Attempt.TotalTimeMs = 2214;
|
||||
Attempt.ExecutionTimeMs = 2214;
|
||||
Attempt.RecognitionTimeMs = 0;
|
||||
Attempt.Mistakes = 0;
|
||||
Attempt.ReplayId = RunState.ReplayPacket.ReplayId;
|
||||
Attempt.CompletedAtUtc = TEXT("2026-04-25T12:02:14Z");
|
||||
Attempt.TimingBreakdown = MakeSampleTrainingTimingBreakdown();
|
||||
|
||||
return UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(RunState, Attempt, Attempt.TotalTimeMs);
|
||||
return UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(RunState, Attempt, 0);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSessionSummary UHyperTwistContractLibrary::MakeSampleTrainingRunSummary()
|
||||
|
|
@ -347,6 +347,291 @@ FHyperTwistTrainingSessionSummary UHyperTwistContractLibrary::MakeSampleTraining
|
|||
return StepResult.SessionSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimingPolicy UHyperTwistContractLibrary::MakeSampleTrainingTimingPolicy()
|
||||
{
|
||||
FHyperTwistTrainingTimingPolicy TimingPolicy;
|
||||
TimingPolicy.bInspectionEnabled = true;
|
||||
TimingPolicy.InspectionDurationMs = 15000;
|
||||
TimingPolicy.bInspectionPenaltiesEnabled = true;
|
||||
TimingPolicy.bAllowManualPenalty = true;
|
||||
TimingPolicy.bHideRunningTime = false;
|
||||
|
||||
FHyperTwistTrainingSplitPhaseDefinition RecognitionPhase;
|
||||
RecognitionPhase.PhaseId = TEXT("recognition");
|
||||
RecognitionPhase.Label = TEXT("Recognition");
|
||||
RecognitionPhase.Order = 0;
|
||||
|
||||
FHyperTwistTrainingSplitPhaseDefinition ExecutionPhase;
|
||||
ExecutionPhase.PhaseId = TEXT("execution");
|
||||
ExecutionPhase.Label = TEXT("Execution");
|
||||
ExecutionPhase.Order = 1;
|
||||
|
||||
TimingPolicy.SplitPhases = {RecognitionPhase, ExecutionPhase};
|
||||
return TimingPolicy;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimingBreakdown UHyperTwistContractLibrary::MakeSampleTrainingTimingBreakdown()
|
||||
{
|
||||
FHyperTwistTrainingTimingBreakdown TimingBreakdown;
|
||||
TimingBreakdown.InspectionElapsedMs = 14750;
|
||||
TimingBreakdown.RawSolveTimeMs = 2214;
|
||||
TimingBreakdown.ManualPenalty = EHyperTwistTrainingPenalty::None;
|
||||
TimingBreakdown.InspectionPenalty = EHyperTwistTrainingPenalty::None;
|
||||
TimingBreakdown.AppliedPenalty = EHyperTwistTrainingPenalty::None;
|
||||
TimingBreakdown.PenaltyOffsetMs = 0;
|
||||
TimingBreakdown.FinalTimeMs = 2214;
|
||||
TimingBreakdown.bDidNotFinish = false;
|
||||
|
||||
FHyperTwistTrainingSplitCapture RecognitionCapture;
|
||||
RecognitionCapture.PhaseId = TEXT("recognition");
|
||||
RecognitionCapture.Label = TEXT("Recognition");
|
||||
RecognitionCapture.TimestampMs = 620;
|
||||
|
||||
TimingBreakdown.SplitCaptures = {RecognitionCapture};
|
||||
return TimingBreakdown;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainingRepositoryState()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingRepositoryState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState RepositoryState;
|
||||
return UHyperTwistTrainingRepositoryLibrary::RecordRunState(RepositoryState, StepResult.UpdatedRunState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistContractLibrary::MakeSampleTrainingDeckHistorySummary()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingDeckHistorySummary();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary(
|
||||
RepositoryState,
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
StepResult.UpdatedRunState.Session.DeckId
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseScopedStats UHyperTwistContractLibrary::MakeSampleTrainingCaseScopedStats()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCaseScopedStats();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
StepResult.UpdatedRunState.Session.DeckId,
|
||||
StepResult.AttemptResolution.UpdatedProgressionMemory.CaseId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckScopedStats UHyperTwistContractLibrary::MakeSampleTrainingDeckScopedStats()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingDeckScopedStats();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
StepResult.UpdatedRunState.Session.DeckId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState UHyperTwistContractLibrary::MakeSampleTrainingCaseLearnerState()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid() || !StepResult.AttemptResolution.NextCaseSelection.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCaseLearnerState();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
|
||||
FHyperTwistTrainingCaseLearnerState LearnerState;
|
||||
if (UHyperTwistTrainingRepositoryLibrary::TryGetLearnerState(
|
||||
RepositoryState,
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
StepResult.UpdatedRunState.Session.DeckId,
|
||||
StepResult.AttemptResolution.UpdatedProgressionMemory.CaseId,
|
||||
LearnerState
|
||||
))
|
||||
{
|
||||
return LearnerState;
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCaseLearnerState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistContractLibrary::MakeSampleTrainingLearnerDeckStateSummary()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary(
|
||||
RepositoryState,
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
StepResult.UpdatedRunState.Session.DeckId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerProfile UHyperTwistContractLibrary::MakeSampleTrainingLearnerProfile()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingLearnerProfile();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistContractLibrary::MakeSampleTrainingCoachMemorySnapshot()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCoachMemorySnapshot();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistContractLibrary::MakeSampleTrainingLearnerPresets()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return TArray<FHyperTwistTrainingLearnerPreset>();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerPresets(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistContractLibrary::MakeSampleTrainingSessionTemplates()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return TArray<FHyperTwistTrainingSessionTemplate>();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveTrainingSessionTemplates(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistContractLibrary::MakeSampleTrainingSessionTemplateDeck()
|
||||
{
|
||||
const TArray<FHyperTwistTrainingSessionTemplate> Templates = MakeSampleTrainingSessionTemplates();
|
||||
if (Templates.Num() == 0)
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
|
||||
Templates[0],
|
||||
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog()
|
||||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistContractLibrary::MakeSampleTrainingCaseRecommendations()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return TArray<FHyperTwistTrainingCaseRecommendation>();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
|
||||
return UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck(
|
||||
RepositoryState,
|
||||
StepResult.UpdatedRunState.ActiveDeck,
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z"),
|
||||
5
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistContractLibrary::MakeSampleTrainingReviewDeck()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations = MakeSampleTrainingCaseRecommendations();
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
|
||||
StepResult.UpdatedRunState.ActiveDeck,
|
||||
Recommendations,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewPlanState UHyperTwistContractLibrary::MakeSampleTrainingReviewPlanState()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingReviewPlanState();
|
||||
}
|
||||
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations = MakeSampleTrainingCaseRecommendations();
|
||||
const FHyperTwistTrainingDeck ReviewDeck = MakeSampleTrainingReviewDeck();
|
||||
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
|
||||
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(EHyperTwistTrainingDeliveryMode::Timer);
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildReviewPlan(
|
||||
StepResult.UpdatedRunState.ActiveDeck,
|
||||
ReviewDeck,
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
Recommendations,
|
||||
ReviewPolicy,
|
||||
TEXT("review_plan_training_session_01"),
|
||||
TEXT("2026-04-28T12:05:00Z"),
|
||||
EHyperTwistTrainingDeliveryMode::Timer
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewFlowStatus UHyperTwistContractLibrary::MakeSampleTrainingReviewFlowStatus()
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(
|
||||
MakeSampleTrainingReviewPlanState()
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid();
|
||||
|
|
@ -372,6 +657,11 @@ FString UHyperTwistContractLibrary::SerializeContentPackToJson(const FHyperTwist
|
|||
return HyperTwistContractLibraryInternal::SerializeStructToJson(ContentPack);
|
||||
}
|
||||
|
||||
FString UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState)
|
||||
{
|
||||
return HyperTwistContractLibraryInternal::SerializeStructToJson(RepositoryState);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::DeserializePuzzleStateFromJson(const FString& Json, FHyperTwistPuzzleState& OutState)
|
||||
{
|
||||
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutState);
|
||||
|
|
@ -386,3 +676,8 @@ bool UHyperTwistContractLibrary::DeserializeContentPackFromJson(const FString& J
|
|||
{
|
||||
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutContentPack);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState)
|
||||
{
|
||||
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutRepositoryState);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,25 @@
|
|||
|
||||
namespace HyperTwistTrainingCatalogLibraryInternal
|
||||
{
|
||||
FString TemplateKindToTag(EHyperTwistTrainingSessionTemplateKind TemplateKind)
|
||||
{
|
||||
switch (TemplateKind)
|
||||
{
|
||||
case EHyperTwistTrainingSessionTemplateKind::ReviewPlan:
|
||||
return TEXT("template:review-plan");
|
||||
case EHyperTwistTrainingSessionTemplateKind::Recovery:
|
||||
return TEXT("template:recovery");
|
||||
case EHyperTwistTrainingSessionTemplateKind::Accuracy:
|
||||
return TEXT("template:accuracy");
|
||||
case EHyperTwistTrainingSessionTemplateKind::Speed:
|
||||
return TEXT("template:speed");
|
||||
case EHyperTwistTrainingSessionTemplateKind::Coach:
|
||||
return TEXT("template:coach");
|
||||
default:
|
||||
return TEXT("template:unknown");
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSourceAttribution MakeAlgTrainerAttribution(
|
||||
const FString& TrackId,
|
||||
const FString& DeckId,
|
||||
|
|
@ -62,6 +81,29 @@ namespace HyperTwistTrainingCatalogLibraryInternal
|
|||
);
|
||||
return TrainingCase;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimingPolicy MakeStandardAlgorithmTimingPolicy()
|
||||
{
|
||||
FHyperTwistTrainingTimingPolicy TimingPolicy;
|
||||
TimingPolicy.bInspectionEnabled = true;
|
||||
TimingPolicy.InspectionDurationMs = 15000;
|
||||
TimingPolicy.bInspectionPenaltiesEnabled = true;
|
||||
TimingPolicy.bAllowManualPenalty = true;
|
||||
TimingPolicy.bHideRunningTime = false;
|
||||
|
||||
FHyperTwistTrainingSplitPhaseDefinition RecognitionPhase;
|
||||
RecognitionPhase.PhaseId = TEXT("recognition");
|
||||
RecognitionPhase.Label = TEXT("Recognition");
|
||||
RecognitionPhase.Order = 0;
|
||||
|
||||
FHyperTwistTrainingSplitPhaseDefinition ExecutionPhase;
|
||||
ExecutionPhase.PhaseId = TEXT("execution");
|
||||
ExecutionPhase.Label = TEXT("Execution");
|
||||
ExecutionPhase.Order = 1;
|
||||
|
||||
TimingPolicy.SplitPhases = {RecognitionPhase, ExecutionPhase};
|
||||
return TimingPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterContentPack()
|
||||
|
|
@ -102,6 +144,7 @@ FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterC
|
|||
TDeck.bSupportsAlgorithmReveal = true;
|
||||
TDeck.bSupportsVirtualCube = true;
|
||||
TDeck.bSupportsSmartCube = true;
|
||||
TDeck.TimingPolicy = HyperTwistTrainingCatalogLibraryInternal::MakeStandardAlgorithmTimingPolicy();
|
||||
TDeck.Cases = {OLLT01, OLLT02};
|
||||
TDeck.DifficultyBand = TEXT("intermediate");
|
||||
TDeck.Tags = {TEXT("classic"), TEXT("oll"), TEXT("alg-trainer"), TEXT("subset:t")};
|
||||
|
|
@ -144,6 +187,7 @@ FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterC
|
|||
UDeck.bSupportsAlgorithmReveal = true;
|
||||
UDeck.bSupportsVirtualCube = true;
|
||||
UDeck.bSupportsSmartCube = true;
|
||||
UDeck.TimingPolicy = HyperTwistTrainingCatalogLibraryInternal::MakeStandardAlgorithmTimingPolicy();
|
||||
UDeck.Cases = {OLLU01, OLLU02};
|
||||
UDeck.DifficultyBand = TEXT("intermediate");
|
||||
UDeck.Tags = {TEXT("classic"), TEXT("oll"), TEXT("alg-trainer"), TEXT("subset:u")};
|
||||
|
|
@ -192,6 +236,46 @@ TArray<FHyperTwistContentPack> UHyperTwistTrainingCatalogLibrary::MakePhase3Trai
|
|||
};
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
|
||||
const TArray<FHyperTwistContentPack>& TrainingCatalog,
|
||||
const FString& DeckId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
)
|
||||
{
|
||||
OutDeck = FHyperTwistTrainingDeck();
|
||||
if (DeckId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FHyperTwistContentPack& ContentPack : TrainingCatalog)
|
||||
{
|
||||
if (!ContentPack.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingTrack& Track : ContentPack.Tracks)
|
||||
{
|
||||
if (!Track.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingDeck& Deck : Track.Decks)
|
||||
{
|
||||
if (Deck.DeckId == DeckId)
|
||||
{
|
||||
OutDeck = Deck;
|
||||
return OutDeck.IsStructurallyValid();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingCatalogLibrary::TryGetTrackFromContentPack(
|
||||
const FHyperTwistContentPack& ContentPack,
|
||||
const FString& TrackId,
|
||||
|
|
@ -259,3 +343,69 @@ bool UHyperTwistTrainingCatalogLibrary::TryGetDefaultDeckFromContentPack(
|
|||
OutDeck = ContentPack.Tracks[0].Decks[0];
|
||||
return OutDeck.IsStructurallyValid();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
|
||||
const TArray<FHyperTwistContentPack>& TrainingCatalog
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingDeck SourceDeck;
|
||||
if (!SessionTemplate.IsStructurallyValid()
|
||||
|| !TryFindDeckInCatalog(TrainingCatalog, SessionTemplate.FocusDeckId, SourceDeck))
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck MaterializedDeck = SourceDeck;
|
||||
MaterializedDeck.DeckId = FString::Printf(TEXT("%s/template/%s"), *SourceDeck.DeckId, *SessionTemplate.TemplateId);
|
||||
MaterializedDeck.Title = FString::Printf(TEXT("%s - %s"), *SourceDeck.Title, *SessionTemplate.Title);
|
||||
MaterializedDeck.SelectionPolicy = SessionTemplate.SuggestedSelectionPolicy;
|
||||
MaterializedDeck.Tags.AddUnique(TEXT("template"));
|
||||
MaterializedDeck.Tags.AddUnique(HyperTwistTrainingCatalogLibraryInternal::TemplateKindToTag(SessionTemplate.TemplateKind));
|
||||
if (SessionTemplate.bPreferReviewRun)
|
||||
{
|
||||
MaterializedDeck.Tags.AddUnique(TEXT("review-template"));
|
||||
}
|
||||
if (SessionTemplate.bCoachReview)
|
||||
{
|
||||
MaterializedDeck.Tags.AddUnique(TEXT("coach-template"));
|
||||
}
|
||||
MaterializedDeck.DeliveryModes.AddUnique(SessionTemplate.SuggestedMode);
|
||||
|
||||
TArray<FHyperTwistTrainingCase> MaterializedCases;
|
||||
if (SessionTemplate.FocusCaseIds.Num() > 0)
|
||||
{
|
||||
for (const FString& CaseId : SessionTemplate.FocusCaseIds)
|
||||
{
|
||||
const FHyperTwistTrainingCase* MatchingCase = SourceDeck.Cases.FindByPredicate(
|
||||
[&CaseId](const FHyperTwistTrainingCase& Candidate)
|
||||
{
|
||||
return Candidate.CaseId == CaseId;
|
||||
}
|
||||
);
|
||||
if (MatchingCase != nullptr)
|
||||
{
|
||||
MaterializedCases.Add(*MatchingCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MaterializedCases = SourceDeck.Cases;
|
||||
}
|
||||
|
||||
const int32 RecommendedCaseCount = FMath::Max(1, SessionTemplate.RecommendedCaseCount);
|
||||
if (MaterializedCases.Num() > RecommendedCaseCount)
|
||||
{
|
||||
MaterializedCases.SetNum(RecommendedCaseCount, EAllowShrinking::No);
|
||||
}
|
||||
|
||||
MaterializedDeck.Cases = MoveTemp(MaterializedCases);
|
||||
if (MaterializedDeck.Cases.Num() == 0)
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
MaterializedDeck.SourceAttribution.SourceNotes = SessionTemplate.GuidanceLabel;
|
||||
return MaterializedDeck;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,232 @@
|
|||
|
||||
namespace HyperTwistTrainingLibraryInternal
|
||||
{
|
||||
int32 GetPenaltyOffsetMs(EHyperTwistTrainingPenalty Penalty)
|
||||
{
|
||||
switch (Penalty)
|
||||
{
|
||||
case EHyperTwistTrainingPenalty::Plus2:
|
||||
return 2000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
EHyperTwistTrainingPenalty CombinePenalties(
|
||||
EHyperTwistTrainingPenalty LeftPenalty,
|
||||
EHyperTwistTrainingPenalty RightPenalty
|
||||
)
|
||||
{
|
||||
if (LeftPenalty == EHyperTwistTrainingPenalty::DNF || RightPenalty == EHyperTwistTrainingPenalty::DNF)
|
||||
{
|
||||
return EHyperTwistTrainingPenalty::DNF;
|
||||
}
|
||||
|
||||
if (LeftPenalty == EHyperTwistTrainingPenalty::Plus2 || RightPenalty == EHyperTwistTrainingPenalty::Plus2)
|
||||
{
|
||||
return EHyperTwistTrainingPenalty::Plus2;
|
||||
}
|
||||
|
||||
return EHyperTwistTrainingPenalty::None;
|
||||
}
|
||||
|
||||
EHyperTwistTrainingPenalty ComputeInspectionPenalty(
|
||||
int32 InspectionElapsedMs,
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
)
|
||||
{
|
||||
if (!TimingPolicy.bInspectionEnabled || !TimingPolicy.bInspectionPenaltiesEnabled)
|
||||
{
|
||||
return EHyperTwistTrainingPenalty::None;
|
||||
}
|
||||
|
||||
const int32 LimitMs = FMath::Max(0, TimingPolicy.InspectionDurationMs);
|
||||
if (LimitMs <= 0 || InspectionElapsedMs <= LimitMs)
|
||||
{
|
||||
return EHyperTwistTrainingPenalty::None;
|
||||
}
|
||||
|
||||
if (InspectionElapsedMs <= LimitMs + 2000)
|
||||
{
|
||||
return EHyperTwistTrainingPenalty::Plus2;
|
||||
}
|
||||
|
||||
return EHyperTwistTrainingPenalty::DNF;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimingPolicy NormalizeTimingPolicyInternal(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingTimingPolicy Normalized = TimingPolicy;
|
||||
Normalized.InspectionDurationMs = FMath::Max(0, TimingPolicy.InspectionDurationMs);
|
||||
Normalized.SplitPhases.Reset();
|
||||
|
||||
TArray<FHyperTwistTrainingSplitPhaseDefinition> SortedPhases = TimingPolicy.SplitPhases;
|
||||
SortedPhases.Sort([](const FHyperTwistTrainingSplitPhaseDefinition& Left, const FHyperTwistTrainingSplitPhaseDefinition& Right)
|
||||
{
|
||||
if (Left.Order != Right.Order)
|
||||
{
|
||||
return Left.Order < Right.Order;
|
||||
}
|
||||
|
||||
return Left.PhaseId < Right.PhaseId;
|
||||
});
|
||||
|
||||
TSet<FString> SeenPhaseIds;
|
||||
int32 NextOrder = 0;
|
||||
for (FHyperTwistTrainingSplitPhaseDefinition PhaseDefinition : SortedPhases)
|
||||
{
|
||||
if (!PhaseDefinition.IsStructurallyValid() || SeenPhaseIds.Contains(PhaseDefinition.PhaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenPhaseIds.Add(PhaseDefinition.PhaseId);
|
||||
PhaseDefinition.Order = NextOrder++;
|
||||
if (PhaseDefinition.Label.IsEmpty())
|
||||
{
|
||||
PhaseDefinition.Label = PhaseDefinition.PhaseId;
|
||||
}
|
||||
|
||||
Normalized.SplitPhases.Add(PhaseDefinition);
|
||||
}
|
||||
|
||||
return Normalized;
|
||||
}
|
||||
|
||||
int32 ResolveSplitOrder(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
|
||||
const FHyperTwistTrainingSplitCapture& SplitCapture
|
||||
)
|
||||
{
|
||||
for (const FHyperTwistTrainingSplitPhaseDefinition& PhaseDefinition : TimingPolicy.SplitPhases)
|
||||
{
|
||||
if (PhaseDefinition.PhaseId == SplitCapture.PhaseId)
|
||||
{
|
||||
return PhaseDefinition.Order;
|
||||
}
|
||||
}
|
||||
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
FString ResolveSplitLabel(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
|
||||
const FHyperTwistTrainingSplitCapture& SplitCapture
|
||||
)
|
||||
{
|
||||
if (!SplitCapture.Label.IsEmpty())
|
||||
{
|
||||
return SplitCapture.Label;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingSplitPhaseDefinition& PhaseDefinition : TimingPolicy.SplitPhases)
|
||||
{
|
||||
if (PhaseDefinition.PhaseId == SplitCapture.PhaseId)
|
||||
{
|
||||
return PhaseDefinition.Label;
|
||||
}
|
||||
}
|
||||
|
||||
return SplitCapture.PhaseId;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSplitCapture> NormalizeSplitCaptures(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
|
||||
const TArray<FHyperTwistTrainingSplitCapture>& SplitCaptures
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingSplitCapture> NormalizedCaptures;
|
||||
TSet<FString> SeenPhaseIds;
|
||||
|
||||
for (FHyperTwistTrainingSplitCapture SplitCapture : SplitCaptures)
|
||||
{
|
||||
if (!SplitCapture.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SeenPhaseIds.Contains(SplitCapture.PhaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SplitCapture.TimestampMs = FMath::Max(0, SplitCapture.TimestampMs);
|
||||
SplitCapture.Label = ResolveSplitLabel(TimingPolicy, SplitCapture);
|
||||
SeenPhaseIds.Add(SplitCapture.PhaseId);
|
||||
NormalizedCaptures.Add(SplitCapture);
|
||||
}
|
||||
|
||||
NormalizedCaptures.Sort([&TimingPolicy](const FHyperTwistTrainingSplitCapture& Left, const FHyperTwistTrainingSplitCapture& Right)
|
||||
{
|
||||
const int32 LeftOrder = ResolveSplitOrder(TimingPolicy, Left);
|
||||
const int32 RightOrder = ResolveSplitOrder(TimingPolicy, Right);
|
||||
if (LeftOrder != INDEX_NONE || RightOrder != INDEX_NONE)
|
||||
{
|
||||
if (LeftOrder == INDEX_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (RightOrder == INDEX_NONE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (LeftOrder != RightOrder)
|
||||
{
|
||||
return LeftOrder < RightOrder;
|
||||
}
|
||||
}
|
||||
|
||||
return Left.TimestampMs < Right.TimestampMs;
|
||||
});
|
||||
|
||||
return NormalizedCaptures;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistPhaseTiming> BuildDerivedPhaseTimings(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
|
||||
const FHyperTwistTrainingTimingBreakdown& TimingBreakdown
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistPhaseTiming> PhaseTimings;
|
||||
const TArray<FHyperTwistTrainingSplitCapture> NormalizedCaptures = NormalizeSplitCaptures(
|
||||
TimingPolicy,
|
||||
TimingBreakdown.SplitCaptures
|
||||
);
|
||||
|
||||
int32 PhaseStartMs = 0;
|
||||
int32 ConsumedPhaseCount = 0;
|
||||
for (const FHyperTwistTrainingSplitCapture& SplitCapture : NormalizedCaptures)
|
||||
{
|
||||
const int32 PhaseEndMs = FMath::Max(PhaseStartMs, SplitCapture.TimestampMs);
|
||||
|
||||
FHyperTwistPhaseTiming PhaseTiming;
|
||||
PhaseTiming.PhaseKind = !SplitCapture.Label.IsEmpty() ? SplitCapture.Label : SplitCapture.PhaseId;
|
||||
PhaseTiming.StartedAtMs = PhaseStartMs;
|
||||
PhaseTiming.EndedAtMs = PhaseEndMs;
|
||||
PhaseTiming.DurationMs = FMath::Max(0, PhaseEndMs - PhaseStartMs);
|
||||
PhaseTimings.Add(PhaseTiming);
|
||||
|
||||
PhaseStartMs = PhaseEndMs;
|
||||
ConsumedPhaseCount += 1;
|
||||
}
|
||||
|
||||
if (TimingBreakdown.RawSolveTimeMs > PhaseStartMs && TimingPolicy.SplitPhases.IsValidIndex(ConsumedPhaseCount))
|
||||
{
|
||||
const FHyperTwistTrainingSplitPhaseDefinition& FinalPhaseDefinition = TimingPolicy.SplitPhases[ConsumedPhaseCount];
|
||||
|
||||
FHyperTwistPhaseTiming PhaseTiming;
|
||||
PhaseTiming.PhaseKind = FinalPhaseDefinition.Label;
|
||||
PhaseTiming.StartedAtMs = PhaseStartMs;
|
||||
PhaseTiming.EndedAtMs = TimingBreakdown.RawSolveTimeMs;
|
||||
PhaseTiming.DurationMs = FMath::Max(0, PhaseTiming.EndedAtMs - PhaseTiming.StartedAtMs);
|
||||
PhaseTimings.Add(PhaseTiming);
|
||||
}
|
||||
|
||||
return PhaseTimings;
|
||||
}
|
||||
|
||||
EHyperTwistCaptureMode ToCaptureMode(EHyperTwistTrainingDeliveryMode Mode)
|
||||
{
|
||||
switch (Mode)
|
||||
|
|
@ -159,7 +385,8 @@ namespace HyperTwistTrainingLibraryInternal
|
|||
|
||||
FHyperTwistReplayPacket MakeReplayPacketForRun(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
const FHyperTwistTrainingCase& TrainingCase
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
)
|
||||
{
|
||||
FHyperTwistReplayPacket Packet;
|
||||
|
|
@ -170,16 +397,31 @@ namespace HyperTwistTrainingLibraryInternal
|
|||
Packet.StartedAtUtc = Session.StartedAtUtc;
|
||||
Packet.EndedAtUtc = Session.EndedAtUtc;
|
||||
|
||||
FHyperTwistReplayEvent TimerStartEvent;
|
||||
TimerStartEvent.EventId = TEXT("evt_timer_start");
|
||||
TimerStartEvent.Sequence = 1;
|
||||
TimerStartEvent.TimeMs = 0;
|
||||
TimerStartEvent.EventType = EHyperTwistReplayEventType::TimerStart;
|
||||
const FHyperTwistTrainingTimingPolicy NormalizedTimingPolicy = NormalizeTimingPolicyInternal(TimingPolicy);
|
||||
FHyperTwistReplayEvent StartEvent;
|
||||
StartEvent.Sequence = 1;
|
||||
StartEvent.TimeMs = 0;
|
||||
StartEvent.EventType = NormalizedTimingPolicy.bInspectionEnabled
|
||||
? EHyperTwistReplayEventType::InspectionStart
|
||||
: EHyperTwistReplayEventType::TimerStart;
|
||||
StartEvent.EventId = NormalizedTimingPolicy.bInspectionEnabled
|
||||
? TEXT("evt_inspection_start")
|
||||
: TEXT("evt_timer_start");
|
||||
|
||||
Packet = UHyperTwistReplayLibrary::AppendReplayEvent(Packet, TimerStartEvent);
|
||||
Packet = UHyperTwistReplayLibrary::AppendReplayEvent(Packet, StartEvent);
|
||||
return Packet;
|
||||
}
|
||||
|
||||
int32 GetObservedAttemptTimeMs(const FHyperTwistTrainingAttempt& Attempt)
|
||||
{
|
||||
return FMath::Max(
|
||||
0,
|
||||
Attempt.TimingBreakdown.InspectionElapsedMs
|
||||
+ Attempt.TimingBreakdown.RawSolveTimeMs
|
||||
+ FMath::Max(0, Attempt.RecognitionTimeMs)
|
||||
);
|
||||
}
|
||||
|
||||
float ComputeSelectionScore(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistProgressionMemory* Memory,
|
||||
|
|
@ -274,6 +516,17 @@ FHyperTwistContentPack UHyperTwistTrainingLibrary::MakeSampleClassicContentPack(
|
|||
Deck.bSupportsAlgorithmReveal = true;
|
||||
Deck.bSupportsVirtualCube = true;
|
||||
Deck.bSupportsSmartCube = false;
|
||||
|
||||
FHyperTwistTrainingSplitPhaseDefinition CrossPhaseDefinition;
|
||||
CrossPhaseDefinition.PhaseId = TEXT("cross");
|
||||
CrossPhaseDefinition.Label = TEXT("Cross");
|
||||
CrossPhaseDefinition.Order = 0;
|
||||
|
||||
Deck.TimingPolicy.bInspectionEnabled = true;
|
||||
Deck.TimingPolicy.InspectionDurationMs = 15000;
|
||||
Deck.TimingPolicy.bInspectionPenaltiesEnabled = true;
|
||||
Deck.TimingPolicy.bAllowManualPenalty = true;
|
||||
Deck.TimingPolicy.SplitPhases = {CrossPhaseDefinition};
|
||||
Deck.Cases = {CaseA, CaseB};
|
||||
Deck.DifficultyBand = TEXT("starter");
|
||||
Deck.Tags = {TEXT("classic"), TEXT("cross")};
|
||||
|
|
@ -403,7 +656,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingLibrary::StartTrainingRun(
|
|||
);
|
||||
RunState.ReplayPacket = HyperTwistTrainingLibraryInternal::MakeReplayPacketForRun(
|
||||
RunState.Session,
|
||||
RunState.CurrentSelection.TrainingCase
|
||||
RunState.CurrentSelection.TrainingCase,
|
||||
Deck.TimingPolicy
|
||||
);
|
||||
if (RunState.ReplayPacket.Events.Num() > 0)
|
||||
{
|
||||
|
|
@ -519,6 +773,103 @@ FHyperTwistTrainingSession UHyperTwistTrainingLibrary::RecordTrainingAttempt(
|
|||
return UpdatedSession;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimingPolicy UHyperTwistTrainingLibrary::NormalizeTimingPolicy(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
)
|
||||
{
|
||||
return HyperTwistTrainingLibraryInternal::NormalizeTimingPolicyInternal(TimingPolicy);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingAttempt UHyperTwistTrainingLibrary::NormalizeTrainingAttemptTiming(
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingAttempt NormalizedAttempt = Attempt;
|
||||
FHyperTwistTrainingTimingBreakdown TimingBreakdown = Attempt.TimingBreakdown;
|
||||
const FHyperTwistTrainingTimingPolicy NormalizedTimingPolicy = NormalizeTimingPolicy(TimingPolicy);
|
||||
|
||||
TimingBreakdown.InspectionElapsedMs = NormalizedTimingPolicy.bInspectionEnabled
|
||||
? FMath::Max(0, TimingBreakdown.InspectionElapsedMs)
|
||||
: 0;
|
||||
TimingBreakdown.ManualPenalty = NormalizedTimingPolicy.bAllowManualPenalty
|
||||
? TimingBreakdown.ManualPenalty
|
||||
: EHyperTwistTrainingPenalty::None;
|
||||
TimingBreakdown.InspectionPenalty = HyperTwistTrainingLibraryInternal::ComputeInspectionPenalty(
|
||||
TimingBreakdown.InspectionElapsedMs,
|
||||
NormalizedTimingPolicy
|
||||
);
|
||||
TimingBreakdown.AppliedPenalty = HyperTwistTrainingLibraryInternal::CombinePenalties(
|
||||
TimingBreakdown.InspectionPenalty,
|
||||
TimingBreakdown.ManualPenalty
|
||||
);
|
||||
|
||||
const int32 RecognitionTimeMs = FMath::Max(0, Attempt.RecognitionTimeMs);
|
||||
int32 RawSolveTimeMs = FMath::Max(0, TimingBreakdown.RawSolveTimeMs);
|
||||
if (RawSolveTimeMs == 0 && Attempt.ExecutionTimeMs > 0)
|
||||
{
|
||||
RawSolveTimeMs = Attempt.ExecutionTimeMs;
|
||||
}
|
||||
if (RawSolveTimeMs == 0 && Attempt.TotalTimeMs > 0)
|
||||
{
|
||||
RawSolveTimeMs = FMath::Max(
|
||||
0,
|
||||
Attempt.TotalTimeMs
|
||||
- RecognitionTimeMs
|
||||
- HyperTwistTrainingLibraryInternal::GetPenaltyOffsetMs(TimingBreakdown.AppliedPenalty)
|
||||
);
|
||||
}
|
||||
|
||||
TimingBreakdown.RawSolveTimeMs = RawSolveTimeMs;
|
||||
TimingBreakdown.SplitCaptures = HyperTwistTrainingLibraryInternal::NormalizeSplitCaptures(
|
||||
NormalizedTimingPolicy,
|
||||
TimingBreakdown.SplitCaptures
|
||||
);
|
||||
TimingBreakdown.DerivedPhaseTimings = HyperTwistTrainingLibraryInternal::BuildDerivedPhaseTimings(
|
||||
NormalizedTimingPolicy,
|
||||
TimingBreakdown
|
||||
);
|
||||
TimingBreakdown.PenaltyOffsetMs = HyperTwistTrainingLibraryInternal::GetPenaltyOffsetMs(
|
||||
TimingBreakdown.AppliedPenalty
|
||||
);
|
||||
|
||||
if (NormalizedAttempt.Result == EHyperTwistTrainingAttemptResult::Aborted)
|
||||
{
|
||||
TimingBreakdown.bDidNotFinish = true;
|
||||
}
|
||||
|
||||
if (TimingBreakdown.AppliedPenalty == EHyperTwistTrainingPenalty::DNF)
|
||||
{
|
||||
TimingBreakdown.bDidNotFinish = true;
|
||||
if (NormalizedAttempt.Result != EHyperTwistTrainingAttemptResult::Aborted)
|
||||
{
|
||||
NormalizedAttempt.Result = EHyperTwistTrainingAttemptResult::DNF;
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedAttempt.ExecutionTimeMs = RawSolveTimeMs;
|
||||
NormalizedAttempt.TimingBreakdown = TimingBreakdown;
|
||||
|
||||
if (TimingBreakdown.bDidNotFinish || NormalizedAttempt.Result == EHyperTwistTrainingAttemptResult::DNF)
|
||||
{
|
||||
NormalizedAttempt.TotalTimeMs = 0;
|
||||
NormalizedAttempt.TimingBreakdown.FinalTimeMs = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 FinalTimeMs = FMath::Max(
|
||||
0,
|
||||
RawSolveTimeMs
|
||||
+ RecognitionTimeMs
|
||||
+ TimingBreakdown.PenaltyOffsetMs
|
||||
);
|
||||
NormalizedAttempt.TotalTimeMs = FinalTimeMs;
|
||||
NormalizedAttempt.TimingBreakdown.FinalTimeMs = FinalTimeMs;
|
||||
}
|
||||
|
||||
return NormalizedAttempt;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSession UHyperTwistTrainingLibrary::CompleteTrainingSession(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
bool bAbortSession
|
||||
|
|
@ -609,6 +960,7 @@ FHyperTwistProgressionMemory UHyperTwistTrainingLibrary::UpdateProgressionMemory
|
|||
UpdatedMemory.ResurfacePriority += 1.25f;
|
||||
break;
|
||||
case EHyperTwistTrainingAttemptResult::Timeout:
|
||||
case EHyperTwistTrainingAttemptResult::DNF:
|
||||
UpdatedMemory.StrengthScore -= 0.50f;
|
||||
UpdatedMemory.ResurfacePriority += 1.50f;
|
||||
break;
|
||||
|
|
@ -713,6 +1065,7 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
|
|||
Summary.TotalTimeMs += FMath::Max(0, Attempt.TotalTimeMs);
|
||||
Summary.TotalExecutionTimeMs += FMath::Max(0, Attempt.ExecutionTimeMs);
|
||||
Summary.TotalRecognitionTimeMs += FMath::Max(0, Attempt.RecognitionTimeMs);
|
||||
Summary.TotalInspectionTimeMs += FMath::Max(0, Attempt.TimingBreakdown.InspectionElapsedMs);
|
||||
Summary.TotalMistakes += FMath::Max(0, Attempt.Mistakes);
|
||||
|
||||
if (Attempt.TotalTimeMs > 0)
|
||||
|
|
@ -722,6 +1075,18 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
|
|||
: FMath::Min(Summary.BestTotalTimeMs, Attempt.TotalTimeMs);
|
||||
}
|
||||
|
||||
if (Attempt.TimingBreakdown.RawSolveTimeMs > 0)
|
||||
{
|
||||
Summary.BestRawSolveTimeMs = Summary.BestRawSolveTimeMs == 0
|
||||
? Attempt.TimingBreakdown.RawSolveTimeMs
|
||||
: FMath::Min(Summary.BestRawSolveTimeMs, Attempt.TimingBreakdown.RawSolveTimeMs);
|
||||
}
|
||||
|
||||
if (Attempt.TimingBreakdown.AppliedPenalty == EHyperTwistTrainingPenalty::Plus2)
|
||||
{
|
||||
++Summary.Plus2PenaltyCount;
|
||||
}
|
||||
|
||||
switch (Attempt.Result)
|
||||
{
|
||||
case EHyperTwistTrainingAttemptResult::Success:
|
||||
|
|
@ -733,6 +1098,9 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
|
|||
case EHyperTwistTrainingAttemptResult::Timeout:
|
||||
++Summary.TimeoutCount;
|
||||
break;
|
||||
case EHyperTwistTrainingAttemptResult::DNF:
|
||||
++Summary.DNFCount;
|
||||
break;
|
||||
case EHyperTwistTrainingAttemptResult::Aborted:
|
||||
++Summary.AbortedCount;
|
||||
break;
|
||||
|
|
@ -752,6 +1120,8 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
|
|||
if (Summary.CompletedAttemptCount > 0)
|
||||
{
|
||||
Summary.AverageTotalTimeMs = Summary.TotalTimeMs / Summary.CompletedAttemptCount;
|
||||
Summary.AverageRawSolveTimeMs = Summary.TotalExecutionTimeMs / Summary.CompletedAttemptCount;
|
||||
Summary.AverageInspectionTimeMs = Summary.TotalInspectionTimeMs / Summary.CompletedAttemptCount;
|
||||
Summary.SuccessRate = static_cast<float>(Summary.SuccessCount) / static_cast<float>(Summary.CompletedAttemptCount);
|
||||
}
|
||||
|
||||
|
|
@ -781,6 +1151,7 @@ FHyperTwistReplayEvent UHyperTwistTrainingLibrary::MakeTrainingAttemptReplayEven
|
|||
Payload.TotalTimeMs = Attempt.TotalTimeMs;
|
||||
Payload.ExecutionTimeMs = Attempt.ExecutionTimeMs;
|
||||
Payload.RecognitionTimeMs = Attempt.RecognitionTimeMs;
|
||||
Payload.TimingBreakdown = Attempt.TimingBreakdown;
|
||||
Payload.Mistakes = Attempt.Mistakes;
|
||||
Payload.ReplayId = Attempt.ReplayId;
|
||||
Payload.CompletedAtUtc = Attempt.CompletedAtUtc;
|
||||
|
|
@ -789,7 +1160,10 @@ FHyperTwistReplayEvent UHyperTwistTrainingLibrary::MakeTrainingAttemptReplayEven
|
|||
? TEXT("training_attempt")
|
||||
: FString::Printf(TEXT("training_attempt_%s"), *Attempt.AttemptId);
|
||||
ReplayEvent.Sequence = 0;
|
||||
ReplayEvent.TimeMs = FMath::Max(0, TimeMs > 0 ? TimeMs : Attempt.TotalTimeMs);
|
||||
ReplayEvent.TimeMs = FMath::Max(
|
||||
0,
|
||||
TimeMs > 0 ? TimeMs : HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(Attempt)
|
||||
);
|
||||
ReplayEvent.EventType = EHyperTwistReplayEventType::Annotation;
|
||||
ReplayEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(Payload);
|
||||
return ReplayEvent;
|
||||
|
|
@ -802,13 +1176,56 @@ FHyperTwistReplayPacket UHyperTwistTrainingLibrary::AppendTrainingAttemptToRepla
|
|||
int32 TimeMs
|
||||
)
|
||||
{
|
||||
FHyperTwistReplayPacket UpdatedReplayPacket = ReplayPacket;
|
||||
const int32 InspectionOffsetMs = FMath::Max(0, Attempt.TimingBreakdown.InspectionElapsedMs);
|
||||
|
||||
if (InspectionOffsetMs > 0)
|
||||
{
|
||||
FHyperTwistReplayEvent InspectionEndEvent;
|
||||
InspectionEndEvent.EventType = EHyperTwistReplayEventType::InspectionEnd;
|
||||
InspectionEndEvent.TimeMs = InspectionOffsetMs;
|
||||
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, InspectionEndEvent);
|
||||
|
||||
FHyperTwistReplayEvent TimerStartEvent;
|
||||
TimerStartEvent.EventType = EHyperTwistReplayEventType::TimerStart;
|
||||
TimerStartEvent.TimeMs = InspectionOffsetMs;
|
||||
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, TimerStartEvent);
|
||||
}
|
||||
|
||||
for (int32 PhaseIndex = 0; PhaseIndex < Attempt.TimingBreakdown.DerivedPhaseTimings.Num(); ++PhaseIndex)
|
||||
{
|
||||
const FHyperTwistPhaseTiming& PhaseTiming = Attempt.TimingBreakdown.DerivedPhaseTimings[PhaseIndex];
|
||||
const FString PhaseId = FString::Printf(TEXT("phase_%02d"), PhaseIndex + 1);
|
||||
|
||||
FHyperTwistReplayPhaseMarkPayload PhaseStartPayload;
|
||||
PhaseStartPayload.PhaseId = PhaseId;
|
||||
PhaseStartPayload.PhaseKind = PhaseTiming.PhaseKind;
|
||||
PhaseStartPayload.PhaseSource = TEXT("training-timing");
|
||||
PhaseStartPayload.PhaseAction = TEXT("start");
|
||||
|
||||
FHyperTwistReplayEvent PhaseStartEvent;
|
||||
PhaseStartEvent.EventType = EHyperTwistReplayEventType::PhaseMark;
|
||||
PhaseStartEvent.TimeMs = InspectionOffsetMs + FMath::Max(0, PhaseTiming.StartedAtMs);
|
||||
PhaseStartEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(PhaseStartPayload);
|
||||
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, PhaseStartEvent);
|
||||
|
||||
FHyperTwistReplayPhaseMarkPayload PhaseCompletePayload = PhaseStartPayload;
|
||||
PhaseCompletePayload.PhaseAction = TEXT("complete");
|
||||
|
||||
FHyperTwistReplayEvent PhaseCompleteEvent;
|
||||
PhaseCompleteEvent.EventType = EHyperTwistReplayEventType::PhaseMark;
|
||||
PhaseCompleteEvent.TimeMs = InspectionOffsetMs + FMath::Max(0, PhaseTiming.EndedAtMs);
|
||||
PhaseCompleteEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(PhaseCompletePayload);
|
||||
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, PhaseCompleteEvent);
|
||||
}
|
||||
|
||||
const FHyperTwistReplayEvent ReplayEvent = MakeTrainingAttemptReplayEvent(Session, Attempt, TimeMs);
|
||||
if (!ReplayEvent.IsStructurallyValid())
|
||||
{
|
||||
return ReplayPacket;
|
||||
return UpdatedReplayPacket;
|
||||
}
|
||||
|
||||
return UHyperTwistReplayLibrary::AppendReplayEvent(ReplayPacket, ReplayEvent);
|
||||
return UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, ReplayEvent);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTrainingAttempt(
|
||||
|
|
@ -826,6 +1243,11 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
|
|||
return Resolution;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingAttempt NormalizedAttempt = NormalizeTrainingAttemptTiming(
|
||||
Attempt,
|
||||
Deck.TimingPolicy
|
||||
);
|
||||
|
||||
FHyperTwistProgressionMemory SeedMemory;
|
||||
bool bFoundExistingMemory = false;
|
||||
for (const FHyperTwistProgressionMemory& Memory : Memories)
|
||||
|
|
@ -843,8 +1265,12 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
|
|||
SeedMemory = MakeInitialProgressionMemory(Session, TrainingCase);
|
||||
}
|
||||
|
||||
Resolution.UpdatedProgressionMemory = UpdateProgressionMemory(Session, TrainingCase, SeedMemory, Attempt);
|
||||
Resolution.ReplayEvent = MakeTrainingAttemptReplayEvent(Session, Attempt, TimeMs);
|
||||
Resolution.UpdatedProgressionMemory = UpdateProgressionMemory(Session, TrainingCase, SeedMemory, NormalizedAttempt);
|
||||
Resolution.ReplayEvent = MakeTrainingAttemptReplayEvent(
|
||||
Session,
|
||||
NormalizedAttempt,
|
||||
TimeMs > 0 ? TimeMs : HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(NormalizedAttempt)
|
||||
);
|
||||
|
||||
TArray<FHyperTwistProgressionMemory> UpdatedMemories = Memories;
|
||||
bool bReplacedExistingMemory = false;
|
||||
|
|
@ -863,7 +1289,7 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
|
|||
UpdatedMemories.Add(Resolution.UpdatedProgressionMemory);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSession ReviewSession = RecordTrainingAttempt(Session, Attempt);
|
||||
const FHyperTwistTrainingSession ReviewSession = RecordTrainingAttempt(Session, NormalizedAttempt);
|
||||
const FHyperTwistTrainingAdvanceResult AdvanceResult = AdvanceTrainingSessionWithSelection(
|
||||
ReviewSession,
|
||||
Deck,
|
||||
|
|
@ -904,6 +1330,10 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
|
|||
{
|
||||
NormalizedAttempt.ReplayId = RunState.ReplayPacket.ReplayId;
|
||||
}
|
||||
NormalizedAttempt = NormalizeTrainingAttemptTiming(NormalizedAttempt, RunState.ActiveDeck.TimingPolicy);
|
||||
const int32 ObservedAttemptTimeMs = TimeMs > 0
|
||||
? TimeMs
|
||||
: HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(NormalizedAttempt);
|
||||
|
||||
StepResult.AttemptResolution = ResolveTrainingAttempt(
|
||||
RunState.Session,
|
||||
|
|
@ -911,7 +1341,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
|
|||
*TrainingCase,
|
||||
RunState.Memories,
|
||||
NormalizedAttempt,
|
||||
TimeMs
|
||||
ObservedAttemptTimeMs
|
||||
);
|
||||
if (!StepResult.AttemptResolution.IsStructurallyValid())
|
||||
{
|
||||
|
|
@ -925,7 +1355,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
|
|||
RunState.ReplayPacket,
|
||||
RunState.Session,
|
||||
NormalizedAttempt,
|
||||
TimeMs
|
||||
ObservedAttemptTimeMs
|
||||
);
|
||||
if (StepResult.UpdatedRunState.ReplayPacket.Events.Num() > 0)
|
||||
{
|
||||
|
|
@ -970,7 +1400,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
|
|||
|
||||
FHyperTwistReplayEvent SolveEndEvent;
|
||||
SolveEndEvent.EventType = EHyperTwistReplayEventType::SolveEnd;
|
||||
SolveEndEvent.TimeMs = FMath::Max(TimeMs, NormalizedAttempt.TotalTimeMs);
|
||||
SolveEndEvent.TimeMs = ObservedAttemptTimeMs;
|
||||
StepResult.UpdatedRunState.ReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(
|
||||
StepResult.UpdatedRunState.ReplayPacket,
|
||||
SolveEndEvent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
namespace HyperTwistTrainingPersistenceLibraryInternal
|
||||
{
|
||||
FString ResolvePath(const FString& RepositoryPath)
|
||||
{
|
||||
if (RepositoryPath.IsEmpty())
|
||||
{
|
||||
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HyperTwist"), TEXT("training-repository.json"));
|
||||
}
|
||||
|
||||
return FPaths::IsRelative(RepositoryPath)
|
||||
? FPaths::Combine(FPaths::ProjectSavedDir(), RepositoryPath)
|
||||
: RepositoryPath;
|
||||
}
|
||||
}
|
||||
|
||||
FString UHyperTwistTrainingPersistenceLibrary::GetDefaultTrainingRepositoryPath()
|
||||
{
|
||||
return HyperTwistTrainingPersistenceLibraryInternal::ResolvePath(FString());
|
||||
}
|
||||
|
||||
FString UHyperTwistTrainingPersistenceLibrary::ResolveTrainingRepositoryPath(const FString& RepositoryPath)
|
||||
{
|
||||
return HyperTwistTrainingPersistenceLibraryInternal::ResolvePath(RepositoryPath);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingPersistenceLibrary::SaveTrainingRepositoryStateToFile(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& RepositoryPath,
|
||||
FString& OutResolvedPath
|
||||
)
|
||||
{
|
||||
OutResolvedPath = ResolveTrainingRepositoryPath(RepositoryPath);
|
||||
const FString Directory = FPaths::GetPath(OutResolvedPath);
|
||||
if (!Directory.IsEmpty())
|
||||
{
|
||||
IFileManager::Get().MakeDirectory(*Directory, true);
|
||||
}
|
||||
|
||||
const FString Json = UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(RepositoryState);
|
||||
return !Json.IsEmpty() && FFileHelper::SaveStringToFile(Json, *OutResolvedPath);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingPersistenceLibrary::LoadTrainingRepositoryStateFromFile(
|
||||
const FString& RepositoryPath,
|
||||
FHyperTwistTrainingRepositoryState& OutRepositoryState,
|
||||
FString& OutResolvedPath
|
||||
)
|
||||
{
|
||||
OutRepositoryState = FHyperTwistTrainingRepositoryState();
|
||||
OutResolvedPath = ResolveTrainingRepositoryPath(RepositoryPath);
|
||||
|
||||
FString Json;
|
||||
if (!FPaths::FileExists(OutResolvedPath) || !FFileHelper::LoadFileToString(Json, *OutResolvedPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(Json, OutRepositoryState);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -58,6 +58,259 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTr
|
|||
return FHyperTwistTrainingSessionSummary();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRuntimeLibrary::GetTrainingRepositoryState(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetTrainingRepositoryState();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRepositoryState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveDeckHistorySummary();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingDeckHistorySummary();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckScopedStats UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckScopedStats(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveDeckScopedStats();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingDeckScopedStats();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseScopedStats UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCurrentCaseScopedStats(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCurrentCaseScopedStats();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCaseScopedStats();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerDeckStateSummary(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveLearnerDeckStateSummary();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerProfile UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerProfile(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveLearnerProfile();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingLearnerProfile();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachMemorySnapshot();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCoachMemorySnapshot();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerPresets(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveLearnerPresets();
|
||||
}
|
||||
|
||||
return TArray<FHyperTwistTrainingLearnerPreset>();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingSessionTemplates(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveTrainingSessionTemplates();
|
||||
}
|
||||
|
||||
return TArray<FHyperTwistTrainingSessionTemplate>();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCaseRecommendations(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCaseRecommendations();
|
||||
}
|
||||
|
||||
return TArray<FHyperTwistTrainingCaseRecommendation>();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingReviewPlanState(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveReviewPlanState();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingReviewPlanState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingReviewFlowStatus(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveReviewFlowStatus();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingReviewFlowStatus();
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingRunRecord(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TrainingSessionId,
|
||||
FHyperTwistTrainingRunRecord& OutRunRecord
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryGetStoredRunRecord(TrainingSessionId, OutRunRecord);
|
||||
}
|
||||
|
||||
OutRunRecord = FHyperTwistTrainingRunRecord();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingLearnerState(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
FHyperTwistTrainingCaseLearnerState& OutLearnerState
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryGetStoredLearnerState(UserId, DeckId, CaseId, OutLearnerState);
|
||||
}
|
||||
|
||||
OutLearnerState = FHyperTwistTrainingCaseLearnerState();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingReviewPlan(
|
||||
UObject* WorldContextObject,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryGetStoredReviewPlan(PlanId, OutReviewPlan);
|
||||
}
|
||||
|
||||
OutReviewPlan = FHyperTwistTrainingReviewPlanState();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::SaveTrainingRepositoryStateToDefaultPath(
|
||||
UObject* WorldContextObject,
|
||||
FString& OutResolvedPath
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->SaveTrainingRepositoryStateToDefaultPath(OutResolvedPath);
|
||||
}
|
||||
|
||||
OutResolvedPath.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::LoadTrainingRepositoryStateFromDefaultPath(
|
||||
UObject* WorldContextObject,
|
||||
FString& OutResolvedPath
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->LoadTrainingRepositoryStateFromDefaultPath(OutResolvedPath);
|
||||
}
|
||||
|
||||
OutResolvedPath.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryMaterializeActiveTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TemplateId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryMaterializeActiveTrainingSessionTemplate(TemplateId, OutDeck);
|
||||
}
|
||||
|
||||
OutDeck = FHyperTwistTrainingDeck();
|
||||
return false;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveReviewDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->BuildActiveReviewDeck(MaxCases);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartTrainingRunFromTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TemplateId,
|
||||
const FString& UserId,
|
||||
const FString& SessionId
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->StartTrainingRunFromTemplate(TemplateId, UserId, SessionId);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartRecommendedReviewRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->StartRecommendedReviewRun(SessionId, MaxCases, Mode);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartSampleClassicTrainingRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::HasActiveRun() const
|
||||
{
|
||||
|
|
@ -18,6 +20,268 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingSubsystem::GetActiveRunSumm
|
|||
return ActiveRunSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepositoryState() const
|
||||
{
|
||||
return TrainingRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingSubsystem::GetActiveDeckHistorySummary() const
|
||||
{
|
||||
return ActiveDeckHistorySummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckScopedStats UHyperTwistTrainingSubsystem::GetActiveDeckScopedStats() const
|
||||
{
|
||||
return ActiveDeckScopedStats;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseScopedStats UHyperTwistTrainingSubsystem::GetActiveCurrentCaseScopedStats() const
|
||||
{
|
||||
return ActiveCurrentCaseScopedStats;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistTrainingSubsystem::GetActiveLearnerDeckStateSummary() const
|
||||
{
|
||||
return ActiveLearnerDeckStateSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerProfile UHyperTwistTrainingSubsystem::GetActiveLearnerProfile() const
|
||||
{
|
||||
return ActiveLearnerProfile;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingSubsystem::GetActiveCoachMemorySnapshot() const
|
||||
{
|
||||
return ActiveCoachMemorySnapshot;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistTrainingSubsystem::GetActiveLearnerPresets() const
|
||||
{
|
||||
return ActiveLearnerPresets;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingSubsystem::GetActiveTrainingSessionTemplates() const
|
||||
{
|
||||
return ActiveTrainingSessionTemplates;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingSubsystem::GetActiveCaseRecommendations() const
|
||||
{
|
||||
return ActiveCaseRecommendations;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewPlanState UHyperTwistTrainingSubsystem::GetActiveReviewPlanState() const
|
||||
{
|
||||
return ActiveReviewPlanState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingSubsystem::GetActiveReviewFlowStatus() const
|
||||
{
|
||||
return ActiveReviewFlowStatus;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredRunRecord(
|
||||
const FString& TrainingSessionId,
|
||||
FHyperTwistTrainingRunRecord& OutRunRecord
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetRunRecord(
|
||||
TrainingRepositoryState,
|
||||
TrainingSessionId,
|
||||
OutRunRecord
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredLearnerState(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
FHyperTwistTrainingCaseLearnerState& OutLearnerState
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetLearnerState(
|
||||
TrainingRepositoryState,
|
||||
UserId,
|
||||
DeckId,
|
||||
CaseId,
|
||||
OutLearnerState
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredReviewPlan(
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetReviewPlan(
|
||||
TrainingRepositoryState,
|
||||
PlanId,
|
||||
OutReviewPlan
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveTrainingRepositoryStateToDefaultPath(FString& OutResolvedPath) const
|
||||
{
|
||||
return UHyperTwistTrainingPersistenceLibrary::SaveTrainingRepositoryStateToFile(
|
||||
TrainingRepositoryState,
|
||||
FString(),
|
||||
OutResolvedPath
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::LoadTrainingRepositoryStateFromDefaultPath(FString& OutResolvedPath)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState LoadedState;
|
||||
if (!UHyperTwistTrainingPersistenceLibrary::LoadTrainingRepositoryStateFromFile(FString(), LoadedState, OutResolvedPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = LoadedState;
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryMaterializeActiveTrainingSessionTemplate(
|
||||
const FString& TemplateId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
) const
|
||||
{
|
||||
OutDeck = FHyperTwistTrainingDeck();
|
||||
if (TemplateId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* MatchingTemplate = ActiveTrainingSessionTemplates.FindByPredicate(
|
||||
[&TemplateId](const FHyperTwistTrainingSessionTemplate& Candidate)
|
||||
{
|
||||
return Candidate.TemplateId == TemplateId;
|
||||
}
|
||||
);
|
||||
if (MatchingTemplate == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutDeck = UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
|
||||
*MatchingTemplate,
|
||||
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog()
|
||||
);
|
||||
return OutDeck.IsStructurallyValid();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int32 MaxCases) const
|
||||
{
|
||||
if (!ActiveRunState.ActiveDeck.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
|
||||
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(ActiveRunState.Session.Mode);
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation> FilteredRecommendations =
|
||||
UHyperTwistTrainingRepositoryLibrary::ApplyReviewPolicyToRecommendations(
|
||||
ActiveCaseRecommendations,
|
||||
ReviewPolicy,
|
||||
MaxCases
|
||||
);
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
|
||||
ActiveRunState.ActiveDeck,
|
||||
FilteredRecommendations,
|
||||
MaxCases
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTemplate(
|
||||
const FString& TemplateId,
|
||||
const FString& UserId,
|
||||
const FString& SessionId
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingDeck MaterializedDeck;
|
||||
if (!TryMaterializeActiveTrainingSessionTemplate(TemplateId, MaterializedDeck))
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* MatchingTemplate = ActiveTrainingSessionTemplates.FindByPredicate(
|
||||
[&TemplateId](const FHyperTwistTrainingSessionTemplate& Candidate)
|
||||
{
|
||||
return Candidate.TemplateId == TemplateId;
|
||||
}
|
||||
);
|
||||
if (MatchingTemplate == nullptr)
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
return StartTrainingRunFromDeck(
|
||||
MaterializedDeck,
|
||||
UserId,
|
||||
SessionId,
|
||||
MatchingTemplate->SuggestedMode
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReviewRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingDeck SourceDeck = ActiveRunState.ActiveDeck;
|
||||
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
|
||||
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(Mode);
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations =
|
||||
UHyperTwistTrainingRepositoryLibrary::ApplyReviewPolicyToRecommendations(
|
||||
ActiveCaseRecommendations,
|
||||
ReviewPolicy,
|
||||
MaxCases
|
||||
);
|
||||
const FHyperTwistTrainingDeck ReviewDeck = UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
|
||||
SourceDeck,
|
||||
Recommendations,
|
||||
MaxCases
|
||||
);
|
||||
if (!ReviewDeck.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId;
|
||||
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(ReviewDeck, UserId, SessionId, Mode);
|
||||
if (!RunState.IsStructurallyValid())
|
||||
{
|
||||
return RunState;
|
||||
}
|
||||
|
||||
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::BuildReviewPlan(
|
||||
SourceDeck,
|
||||
ReviewDeck,
|
||||
UserId,
|
||||
Recommendations,
|
||||
ReviewPolicy,
|
||||
FString::Printf(TEXT("review_plan_%s"), *RunState.Session.TrainingSessionId),
|
||||
RunState.Session.StartedAtUtc,
|
||||
Mode
|
||||
);
|
||||
if (ActiveReviewPlanState.IsStructurallyValid())
|
||||
{
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveReviewPlanState
|
||||
);
|
||||
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
}
|
||||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
|
|
@ -25,9 +289,16 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDe
|
|||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
ActiveRunState = UHyperTwistTrainingLibrary::StartTrainingRun(Deck, UserId, SessionId, Mode);
|
||||
bHasActiveRun = ActiveRunState.IsStructurallyValid();
|
||||
RefreshSummary();
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return ActiveRunState;
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +345,24 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingSubsystem::SubmitTrainingAtt
|
|||
ActiveRunState = StepResult.UpdatedRunState;
|
||||
bHasActiveRun = ActiveRunState.IsStructurallyValid();
|
||||
ActiveRunSummary = StepResult.SessionSummary;
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState
|
||||
);
|
||||
if (ActiveReviewPlanState.IsStructurallyValid())
|
||||
{
|
||||
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::UpdateReviewPlanAfterAttempt(
|
||||
ActiveReviewPlanState,
|
||||
StepResult,
|
||||
Attempt
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveReviewPlanState
|
||||
);
|
||||
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +383,23 @@ FHyperTwistTrainingSession UHyperTwistTrainingSubsystem::CompleteActiveRun(bool
|
|||
}
|
||||
|
||||
RefreshSummary();
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState
|
||||
);
|
||||
if (ActiveReviewPlanState.IsStructurallyValid())
|
||||
{
|
||||
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::FinalizeReviewPlan(
|
||||
ActiveReviewPlanState,
|
||||
ActiveRunState.Session
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveReviewPlanState
|
||||
);
|
||||
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return ActiveRunState.Session;
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +407,17 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun()
|
|||
{
|
||||
ActiveRunState = FHyperTwistTrainingRunState();
|
||||
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
|
||||
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
|
||||
ActiveDeckScopedStats = FHyperTwistTrainingDeckScopedStats();
|
||||
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
|
||||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
ActiveCaseRecommendations.Reset();
|
||||
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
bHasActiveRun = false;
|
||||
}
|
||||
|
||||
|
|
@ -117,3 +434,81 @@ void UHyperTwistTrainingSubsystem::RefreshSummary()
|
|||
ActiveRunState.Attempts
|
||||
);
|
||||
}
|
||||
|
||||
void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
||||
{
|
||||
if (!ActiveRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
|
||||
ActiveDeckScopedStats = FHyperTwistTrainingDeckScopedStats();
|
||||
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
|
||||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
ActiveCaseRecommendations.Reset();
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
ActiveDeckHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveRunState.Session.DeckId
|
||||
);
|
||||
ActiveDeckScopedStats = UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveRunState.Session.DeckId,
|
||||
FString()
|
||||
);
|
||||
const FString CurrentCaseId = ActiveRunState.CurrentSelection.IsStructurallyValid()
|
||||
? ActiveRunState.CurrentSelection.TrainingCase.CaseId
|
||||
: ActiveRunState.Session.CurrentCaseId;
|
||||
ActiveCurrentCaseScopedStats = !CurrentCaseId.IsEmpty()
|
||||
? UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveRunState.Session.DeckId,
|
||||
CurrentCaseId,
|
||||
ActiveDeckScopedStats.ReferenceUtc
|
||||
)
|
||||
: FHyperTwistTrainingCaseScopedStats();
|
||||
ActiveLearnerDeckStateSummary = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveRunState.Session.DeckId,
|
||||
FString()
|
||||
);
|
||||
ActiveLearnerProfile = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveCoachMemorySnapshot = UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveLearnerPresets = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerPresets(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveTrainingSessionTemplates = UHyperTwistTrainingRepositoryLibrary::DeriveTrainingSessionTemplates(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveCaseRecommendations = UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.ActiveDeck,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc,
|
||||
5
|
||||
);
|
||||
ActiveReviewFlowStatus = ActiveReviewPlanState.IsStructurallyValid()
|
||||
? UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState)
|
||||
: FHyperTwistTrainingReviewFlowStatus();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,57 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingSessionSummary MakeSampleTrainingRunSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingTimingPolicy MakeSampleTrainingTimingPolicy();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingTimingBreakdown MakeSampleTrainingTimingBreakdown();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRepositoryState MakeSampleTrainingRepositoryState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingDeckHistorySummary MakeSampleTrainingDeckHistorySummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingCaseScopedStats MakeSampleTrainingCaseScopedStats();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingDeckScopedStats MakeSampleTrainingDeckScopedStats();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingCaseLearnerState MakeSampleTrainingCaseLearnerState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingLearnerDeckStateSummary MakeSampleTrainingLearnerDeckStateSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingLearnerProfile MakeSampleTrainingLearnerProfile();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingCoachMemorySnapshot MakeSampleTrainingCoachMemorySnapshot();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static TArray<FHyperTwistTrainingLearnerPreset> MakeSampleTrainingLearnerPresets();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static TArray<FHyperTwistTrainingSessionTemplate> MakeSampleTrainingSessionTemplates();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingDeck MakeSampleTrainingSessionTemplateDeck();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static TArray<FHyperTwistTrainingCaseRecommendation> MakeSampleTrainingCaseRecommendations();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingDeck MakeSampleTrainingReviewDeck();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingReviewPlanState MakeSampleTrainingReviewPlanState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingReviewFlowStatus MakeSampleTrainingReviewFlowStatus();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
|
||||
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
|
||||
|
||||
|
|
@ -80,6 +131,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
|
||||
static FString SerializeContentPackToJson(const FHyperTwistContentPack& ContentPack);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
|
||||
static FString SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
|
||||
static bool DeserializePuzzleStateFromJson(const FString& Json, FHyperTwistPuzzleState& OutState);
|
||||
|
||||
|
|
@ -88,4 +142,7 @@ public:
|
|||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
|
||||
static bool DeserializeContentPackFromJson(const FString& Json, FHyperTwistContentPack& OutContentPack);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
|
||||
static bool DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog")
|
||||
static TArray<FHyperTwistContentPack> MakePhase3TrainingCatalog();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Catalog")
|
||||
static bool TryFindDeckInCatalog(
|
||||
const TArray<FHyperTwistContentPack>& TrainingCatalog,
|
||||
const FString& DeckId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Catalog")
|
||||
static bool TryGetTrackFromContentPack(
|
||||
const FHyperTwistContentPack& ContentPack,
|
||||
|
|
@ -37,4 +44,10 @@ public:
|
|||
const FHyperTwistContentPack& ContentPack,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog")
|
||||
static FHyperTwistTrainingDeck MaterializeTrainingSessionTemplateDeck(
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
|
||||
const TArray<FHyperTwistContentPack>& TrainingCatalog
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,17 @@ public:
|
|||
const FHyperTwistTrainingAttempt& Attempt
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingTimingPolicy NormalizeTimingPolicy(
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingAttempt NormalizeTrainingAttemptTiming(
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
const FHyperTwistTrainingTimingPolicy& TimingPolicy
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingSession CompleteTrainingSession(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingPersistenceLibrary.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingPersistenceLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Persistence")
|
||||
static FString GetDefaultTrainingRepositoryPath();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Persistence")
|
||||
static FString ResolveTrainingRepositoryPath(const FString& RepositoryPath);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Persistence")
|
||||
static bool SaveTrainingRepositoryStateToFile(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& RepositoryPath,
|
||||
FString& OutResolvedPath
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Persistence")
|
||||
static bool LoadTrainingRepositoryStateFromFile(
|
||||
const FString& RepositoryPath,
|
||||
FHyperTwistTrainingRepositoryState& OutRepositoryState,
|
||||
FString& OutResolvedPath
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingRepositoryLibrary.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingRepositoryLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingAttemptHistoryEntry MakeAttemptHistoryEntry(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
const FHyperTwistTrainingAttempt& Attempt
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static EHyperTwistTrainingReviewGrade DeriveReviewGrade(
|
||||
const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry,
|
||||
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ExistingReviewHistory
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewHistoryEntry MakeReviewHistoryEntry(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ExistingReviewHistory
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingSolveHistoryEntry MakeSolveHistoryEntry(
|
||||
const FHyperTwistTrainingAttempt& Attempt
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRunRecord BuildRunRecord(
|
||||
const FHyperTwistTrainingRunState& RunState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRepositoryState UpsertRunRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingRunRecord& RunRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRepositoryState RecordRunState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingRunState& RunState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
|
||||
static bool TryGetRunRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& TrainingSessionId,
|
||||
FHyperTwistTrainingRunRecord& OutRunRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingRunRecord> ListRunRecordsForDeck(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCaseLearnerState DeriveLearnerStateForCase(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
|
||||
static bool TryGetLearnerState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
FHyperTwistTrainingCaseLearnerState& OutLearnerState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingCaseLearnerState> ListLearnerStatesForDeck(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingDeckHistorySummary DeriveDeckHistorySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCaseScopedStats DeriveCaseScopedStats(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingCaseScopedStats> ListCaseScopedStatsForDeck(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingDeckScopedStats DeriveDeckScopedStats(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingLearnerDeckStateSummary DeriveLearnerDeckStateSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingLearnerProfile DeriveLearnerProfile(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCoachMemorySnapshot DeriveCoachMemorySnapshot(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingLearnerPreset> DeriveLearnerPresets(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingSessionTemplate> DeriveTrainingSessionTemplates(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCaseRecommendation MakeCaseRecommendation(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistTrainingCaseLearnerState& LearnerState,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewPolicy GetDefaultReviewPolicy(
|
||||
EHyperTwistTrainingDeliveryMode DeliveryMode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingCaseRecommendation> ListCaseRecommendationsForDeck(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc,
|
||||
int32 MaxRecommendations
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static TArray<FHyperTwistTrainingCaseRecommendation> ApplyReviewPolicyToRecommendations(
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
|
||||
const FHyperTwistTrainingReviewPolicy& ReviewPolicy,
|
||||
int32 MaxRecommendations
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingDeck BuildReviewDeckFromRecommendations(
|
||||
const FHyperTwistTrainingDeck& SourceDeck,
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewPlanState BuildReviewPlan(
|
||||
const FHyperTwistTrainingDeck& SourceDeck,
|
||||
const FHyperTwistTrainingDeck& ReviewDeck,
|
||||
const FString& UserId,
|
||||
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
|
||||
const FHyperTwistTrainingReviewPolicy& ReviewPolicy,
|
||||
const FString& PlanId,
|
||||
const FString& ReferenceUtc,
|
||||
EHyperTwistTrainingDeliveryMode DeliveryMode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRepositoryState UpsertReviewPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
|
||||
static bool TryGetReviewPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewPlanState UpdateReviewPlanAfterAttempt(
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
|
||||
const FHyperTwistTrainingRunStepResult& StepResult,
|
||||
const FHyperTwistTrainingAttempt& Attempt
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewPlanState FinalizeReviewPlan(
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
|
||||
const FHyperTwistTrainingSession& Session
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingReviewFlowStatus DeriveReviewFlowStatus(
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan
|
||||
);
|
||||
};
|
||||
|
|
@ -25,6 +25,106 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingSessionSummary GetActiveTrainingRunSummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRepositoryState GetTrainingRepositoryState(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeckHistorySummary GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeckScopedStats GetActiveTrainingDeckScopedStats(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCaseScopedStats GetActiveTrainingCurrentCaseScopedStats(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingLearnerDeckStateSummary GetActiveTrainingLearnerDeckStateSummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingLearnerProfile GetActiveTrainingLearnerProfile(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachMemorySnapshot GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingLearnerPreset> GetActiveTrainingLearnerPresets(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingSessionTemplate> GetActiveTrainingSessionTemplates(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingCaseRecommendation> GetActiveTrainingCaseRecommendations(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingReviewPlanState GetActiveTrainingReviewPlanState(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingReviewFlowStatus GetActiveTrainingReviewFlowStatus(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingRunRecord(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TrainingSessionId,
|
||||
FHyperTwistTrainingRunRecord& OutRunRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingLearnerState(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
FHyperTwistTrainingCaseLearnerState& OutLearnerState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingReviewPlan(
|
||||
UObject* WorldContextObject,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool SaveTrainingRepositoryStateToDefaultPath(
|
||||
UObject* WorldContextObject,
|
||||
FString& OutResolvedPath
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool LoadTrainingRepositoryStateFromDefaultPath(
|
||||
UObject* WorldContextObject,
|
||||
FString& OutResolvedPath
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryMaterializeActiveTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TemplateId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeck BuildActiveReviewDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TemplateId,
|
||||
const FString& UserId,
|
||||
const FString& SessionId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartRecommendedReviewRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
|
||||
UObject* WorldContextObject,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,91 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingSessionSummary GetActiveRunSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingDeckHistorySummary GetActiveDeckHistorySummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingDeckScopedStats GetActiveDeckScopedStats() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingCaseScopedStats GetActiveCurrentCaseScopedStats() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingLearnerDeckStateSummary GetActiveLearnerDeckStateSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingLearnerProfile GetActiveLearnerProfile() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingCoachMemorySnapshot GetActiveCoachMemorySnapshot() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingLearnerPreset> GetActiveLearnerPresets() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingSessionTemplate> GetActiveTrainingSessionTemplates() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingCaseRecommendation> GetActiveCaseRecommendations() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingReviewPlanState GetActiveReviewPlanState() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingReviewFlowStatus GetActiveReviewFlowStatus() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetStoredRunRecord(
|
||||
const FString& TrainingSessionId,
|
||||
FHyperTwistTrainingRunRecord& OutRunRecord
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetStoredLearnerState(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
FHyperTwistTrainingCaseLearnerState& OutLearnerState
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetStoredReviewPlan(
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool SaveTrainingRepositoryStateToDefaultPath(FString& OutResolvedPath) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool LoadTrainingRepositoryStateFromDefaultPath(FString& OutResolvedPath);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryMaterializeActiveTrainingSessionTemplate(
|
||||
const FString& TemplateId,
|
||||
FHyperTwistTrainingDeck& OutDeck
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingDeck BuildActiveReviewDeck(int32 MaxCases) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
|
||||
const FString& TemplateId,
|
||||
const FString& UserId,
|
||||
const FString& SessionId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartRecommendedReviewRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
|
|
@ -49,6 +134,7 @@ public:
|
|||
|
||||
private:
|
||||
void RefreshSummary();
|
||||
void RefreshRepositoryViews();
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingRunState ActiveRunState;
|
||||
|
|
@ -56,6 +142,42 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistTrainingSessionSummary ActiveRunSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingRepositoryState TrainingRepositoryState;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingDeckHistorySummary ActiveDeckHistorySummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingDeckScopedStats ActiveDeckScopedStats;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCaseScopedStats ActiveCurrentCaseScopedStats;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingLearnerDeckStateSummary ActiveLearnerDeckStateSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingLearnerProfile ActiveLearnerProfile;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachMemorySnapshot ActiveCoachMemorySnapshot;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingLearnerPreset> ActiveLearnerPresets;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingSessionTemplate> ActiveTrainingSessionTemplates;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingCaseRecommendation> ActiveCaseRecommendations;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingReviewPlanState ActiveReviewPlanState;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingReviewFlowStatus ActiveReviewFlowStatus;
|
||||
|
||||
UPROPERTY()
|
||||
bool bHasActiveRun = false;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue