Select Git revision
string_tools.test.cpp
SFGameInstance.cpp 29.35 KiB
// Fill out your copyright notice in the Description page of Project Settings.
#include "SFGameInstance.h"
#include "Engine/World.h"
#include "IUniversalLogging.h"
#include "HUD/SFFadeHandler.h"
#include "HUD/SFMasterHUD.h"
#include "Help/SFUtils.h"
#include "Logging/SFLoggingBPLibrary.h"
#include "Logging/SFLoggingUtils.h"
USFGameInstance* USFGameInstance::Instance = nullptr;
// ****************************************************************** //
// ******* Initialization ******************************************* //
// ****************************************************************** //
void USFGameInstance::Init()
{
Super::Init();
FSFLoggingUtils::Log("USFGameInstance::Init");
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
if (ClusterManager && !ClusterEventListenerDelegate.IsBound())
{
ClusterEventListenerDelegate = FOnClusterEventJsonListener::CreateUObject(
this, &USFGameInstance::HandleClusterEvent);
ClusterManager->AddClusterEventJsonListener(ClusterEventListenerDelegate);
}
Instance = this;
LogObject = NewObject<USFLogObject>();
if (ConditionIndexToStartAtInit != -1)
{
FSFLoggingUtils::Log(
"Started on a map that was part of the last study, so start the study run for debug reasons from Init()");
RestoreLastParticipantForDebugStart(ConditionIndexToStartAtInit);
}
if (StudySetup)
{
InitFadeHandler(StudySetup->FadeConfig);
}
// Register delegate for ticker callback
TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker(
FTickerDelegate::CreateUObject(this, &USFGameInstance::LogTick));
LogObject->Initialize();
bStudyStarted = false;
bStudyEnded = false;
FWorldDelegates::OnPostWorldCreation.AddUObject(this, &USFGameInstance::OnPostWorldCreation);
}
void USFGameInstance::Shutdown()
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
if (ClusterManager && ClusterEventListenerDelegate.IsBound())
{
ClusterManager->RemoveClusterEventJsonListener(ClusterEventListenerDelegate);
}
if (ExperimenterWindow)
{
ExperimenterWindow->DestroyWindow();
}
Instance = nullptr;
// Unregister ticker delegate
FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
FWorldDelegates::OnPostWorldCreation.RemoveAll(this);
Super::Shutdown();
}
void USFGameInstance::OnWorldStart()
{
UWorld* NewWorld = GetWorld();
//when unloading map, NewWorld==nullptr, when loading new map OldWorld==nullptr
if (NewWorld == nullptr)
{
return;
}
if (bStudyStarted)
{
//everything is already up and running, so nothing to do
return;
}
FSFLoggingUtils::Log("USFGameInstance::OnWorldStart for " + NewWorld->GetName());
// so we have loaded a new world and the study is not running, so check whether this is a map in one of the conditions
TArray<USFCondition*> LastConditions = USFParticipant::GetLastParticipantsConditions();
int FirstMapConditionIndex = -1;
for (int i = 0; i < LastConditions.Num(); i++)
{
if (FPaths::GetBaseFilename(LastConditions[i]->Map).Equals(NewWorld->GetName()))
{
FirstMapConditionIndex = i;
break;
}
}
if (FirstMapConditionIndex != -1)
{
if (!IsInitialized())
{
//this is potentially called before init was called
ConditionIndexToStartAtInit = FirstMapConditionIndex;
}
else
{
FSFLoggingUtils::Log(
"[USFGameInstance::OnWorldChanged] Started on a map that was part of the last study, so start the study run for debug reasons.");
RestoreLastParticipantForDebugStart(FirstMapConditionIndex);
LogToHUD("Start map " + NewWorld->GetName() + " for debugging!");
}
return;
}
// otherwise check whether a setup is present
TArray<AActor*> StudySetups;
UGameplayStatics::GetAllActorsOfClass(NewWorld, ASFStudySetup::StaticClass(), StudySetups);
FSFLoggingUtils::Log("Found " + FString::FromInt(StudySetups.Num()) + " ASFStudySetup actors on this map.");
if (StudySetups.Num() == 1)
{
PrepareWithStudySetup(Cast<ASFStudySetup>(StudySetups[0]));
return;
}
// else we started on an unrelated map and just disable the HUD, so people can test their stuff
bStartedOnUnrelatedMap = true;
StudySetup = NewObject<ASFStudySetup>(this, TEXT("DefaultStudySetupForUnrelatedMap"));
InitFadeHandler(FFadeConfig());
FadeHandler->FadeIn();
FSFLoggingUtils::Log(
"[USFGameInstance::OnWorldChanged] world started that neither contains exactly one SFStudySetup actor, nor is a level that is part of one of the conditions from the last study run!",
false);
}
void USFGameInstance::OnPostWorldCreation(UWorld* World)
{
//this is actually called before BeginPlay but after EndPlay of the world before, so actually switch the current condition just here
if(Participant)
Participant->ApplyNextCondition();
}
void USFGameInstance::RestoreLastParticipantForDebugStart(int InStartConditionIndex)
{
bInDebugMode = true;
const int ParticipantSequenceNumber = USFParticipant::GetLastParticipantSequenceNumber();
const FString ParticipantID = USFParticipant::GetLastParticipantID();
Participant = NewObject<USFParticipant>(this,
FName(TEXT("Participant_") + ParticipantID));
StudySetup = USFParticipant::GetLastParticipantSetup();
StartConditionIndex = InStartConditionIndex;
Participant->Initialize(ParticipantSequenceNumber, ParticipantID);
Participant->Conditions = Participant->GetLastParticipantsConditions();
Participant->LoadLastParticipantsIndependentVariables();
Participant->RecoverStudyResultsOfFinishedConditions();
USFCondition* StartCondition = Participant->Conditions[StartConditionIndex];
//also delete any data of the condition that is now restarted (StartCondition)
Participant->DeleteStoredDataForConditionFromLongTable(StartCondition);
for (USFDependentVariable* DV : StartCondition->DependentVariables)
{
if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
{
Participant->DeleteStoredTrialDataForCondition(StartCondition, MTDV);
}
}
InitFadeHandler(StudySetup->FadeConfig);
StartStudy();
//cannot directly fade in here, since Init might not be done, so we wait a second for safety
GetTimerManager().SetTimer(StartFadingTimerHandle, this, &USFGameInstance::StartFadingIn, 1.0f);
}
void USFGameInstance::StartFadingIn()
{
FadeHandler->FadeIn();
GetTimerManager().ClearTimer(StartFadingTimerHandle);
}
USFGameInstance* USFGameInstance::Get()
{
if (Instance == nullptr)
{
FSFUtils::OpenMessageBox("GameInstance is not set to USFGameInstance, Study Framework will not work", true);
}
return Instance;
}
bool USFGameInstance::IsInitialized()
{
return Instance != nullptr;
}
void USFGameInstance::InitFadeHandler(FFadeConfig FadeConfig)
{
if (FadeHandler == nullptr)
{
FadeHandler = NewObject<USFFadeHandler>(GetTransientPackage(), "SFFadeHandler");
FadeHandler->AddToRoot();
}
FadeHandler->SetFadeConfig(FadeConfig);
if (bStartedOnUnrelatedMap)
{
StudySetup->ExperimenterViewConfig.bShowHUD = false;
}
if (StudySetup->ExperimenterViewConfig.bShowExperimenterViewInSecondWindow)
{
ExperimenterWindow = NewObject<USFExperimenterWindow>(GetTransientPackage(), "ExperimenterWindow");
ExperimenterWindow->CreateWindow(StudySetup->ExperimenterViewConfig);
}
}
// When starting on the setup map, i.e. not for debugging purposes
void USFGameInstance::PrepareWithStudySetup(ASFStudySetup* Setup)
{
StudySetup = DuplicateObject(Setup, this);
int ParticipantSequenceNumber = USFParticipant::GetLastParticipantSequenceNumber();
FString LastParticipantID = USFParticipant::GetLastParticipantID();
bool bRecoverParticipantData = false;
if (FSFUtils::IsPrimary())
{
if (USFParticipant::GetLastParticipantFinished())
{
ParticipantSequenceNumber++;
}
else
{
const FString MessageText = FString("The last participant did not finish the study run. Would you like to:")
+
"\n[Continue Participant] Continue last participant (Participant ID: " +
LastParticipantID + ", SequenceNumber: " + FString::FromInt(ParticipantSequenceNumber) +
") where he/she left off in Condition " +
FString::FromInt(USFParticipant::GetLastParticipantLastConditionStarted() + 1) + "/" + FString::FromInt(
USFParticipant::GetLastParticipantsConditions().Num()) +
"\n[Next Participant] Continue with the next participant (ParticipantSequenceNumber: " +
FString::FromInt(ParticipantSequenceNumber + 1) +
")\n[Restart Study] Restart the entire study anew (ParticipantSequenceNumber: 0)";
const FString MessageTitle = "WARNING: Unfinished study run detected";
TArray<FString> Buttons = {
"Continue Participant",
"Next Participant",
"Restart Study"
};
bool bValidDecision = false;
while (!bValidDecision)
{
int Answer = FSFUtils::OpenCustomDialog(MessageTitle, MessageText, Buttons);
const FString ModeSelection = Buttons[Answer];
if (Answer < 0)
{
FSFLoggingUtils::Log(
"[USFGameInstance::PrepareWithStudySetup] The window for deciding on how to recover from an unfinished run was closed without giving an answer, repeat question!",
false);
continue;
}
switch (Answer)
{
case 2:
FSFLoggingUtils::Log("[USFGameInstance::PrepareWithStudySetup]: Restart entire study");
ParticipantSequenceNumber = 0;
//clear data (but only on primary to avoid data races)
USFParticipant::ClearLogData();
bValidDecision = true;
break;
case 0:
FSFLoggingUtils::Log("[USFGameInstance::PrepareWithStudySetup]: Continue last participant");
bRecoverParticipantData = true;
bValidDecision = true;
break;
case 1:
FSFLoggingUtils::Log(
"[USFGameInstance::PrepareWithStudySetup]: Continue with the next participant");
ParticipantSequenceNumber++;
bValidDecision = true;
break;
default:
FSFLoggingUtils::Log(
"[USFGameInstance::PrepareWithStudySetup]: Unknown UnfinishedRunDetectedDialog answer: " +
FString::FromInt(Answer), true);
break;
}
}
}
// Participant
FString ParticipantID = "";
if (StudySetup->bUseCustomParticipantIDs)
{
//if we are recovering, restore it:
if (bRecoverParticipantData)
{
ParticipantID = USFParticipant::GetLastParticipantID();
}
else
{
//ask for it:
bool bValidIDEntered = false;
while (!bValidIDEntered)
{
FString IDGiven;
int Result = FSFUtils::OpenCustomDialogText("Participant ID", "Please type in your ID:", "",
IDGiven);
if (Result < 0)
{
FSFLoggingUtils::Log(
"[USFGameInstance::PrepareWithStudySetup] The window for entering the participant ID was closed without giving an answer, repeat question!",
false);
continue;
}
//check whether it was used before
if (USFParticipant::WasParticipantIdAlreadyUsed(IDGiven))
{
FSFUtils::OpenMessageBox(
"Participant ID \"" + IDGiven + "\"was already used, you have to choose another one!");
}
else if (IDGiven.IsEmpty())
{
FSFUtils::OpenMessageBox("Participant ID given is empty, you have to choose one!");
}
else
{
ParticipantID = IDGiven;
bValidIDEntered = true;
}
}
}
}
else
{
//otherwise just use sequence number:
ParticipantID = FString::FromInt(ParticipantSequenceNumber);
}
// Primary Node in cluster mode: Continue preparation by event so it is done on all nodes
if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
{
IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr();
FDisplayClusterClusterEventJson Event;
TMap<FString, FString> Params;
Params.Add("ParticipantSequenceNumber", FString::FromInt(ParticipantSequenceNumber));
Params.Add("ParticipantID", ParticipantID);
Params.Add("bRecoverParticipantData", bRecoverParticipantData ? "true" : "false");
Event.Type = "SFGameInstanceEvent";
Event.Name = "PrepareWithStudySetupAllNodes";
Event.Parameters = Params;
Manager->EmitClusterEventJson(Event, true);
}
// Not in cluster mode: Directly call prep
else
{
PrepareWithStudySetupAllNodes(ParticipantSequenceNumber, ParticipantID, bRecoverParticipantData);
}
}
//this needs to be done directly to not have one frame at the beginning which is not faded out!
if (IsInitialized())
{
InitFadeHandler(StudySetup->FadeConfig);
}
UpdateHUD("Wait for Start");
}
//Continue / Next Participant / Restart Study
void USFGameInstance::PrepareWithStudySetupAllNodes(int NewParticipantSequenceNumber, FString NewParticipantID,
bool bRestoreLast)
{
FSFLoggingUtils::Log(FString("[USFGameInstance::PrepareWithStudySetupAllNodes]:") +
" Participant#: " + FString::FromInt(NewParticipantSequenceNumber) +
" ParticipantID: " + NewParticipantID +
" RecoverLast: " + (bRestoreLast ? "true" : "false"));
TArray<USFCondition*> Conditions;
if (bRestoreLast)
{
Conditions = USFParticipant::GetLastParticipantsConditions();
StartConditionIndex = USFParticipant::GetLastParticipantLastConditionStarted();
}
else
{
Conditions = StudySetup->GetAllConditionsForRun(NewParticipantSequenceNumber);
}
if (StudySetup->bPreloadAllMapsOnStart)
{
PreloadAllMaps(Conditions);
}
Participant = NewObject<USFParticipant>(this, FName(TEXT("Participant_") + NewParticipantID));
Participant->Initialize(NewParticipantSequenceNumber, NewParticipantID);
if (bRestoreLast)
{
Participant->LoadLastParticipantsIndependentVariables();
}
else
{
Participant->SetIndependentVariablesFromStudySetup(StudySetup);
Participant->StoreInIndependentVarLongTable();
}
Participant->SetStudyConditions(Conditions);
if (bRestoreLast)
{
Participant->RecoverStudyResultsOfFinishedConditions();
if (FSFUtils::IsPrimary())
{
//also delete any data of the condition that is now restarted (StartCondition), only on primary to avoid data races
Participant->DeleteStoredDataForConditionFromLongTable(Participant->Conditions[StartConditionIndex]);
for (USFDependentVariable* DV : Participant->Conditions[StartConditionIndex]->DependentVariables)
{
if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
{
Participant->DeleteStoredTrialDataForCondition(Participant->Conditions[StartConditionIndex], MTDV);
}
}
}
}
}
void USFGameInstance::PreloadAllMaps(const TArray<USFCondition*>& Conditions)
{
TArray<FString> Maps;
for (const USFCondition* Condition : Conditions)
{
if (!Maps.Contains(Condition->Map))
{
Maps.Add(Condition->Map);
}
}
for (FString Map : Maps)
{
LoadPackage(nullptr, *Map, LOAD_None);
}
FSFLoggingUtils::Log("Sucessfully preloaded all maps.");
}
// ****************************************************************** //
// ******* Control Study ******************************************** //
// ****************************************************************** //
bool USFGameInstance::StartStudy()
{
if (bStudyStarted)
{
FSFLoggingUtils::Log("[USFGameInstance::StartStudy()]: Study already started.", true);
return false;
}
if (!bInDebugMode)
{
//we are actually doing a real start and not just a "debug-start"
if (!Participant->StartStudy())
{
FSFLoggingUtils::Log("[USFGameInstance::StartStudy()]: unable to start study.", true);
return false;
}
}
if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD()))
{
ASFMasterHUD* HUD = Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
HUD->SetStartStudyButtonVisibility(ESlateVisibility::Collapsed);
HUD->SetNextConditionButtonVisibility(ESlateVisibility::Visible);
}
if (StartConditionIndex == -1)
{
NextCondition();
}
else
{
GoToCondition(StartConditionIndex);
}
//check whether we want to use the eye tracker
if (StudySetup && StudySetup->UseGazeTracker != EGazeTrackerMode::NotTracking)
{
GazeTracker = NewObject<USFGazeTracker>(this, TEXT("GazeTracker"));
GazeTracker->Init(StudySetup->UseGazeTracker, StudySetup->bIgnoreNonGazeTargetActors,
StudySetup->EyeTrackingFrameRate, StudySetup->GazeTrackingBackend);
}
return true;
}
void USFGameInstance::EndStudy()
{
USFCondition* LastCondition = Participant->GetCurrentCondition();
if (!LastCondition || !LastCondition->WasStarted() || LastCondition->IsFinished())
{
return;
}
LastCondition->End();
Participant->EndStudy();
UpdateHUD("Study ended");
if (GetHUD())
{
GetHUD()->SetNextConditionButtonVisibility(ESlateVisibility::Collapsed);
}
bStudyEnded = true;
FadeHandler->FadeOut();
}
bool USFGameInstance::NextCondition(bool bForced /*=false*/,
EFadeBetweenCondition Fade /*= EFadeBetweenCondition::AsDefault*/)
{
// Check if is already fading
if (FadeHandler->GetIsFading() && !FadeHandler->GetIsFadedOutWaitingForLevel())
{
FSFLoggingUtils::Log("[USFGameInstance::NextCondition()]: Already Fading between levels", true);
return false;
}
TOptional<int> NextConditionIdx = Participant->GetNextConditionIndex();
if (!NextConditionIdx.IsSet())
{
FSFLoggingUtils::Log("[SFGameInstance::NextCondition]: All conditions already ran, no NextCondition", false);
EndStudy();
return false;
}
return GoToCondition(NextConditionIdx.GetValue(), bForced, Fade);
}
bool USFGameInstance::GoToCondition(const int NewConditionIndex, bool bForced /*=false*/,
EFadeBetweenCondition Fade /*= EFadeBetweenCondition::AsDefault*/)
{
// Check if is already fading
if (FadeHandler->GetIsFading() && !FadeHandler->GetIsFadedOutWaitingForLevel())
{
FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Already Fading between levels", true);
return false;
}
if (NewConditionIndex < 0 || NewConditionIndex >= GetParticipant()->GetAllConditions().Num())
{
FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Could not load next condition.", true);
return false;
}
if (!bStudyStarted && !bIsStarting)
{
StartConditionIndex = NewConditionIndex; //Start at this condition
bIsStarting = true; //To avoid infinite loops, when StartStudy calls GoToCondition(StartCondition)
return StartStudy();
}
GoToConditionSynced(NewConditionIndex, bForced, Fade);
return true;
}
void USFGameInstance::GoToConditionSynced(int ConditionIndex, bool bForced, EFadeBetweenCondition Fade)
{
const EDisplayClusterOperationMode OperationMode = IDisplayCluster::Get().GetOperationMode();
if (OperationMode != EDisplayClusterOperationMode::Cluster)
{
HandleGoToConditionSynced(ConditionIndex, bForced, Fade);
}
else
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
check(ClusterManager != nullptr);
FDisplayClusterClusterEventJson ClusterEvent;
ClusterEvent.Type = "SFGameInstanceEvent";
ClusterEvent.Name = "GoToConditionSynced";
TMap<FString, FString> Params;
Params.Add("ConditionIndex", FString::FromInt(ConditionIndex));
Params.Add("bForced", bForced ? "true" : "false");
Params.Add("Fade", (Fade == EFadeBetweenCondition::AsDefault
? "AsDefault"
: (Fade == EFadeBetweenCondition::ForceNoFade ? "ForceNoFade" : "ForceFade")));
ClusterEvent.Parameters = Params;
ClusterEvent.bShouldDiscardOnRepeat = true;
ClusterManager->EmitClusterEventJson(ClusterEvent, true);
}
}
void USFGameInstance::HandleClusterEvent(const FDisplayClusterClusterEventJson& Event)
{
if (Event.Type == "SFGameInstanceEvent")
{
if (Event.Name == "PrepareWithStudySetupAllNodes")
{
int NewParticipantSequenceNumber = FCString::Atoi(*Event.Parameters["ParticipantSequenceNumber"]);
FString NewParticipantID = Event.Parameters["ParticipantID"];
bool bRecoverParticipantData = Event.Parameters["bRecoverParticipantData"] == "true";
PrepareWithStudySetupAllNodes(NewParticipantSequenceNumber, NewParticipantID, bRecoverParticipantData);
}
//now we actually react on all cluster nodes:
if (Event.Name == "GoToConditionSynced")
{
EFadeBetweenCondition Fade = EFadeBetweenCondition::AsDefault;
if (Event.Parameters["Fade"] != "AsDefault")
{
if (Event.Parameters["Fade"] == "ForceNoFade")
{
Fade = EFadeBetweenCondition::ForceNoFade;
}
else if (Event.Parameters["Fade"] == "ForceFade")
{
Fade = EFadeBetweenCondition::ForceFade;
}
else
{
FSFLoggingUtils::Log(
"[USFGameInstance::HandleClusterEvent] Unknown Fade value: " + Event.Parameters["Fade"], true);
}
}
HandleGoToConditionSynced(FCString::Atoi(*Event.Parameters["ConditionIndex"]),
Event.Parameters["bForced"] == "true", Fade);
}
}
}
void USFGameInstance::HandleGoToConditionSynced(int ConditionIndex, bool bForced, EFadeBetweenCondition Fade)
{
USFCondition* NextCondition = Participant->GetAllConditions()[ConditionIndex];
USFCondition* LastCondition = Participant->GetCurrentCondition();
if (NextCondition->WasStarted() && bForced)
{
LogComment(
"Restarting Condition " + NextCondition->GetPrettyName() +
", so we delete all gathered data for that condition.");
FString BackUpFolderName = "Restart_" + NextCondition->GetShortName() + "_BackUp-" + FDateTime::Now().
ToString();
Participant->SetCurrentBackUpFolderName(BackUpFolderName);
//so remove already stored data!
Participant->DeleteStoredDataForConditionFromLongTable(NextCondition);
for (USFDependentVariable* DV : NextCondition->DependentVariables)
{
if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
{
Participant->DeleteStoredTrialDataForCondition(NextCondition, MTDV);
}
}
}
else if (LastCondition && LastCondition->WasStarted())
{
TArray<FString> UnfinishedVars = LastCondition->End();
if (UnfinishedVars.Num() != 0)
{
FString Missing = "Missing data for: {";
for (FString UnfinishedVar : UnfinishedVars)
{
Missing += UnfinishedVar + " ";
}
Missing += "}";
if (bForced)
{
FSFLoggingUtils::Log(
"[USFGameInstance::GoToCondition()]: Forced to go to next condition, but current one is not finished! "
+ Missing,
true);
}
else
{
FSFLoggingUtils::Log(
"[USFGameInstance::GoToCondition()]: Cannot go to next condition, since current one is not finished! "
+ Missing,
true);
return;
}
}
}
const bool bConditionPresent = Participant->SetNextCondition(ConditionIndex);
if (!bConditionPresent)
{
FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Requested GoTo Condition seems not to exist.", true);
return;
}
// Fade to next Level
if (IsInitialized())
{
if (FadeHandler->GetIsFadedOutWaitingForLevel())
{
FadeHandler->SetLevelToLoad(NextCondition->Map);
}
else
{
FadeHandler->FadeToLevel(NextCondition->Map, Fade);
}
UpdateHUD("Fading out");
}
bStudyStarted = true;
}
bool USFGameInstance::IsStarted() const
{
return bStudyStarted;
}
bool USFGameInstance::IsInDebugMode() const
{
return bInDebugMode;
}
bool USFGameInstance::IsContinueStudyStart() const
{
return !bInDebugMode && (StartConditionIndex != -1);
}
bool USFGameInstance::HasEnded() const
{
return bStudyEnded;
}
float USFGameInstance::GetCurrentTime() const
{
if (GetParticipant() && IsStarted())
{
return GetParticipant()->GetCurrentTime();
}
return 0.0f;
}
FString USFGameInstance::GetFactorLevel(FString FactorName) const
{
if (!Participant)
{
return "ParticipantNotSet";
}
if (Participant->GetCurrentCondition()->FactorLevels.Contains(FactorName))
{
return Participant->GetCurrentCondition()->FactorLevels[FactorName];
}
FSFLoggingUtils::Log("[USFGameInstance::GetFactorLevel()]: Factor " + FactorName + " seems not to exist!", true);
return "FactorNotPresent";
}
bool USFGameInstance::HasFactor(FString FactorName) const
{
if (Participant && Participant->GetCurrentCondition()->FactorLevels.Contains(FactorName))
{
return true;
}
return false;
}
FString USFGameInstance::GetIndependentVariableValue(FString VariableName) const
{
if (!GetParticipant())
{
return "";
}
return GetParticipant()->GetIndependentVariable(VariableName).Value;
}
FString USFGameInstance::GetCurrentPhase() const
{
if (!Participant)
{
return "ParticipantNotSet";
}
if (!Participant->GetCurrentCondition())
{
return "NoConditionActive";
}
return Participant->GetCurrentCondition()->PhaseName;
}
int USFGameInstance::GetCurrentConditionsSequenceNumber() const
{
if (!Participant)
{
return -1;
}
return Participant->GetCurrentConditionNumber() + 1;
}
USFCondition* USFGameInstance::GetCurrentCondition() const
{
if (!Participant)
{
return nullptr;
}
return Participant->GetCurrentCondition();
}
USFCondition* USFGameInstance::GetNextCondition() const
{
if (!Participant || !Participant->GetNextConditionIndex().IsSet())
{
return nullptr;
}
return Participant->GetAllConditions()[Participant->GetNextConditionIndex().GetValue()];
}
void USFGameInstance::LogToHUD(FString Text)
{
if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())
&&
Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->IsWidgetPresent())
{
Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->AddLogMessage(Text);
}
else
{
HUDSavedData.LogMessages.Add(Text);
}
//by default we also log this into the debug stream, so you can also check on older messages
FSFLoggingUtils::Log("[Logged to HUD] " + Text);
}
void USFGameInstance::UpdateHUD(FString Status)
{
if (GetHUD())
{
GetHUD()->UpdateHUD(Participant, Status);
}
else
{
HUDSavedData.Status = Status;
if (Participant)
{
HUDSavedData.Participant = Participant->GetID();
}
}
}
ASFMasterHUD* USFGameInstance::GetHUD()
{
if (GetWorld() && GetWorld()->GetFirstPlayerController())
{
return Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
}
return nullptr;
}
USFGazeTracker* USFGameInstance::GetGazeTracker() const
{
return GazeTracker;
}
FExperimenterViewConfig USFGameInstance::GetExperimenterViewConfig() const
{
if (StudySetup)
{
return StudySetup->ExperimenterViewConfig;
}
FSFLoggingUtils::Log(
"[USFGameInstance::GetExperimenterViewConfig] No StudySetup present, returning default ExperimenterViewConfig.",
true);
return FExperimenterViewConfig();
}
USFExperimenterWindow* USFGameInstance::GetExperimenterWindow() const
{
return ExperimenterWindow;
}
void USFGameInstance::SetHUDVisible(bool bVisible)
{
if (GetHUD())
{
GetHUD()->SetHUDVisible(bVisible);
}
}
USFFadeHandler* USFGameInstance::GetFadeHandler()
{
return FadeHandler;
}
ASFStudySetup* USFGameInstance::GetStudySetup()
{
return StudySetup;
}
// ****************************************************************** //
// ******* Executing Study ****************************************** //
// ****************************************************************** //
void USFGameInstance::OnLevelLoaded()
{
//do we need to do something here?
UpdateHUD("Fading In");
}
void USFGameInstance::OnFadedIn()
{
if (Participant && Participant->GetCurrentCondition())
{
//this should be logged first
if (bInDebugMode)
{
USFLoggingBPLibrary::LogComment("Running in DEBUG MODE");
}
FString NumberStr = FString::FromInt(GetCurrentConditionsSequenceNumber()) + "/" + FString::FromInt(
Participant->GetAllConditions().Num());
USFLoggingBPLibrary::LogComment(
"Start Condition " + NumberStr + ": " + Participant->GetCurrentCondition()->GetPrettyName());
}
OnFadedInDelegate.Broadcast();
if (bStartedOnUnrelatedMap)
{
return;
}
Participant->GetCurrentCondition()->Begin();
UpdateHUD("Condition " + FString::FromInt(GetCurrentConditionsSequenceNumber()) + "/" + FString::FromInt(
Participant->GetAllConditions().Num()));
}
USFParticipant* USFGameInstance::GetParticipant() const
{
return Participant;
}
// ****************************************************************** //
// ******* Logging ******************************************** //
// ****************************************************************** //
bool USFGameInstance::LogTick(float DeltaSeconds)
{
if (LogObject->GetLoggingLoopsActive())
{
LogObject->WritePositionLogToFile();
LogObject->WriteGazeTrackingLogToFile();
}
return true;
}
USFLogObject* USFGameInstance::GetLogObject()
{
return LogObject;
}
void USFGameInstance::LogComment(const FString& Comment, bool bAlsoLogToHUD /*= true*/)
{
FSFLoggingUtils::Log("Logged Comment: " + Comment);
if (!GetParticipant())
{
return;
}
UniLog.Log("#" + GetParticipant()->GetCurrentTimeAsString() + ": " + Comment, "ParticipantLog");
if (bAlsoLogToHUD)
{
LogToHUD(Comment);
}
}
void USFGameInstance::LogData(const FString& DependentVariableName, const FString& Value, bool bAlsoLogToHUD /*= true*/)
{
if (!GetParticipant())
{
return;
}
USFCondition* CurrCondition = GetParticipant()->GetCurrentCondition();
if (!CurrCondition->StoreDependentVariableData(DependentVariableName, Value))
{
return;
}
LogComment("Recorded " + DependentVariableName + ": " + Value, bAlsoLogToHUD);
//the data is stored in the phase long table on SetCondition() or EndStudy()
}
void USFGameInstance::LogTrialData(const FString& DependentVariableName, const TArray<FString>& Values,
bool bAlsoLogToHUD /*= true*/)
{
if (!GetParticipant())
{
return;
}
USFCondition* CurrCondition = Get()->GetParticipant()->GetCurrentCondition();
if (!CurrCondition->StoreMultipleTrialDependentVariableData(DependentVariableName, Values))
{
return;
}
FString Data = "";
for (const FString& Value : Values)
{
Data += (Data.IsEmpty() ? "{" : ",") + Value;
}
Data += "}";
LogComment("Recorded " + DependentVariableName + ": " + Data, bAlsoLogToHUD);
}