Files
UnrealEngine/Engine/Source/Runtime/InstallBundleManager/Private/InstallBundleUtils.cpp
2025-05-18 13:04:45 +08:00

1541 lines
52 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InstallBundleUtils.h"
#include "InstallBundleManagerPrivate.h"
#include "Misc/App.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Containers/Ticker.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/CoreDelegates.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "HAL/PlatformFileManager.h"
#include "Serialization/JsonSerializerMacros.h"
#include "Stats/Stats.h"
#include "Algo/AnyOf.h"
#include "Algo/AllOf.h"
#include "Algo/Find.h"
namespace InstallBundleUtil
{
FString GetAppVersion()
{
return FString::Printf(TEXT("%s-%s"), FApp::GetBuildVersion(), ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName()));
}
bool HasInternetConnection(ENetworkConnectionType ConnectionType)
{
return ConnectionType != ENetworkConnectionType::AirplaneMode
&& ConnectionType != ENetworkConnectionType::None;
}
bool SplitHostUrl(const FStringView Url, FStringView& OutHost, FStringView& OutRemainder)
{
OutHost = OutRemainder = FStringView();
const FStringView ProtoHttp = TEXTVIEW("http://");
const FStringView ProtoHttps = TEXTVIEW("https://");
const FStringView ProtoFile = TEXTVIEW("file://");
FStringView Proto;
if (Url.StartsWith(ProtoHttp))
{
Proto = ProtoHttp;
}
else if (Url.StartsWith(ProtoHttps))
{
Proto = ProtoHttps;
}
else if (Url.StartsWith(ProtoFile))
{
Proto = ProtoFile;
}
else
{
return false;
}
FStringView ProtoChopped = Url.RightChop(Proto.Len());
int32 DelimPos = INDEX_NONE;
if (!ProtoChopped.FindChar(TEXT('/'), DelimPos))
{
OutHost = ProtoChopped;
return !OutHost.IsEmpty();
}
OutHost = Url.Left(Proto.Len() + DelimPos);
OutRemainder = Url.RightChop(OutHost.Len());
return true;
}
const TCHAR* GetInstallBundlePauseReason(EInstallBundlePauseFlags Flags)
{
// Return the most appropriate reason given the flags
if (EnumHasAnyFlags(Flags, EInstallBundlePauseFlags::UserPaused))
return TEXT("UserPaused");
if (EnumHasAnyFlags(Flags, EInstallBundlePauseFlags::NoInternetConnection))
return TEXT("NoInternetConnection");
if (EnumHasAnyFlags(Flags, EInstallBundlePauseFlags::OnCellularNetwork))
return TEXT("OnCellularNetwork");
return TEXT("");
}
const FString& GetInstallBundleSectionPrefix()
{
static FString Prefix(TEXT("InstallBundleDefinition ")); // trailing space intentional
return Prefix;
}
bool GetConfiguredBundleSources(TArray<FString>& OutSources, TMap<FString, FString>& OutFallbackSources)
{
#ifdef INSTALL_BUNDLE_SOURCES_FALLBACK_CONFIG_SECTION
const TCHAR* BundleSourceFallbackSection = TEXT(INSTALL_BUNDLE_SOURCES_FALLBACK_CONFIG_SECTION);
#else
const TCHAR* BundleSourceFallbackSection = TEXT("InstallBundleManager.FallbackBundleSources");
#endif // INSTALL_BUNDLE_SOURCES_FALLBACK_CONFIG_SECTION
TArray<FString> ConfigBundleFallbacks;
if (GConfig->GetArray(BundleSourceFallbackSection, TEXT("FallbackBundleSources"), ConfigBundleFallbacks, GInstallBundleIni))
{
OutFallbackSources.Empty(ConfigBundleFallbacks.Num());
for (FString& ConfigFallback : ConfigBundleFallbacks)
{
// Remove parentheses
ConfigFallback.ReplaceInline(TEXT("("), TEXT(""));
ConfigFallback.ReplaceInline(TEXT(")"), TEXT(""));
TArray<FString> Tokens;
if (2 != ConfigFallback.ParseIntoArrayWS(Tokens, TEXT(",")))
{
ensureAlwaysMsgf(false, TEXT("Malformed entry in InstallBundleManager.FallbackBundleSources"));
return false;
}
OutFallbackSources.Add(MoveTemp(Tokens[0]), MoveTemp(Tokens[1]));
}
}
#ifdef INSTALL_BUNDLE_SOURCES_CONFIG_SECTION
const TCHAR* BundleSourceSection = TEXT(INSTALL_BUNDLE_SOURCES_CONFIG_SECTION);
#else
const TCHAR* BundleSourceSection = TEXT("InstallBundleManager.BundleSources");
#endif // INSTALL_BUNDLE_SOURCE_CONFIG_SECTION
TArray<FString> ConfigBundleSources;
if (ensureAlways(GConfig->GetArray(BundleSourceSection, TEXT("DefaultBundleSources"), ConfigBundleSources, GInstallBundleIni)))
{
OutSources = MoveTemp(ConfigBundleSources);
}
else
{
return false;
}
return true;
}
bool HasInstallBundleInConfig(const FString& BundleName)
{
const FConfigFile* InstallBundleConfig = GConfig->FindConfigFile(GInstallBundleIni);
if (InstallBundleConfig)
{
const FString SectionName = InstallBundleUtil::GetInstallBundleSectionPrefix() + BundleName;
return InstallBundleConfig->DoesSectionExist(*SectionName);
}
return false;
}
bool GetMountOptionsFromConfig(const FStringView BundleName, FConfigMountOptions& OutMountOptions)
{
const FConfigFile* InstallBundleConfig = GConfig->FindConfigFile(GInstallBundleIni);
if (InstallBundleConfig)
{
const FString SectionName = InstallBundleUtil::GetInstallBundleSectionPrefix() + BundleName;
if (InstallBundleConfig->DoesSectionExist(*SectionName))
{
OutMountOptions.bWithSoftReferences = false;
InstallBundleConfig->GetBool(*SectionName, TEXT("bMountSoftReferences"), OutMountOptions.bWithSoftReferences);
return true;
}
}
return false;
}
bool AllInstallBundlePredicate(const FConfigFile& InstallBundleConfig, const FString& Section)
{
return true;
}
bool IsPlatformInstallBundlePredicate(const FConfigFile& InstallBundleConfig, const FString& Section)
{
FString PlatformChunkName;
InstallBundleConfig.GetString(*Section, TEXT("PlatformChunkName"), PlatformChunkName);
int32 ChunkID = 0;
if (PlatformChunkName.IsEmpty() && InstallBundleConfig.GetInt(*Section, TEXT("PlatformChunkID"), ChunkID) && ChunkID < 0)
{
return false;
}
return true;
}
TArray<TPair<FString, TArray<FRegexPattern>>> LoadBundleRegexFromConfig(
const FConfigFile& InstallBundleConfig,
TFunctionRef<bool(const FConfigFile& InstallBundleConfig, const FString& Section)> SectionPredicate /*= AllInstallBundlePredicate*/)
{
TArray<TPair<FString, TArray<FRegexPattern>>> BundleRegexList; // BundleName -> FileRegex
for (const TPair<FString, FConfigSection>& Pair : InstallBundleConfig)
{
const FString& Section = Pair.Key;
if (!Section.StartsWith(InstallBundleUtil::GetInstallBundleSectionPrefix()))
continue;
if (!SectionPredicate(InstallBundleConfig, Section))
continue;
TArray<FString> StrSearchRegexPatterns;
if (!InstallBundleConfig.GetArray(*Section, TEXT("FileRegex"), StrSearchRegexPatterns))
continue;
TArray<FRegexPattern> SearchRegexPatterns;
SearchRegexPatterns.Reserve(StrSearchRegexPatterns.Num());
for (const FString& Str : StrSearchRegexPatterns)
{
SearchRegexPatterns.Emplace(Str, ERegexPatternFlags::CaseInsensitive);
}
const FString BundleName = Section.RightChop(InstallBundleUtil::GetInstallBundleSectionPrefix().Len());
BundleRegexList.Emplace(TPair<FString, TArray<FRegexPattern>>(BundleName, MoveTemp(SearchRegexPatterns)));
}
BundleRegexList.StableSort([&InstallBundleConfig](const TPair<FString, TArray<FRegexPattern>>& PairA, const TPair<FString, TArray<FRegexPattern>>& PairB) -> bool
{
int32 BundleAOrder = INT_MAX;
int32 BundleBOrder = INT_MAX;
const FString SectionA = InstallBundleUtil::GetInstallBundleSectionPrefix() + PairA.Key;
const FString SectionB = InstallBundleUtil::GetInstallBundleSectionPrefix() + PairB.Key;
if (!InstallBundleConfig.GetInt(*SectionA, TEXT("Order"), BundleAOrder))
{
UE_LOG(LogInstallBundleManager, Warning, TEXT("Bundle Section %s doesn't have an order"), *SectionA);
}
if (!InstallBundleConfig.GetInt(*SectionB, TEXT("Order"), BundleBOrder))
{
UE_LOG(LogInstallBundleManager, Warning, TEXT("Bundle Section %s doesn't have an order"), *SectionB);
}
return BundleAOrder < BundleBOrder;
});
return BundleRegexList;
}
bool MatchBundleRegex(
const TArray<TPair<FString, TArray<FRegexPattern>>>& BundleRegexList,
const FString& Path,
FString& OutBundleName)
{
const TPair<FString, TArray<FRegexPattern>>* BundleRegexPair = Algo::FindByPredicate(BundleRegexList,
[&Path](const TPair<FString, TArray<FRegexPattern>>& Pair)
{
const TArray<FRegexPattern>& SearchRegexPatterns = Pair.Value;
return Algo::AnyOf(SearchRegexPatterns, [&Path](const FRegexPattern& Pattern)
{
return FRegexMatcher(Pattern, Path).FindNext();
});
});
if (BundleRegexPair)
{
OutBundleName = BundleRegexPair->Key;
return true;
}
return false;
}
FName FInstallBundleManagerKeepAwake::Tag(TEXT("InstallBundleManagerKeepAwake"));
FName FInstallBundleManagerKeepAwake::TagWithRendering(TEXT("InstallBundleManagerKeepAwakeWithRendering"));
bool FInstallBundleManagerScreenSaverControl::bDidDisableScreensaver = false;
int FInstallBundleManagerScreenSaverControl::DisableCount = 0;
void FInstallBundleManagerScreenSaverControl::IncDisable()
{
if (!bDidDisableScreensaver && FPlatformApplicationMisc::IsScreensaverEnabled())
{
bDidDisableScreensaver = FPlatformApplicationMisc::ControlScreensaver(FGenericPlatformApplicationMisc::EScreenSaverAction::Disable);
}
++DisableCount;
}
void FInstallBundleManagerScreenSaverControl::DecDisable()
{
--DisableCount;
if (DisableCount == 0 && bDidDisableScreensaver)
{
FPlatformApplicationMisc::ControlScreensaver(FGenericPlatformApplicationMisc::EScreenSaverAction::Enable);
bDidDisableScreensaver = false;
}
}
std::atomic<int32> InstallBundleSuppressAnalyticsCounter = 0;
FInstallBundleSuppressAnalytics::FInstallBundleSuppressAnalytics()
: bIsEnabled(false)
{
}
FInstallBundleSuppressAnalytics::~FInstallBundleSuppressAnalytics()
{
Disable();
}
void FInstallBundleSuppressAnalytics::Enable()
{
if (!bIsEnabled)
{
bIsEnabled = true;
ensure(InstallBundleSuppressAnalyticsCounter++ >= 0);
}
}
void FInstallBundleSuppressAnalytics::Disable()
{
if (bIsEnabled)
{
bIsEnabled = false;
ensure(--InstallBundleSuppressAnalyticsCounter >= 0);
}
}
bool FInstallBundleSuppressAnalytics::IsEnabled()
{
return InstallBundleSuppressAnalyticsCounter > 0;
}
void StartInstallBundleAsyncIOTask(TUniqueFunction<void()> WorkFunc)
{
return StartInstallBundleAsyncIOTask(GIOThreadPool, MoveTemp(WorkFunc));
}
void StartInstallBundleAsyncIOTask(FQueuedThreadPool* ThreadPool, TUniqueFunction<void()> WorkFunc)
{
(new FAutoDeleteInstallBundleTask(MoveTemp(WorkFunc), nullptr))->StartBackgroundTask(ThreadPool);
}
void StartInstallBundleAsyncIOTask(TArray<TUniquePtr<FInstallBundleTask>>& Tasks, TUniqueFunction<void()> WorkFunc, TUniqueFunction<void()> OnComplete)
{
TUniquePtr<FInstallBundleTask> Task = MakeUnique<FInstallBundleTask>(MoveTemp(WorkFunc), MoveTemp(OnComplete));
Task->StartBackgroundTask(GIOThreadPool);
Tasks.Add(MoveTemp(Task));
}
void StartInstallBundleAsyncIOTask(FQueuedThreadPool* ThreadPool, TArray<TUniquePtr<FInstallBundleTask>>& Tasks, TUniqueFunction<void()> WorkFunc, TUniqueFunction<void()> OnComplete)
{
TUniquePtr<FInstallBundleTask> Task = MakeUnique<FInstallBundleTask>(MoveTemp(WorkFunc), MoveTemp(OnComplete));
Task->StartBackgroundTask(ThreadPool);
Tasks.Add(MoveTemp(Task));
}
void FinishInstallBundleAsyncIOTasks(TArray<TUniquePtr<FInstallBundleTask>>& Tasks)
{
TArray<TUniquePtr<FInstallBundleTask>> FinishedTasks;
for (int32 i = 0; i < Tasks.Num();)
{
TUniquePtr<FInstallBundleTask>& Task = Tasks[i];
check(Task);
if (Task->IsDone())
{
FinishedTasks.Add(MoveTemp(Task));
Tasks.RemoveAtSwap(i, EAllowShrinking::No);
}
else
{
++i;
}
}
for (TUniquePtr<FInstallBundleTask>& Task : FinishedTasks)
{
Task->GetTask().CallOnComplete();
}
}
void CleanupInstallBundleAsyncIOTasks(TArray<TUniquePtr<FInstallBundleTask>>& Tasks)
{
for (TUniquePtr<FInstallBundleTask>& Task : Tasks)
{
check(Task);
if (!Task->Cancel())
{
Task->EnsureCompletion(false);
}
}
}
void FContentRequestStatsMap::StatsBegin(FName BundleName)
{
FContentRequestStats& Stats = StatsMap.FindOrAdd(BundleName);
if (false == ensureAlwaysMsgf(Stats.bOpen, TEXT("StatsBegin - Stat closed for %s"), *BundleName.ToString()))
{
Stats = FContentRequestStats();
}
Stats.StartTime = FPlatformTime::Seconds();
}
void FContentRequestStatsMap::StatsEnd(FName BundleName)
{
FContentRequestStats& Stats = StatsMap.FindOrAdd(BundleName);
ensureAlwaysMsgf(Stats.bOpen && Stats.StartTime > 0, TEXT("StatsEnd - Stat closed for %s"), *BundleName.ToString());
if (Stats.bOpen)
{
ensureAlwaysMsgf(
Algo::AllOf(Stats.StateStats,
[](const TPair<FString, FContentRequestStateStats>& Pair) { return !Pair.Value.bOpen; }),
TEXT("StatsEnd - StateStat open for %s"), *BundleName.ToString());
Stats.EndTime = FPlatformTime::Seconds();
Stats.bOpen = false;
}
}
void FContentRequestStatsMap::StatsReset(FName BundleName)
{
if (FContentRequestStats* Stats = StatsMap.Find(BundleName))
{
ensureAlwaysMsgf(!Stats->bOpen, TEXT("StatsReset - Stat open for %s"), *BundleName.ToString());
ensureAlwaysMsgf(
Algo::AllOf(Stats->StateStats,
[](const TPair<FString, FContentRequestStateStats>& Pair) { return !Pair.Value.bOpen; }),
TEXT("StatsReset - StateStat open for %s"), *BundleName.ToString());
StatsMap.Remove(BundleName);
}
}
void FContentRequestStatsMap::StatsBegin(FName BundleName, const TCHAR* State)
{
FContentRequestStats& Stats = StatsMap.FindOrAdd(BundleName);
if (false == ensureAlwaysMsgf(Stats.bOpen, TEXT("StatsBegin - Stat closed for %s - %s"), *BundleName.ToString(), State))
{
Stats = FContentRequestStats();
Stats.StartTime = FPlatformTime::Seconds();
}
FContentRequestStateStats& StateStats = Stats.StateStats.FindOrAdd(State);
if (false == ensureAlwaysMsgf(StateStats.bOpen, TEXT("StatsBegin - StateStat closed for %s - %s"), *BundleName.ToString(), State))
{
StateStats = FContentRequestStateStats();
}
StateStats.StartTime = FPlatformTime::Seconds();
}
void FContentRequestStatsMap::StatsEnd(FName BundleName, const TCHAR* State, uint64 DataSize /*= 0*/)
{
FContentRequestStats& Stats = StatsMap.FindOrAdd(BundleName);
if (false == ensureAlwaysMsgf(Stats.bOpen && Stats.StartTime > 0, TEXT("StatsEnd - Stat closed for %s - %s"), *BundleName.ToString(), State))
{
Stats = FContentRequestStats();
Stats.StartTime = FPlatformTime::Seconds();
}
FContentRequestStateStats& StateStats = Stats.StateStats.FindOrAdd(State);
if (ensureAlwaysMsgf(StateStats.bOpen && StateStats.StartTime > 0, TEXT("StatsEnd - StateStat closed for %s - %s"), *BundleName.ToString(), State))
{
StateStats.EndTime = FPlatformTime::Seconds();
StateStats.DataSize = DataSize;
StateStats.bOpen = false;
}
}
namespace PersistentStats
{
const FString& LexToString(ETimingStatNames InType)
{
static const FString TotalTime_Real(TEXT("TotalTime_Real"));
static const FString TotalTime_FG(TEXT("TotalTime_FG"));
static const FString TotalTime_BG(TEXT("TotalTime_BG"));
static const FString ChunkDBDownloadTime_Real(TEXT("ChunkDBDownloadTime_Real"));
static const FString ChunkDBDownloadTime_FG(TEXT("ChunkDBDownloadTime_FG"));
static const FString ChunkDBDownloadTime_BG(TEXT("ChunkDBDownloadTime_BG"));
static const FString InstallTime_Real(TEXT("InstallTime_Real"));
static const FString InstallTime_FG(TEXT("InstallTime_FG"));
static const FString InstallTime_BG(TEXT("InstallTime_BG"));
static const FString PSOTime_Real(TEXT("PSOTime_Real"));
static const FString PSOTime_FG(TEXT("PSOTime_FG"));
static const FString PSOTime_BG(TEXT("PSOTime_BG"));
static const FString Unknown(TEXT("<Unknown PersistentStats::ETimingStatNames Value>"));
switch (InType)
{
case ETimingStatNames::TotalTime_Real:
return TotalTime_Real;
case ETimingStatNames::TotalTime_FG:
return TotalTime_FG;
case ETimingStatNames::TotalTime_BG:
return TotalTime_BG;
case ETimingStatNames::ChunkDBDownloadTime_Real:
return ChunkDBDownloadTime_Real;
case ETimingStatNames::ChunkDBDownloadTime_FG:
return ChunkDBDownloadTime_FG;
case ETimingStatNames::ChunkDBDownloadTime_BG:
return ChunkDBDownloadTime_BG;
case ETimingStatNames::InstallTime_Real:
return InstallTime_Real;
case ETimingStatNames::InstallTime_FG:
return InstallTime_FG;
case ETimingStatNames::InstallTime_BG:
return InstallTime_BG;
case ETimingStatNames::PSOTime_Real:
return PSOTime_Real;
case ETimingStatNames::PSOTime_FG:
return PSOTime_FG;
case ETimingStatNames::PSOTime_BG:
return PSOTime_BG;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames LexToString entry! Missing Entry as Int: %d"), (int)(InType));
return Unknown;
}
const FString& LexToString(ECountStatNames InType)
{
static const FString NumResumedFromBackground(TEXT("NumResumedFromBackground"));
static const FString NumResumedFromLaunch(TEXT("NumResumedFromLaunch"));
static const FString NumBackgrounded(TEXT("NumBackgrounded"));
static const FString Unknown(TEXT("<Unknown PersistentStats::ETimingStatNames Value>"));
switch (InType)
{
case ECountStatNames::NumResumedFromBackground:
return NumResumedFromBackground;
case ECountStatNames::NumResumedFromLaunch:
return NumResumedFromLaunch;
case ECountStatNames::NumBackgrounded:
return NumBackgrounded;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ECountStatNames LexToString entry! Missing Entry as Int: %d"), (int)(InType));
return Unknown;
}
bool IsTimerReal(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for known true types
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::PSOTime_Real:
return true;
//Intentional fallthrough for known false types
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::TotalTime_BG:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_BG:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::InstallTime_BG:
case ETimingStatNames::PSOTime_FG:
case ETimingStatNames::PSOTime_BG:
return false;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames IsTimerReal entry! Missing Entry:%s"), *LexToString(InTimerType));
return false;
}
bool IsTimerFG(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for known true types
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::PSOTime_FG:
return true;
//Intentional fallthrough for known false types
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::TotalTime_BG:
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_BG:
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::InstallTime_BG:
case ETimingStatNames::PSOTime_Real:
case ETimingStatNames::PSOTime_BG:
return false;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames IsTimerFG entry! Missing Entry as Int: %s"), *LexToString(InTimerType));
return false;
}
bool IsTimerBG(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for known true types
case ETimingStatNames::TotalTime_BG:
case ETimingStatNames::ChunkDBDownloadTime_BG:
case ETimingStatNames::InstallTime_BG:
case ETimingStatNames::PSOTime_BG:
return true;
//Intentional fallthrough for known false types
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::PSOTime_Real:
case ETimingStatNames::PSOTime_FG:
return false;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames IsTimerBG entry! Missing Entry as Int: %s"), *LexToString(InTimerType));
return false;
}
ETimingStatNames GetAssociatedRealTimerName(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for TotalTime
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::TotalTime_BG:
return ETimingStatNames::TotalTime_Real;
//Intentional fallthrough for ChunkDBDownloadTime
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_BG:
return ETimingStatNames::ChunkDBDownloadTime_Real;
//Intentional fallthrough for InstallTime
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::InstallTime_BG:
return ETimingStatNames::InstallTime_Real;
//Intentional fallthrough for PSOTime
case ETimingStatNames::PSOTime_Real:
case ETimingStatNames::PSOTime_FG:
case ETimingStatNames::PSOTime_BG:
return ETimingStatNames::PSOTime_Real;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames GetAssociatedRealTimerName entry! Missing Entry as Int: %s"), *LexToString(InTimerType));
return ETimingStatNames::NumStatNames;
}
ETimingStatNames GetAssociatedFGTimerName(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for TotalTime
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::TotalTime_BG:
return ETimingStatNames::TotalTime_FG;
//Intentional fallthrough for ChunkDBDownloadTime
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_BG:
return ETimingStatNames::ChunkDBDownloadTime_FG;
//Intentional fallthrough for InstallTime
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::InstallTime_BG:
return ETimingStatNames::InstallTime_FG;
//Intentional fallthrough for PSOTime
case ETimingStatNames::PSOTime_Real:
case ETimingStatNames::PSOTime_FG:
case ETimingStatNames::PSOTime_BG:
return ETimingStatNames::PSOTime_FG;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames GetAssociatedFGTimerName entry! Missing Entry as Int: %s"), *LexToString(InTimerType));
return ETimingStatNames::NumStatNames;
}
ETimingStatNames GetAssociatedBGTimerName(ETimingStatNames InTimerType)
{
switch (InTimerType)
{
//Intentional fallthrough for TotalTime
case ETimingStatNames::TotalTime_Real:
case ETimingStatNames::TotalTime_FG:
case ETimingStatNames::TotalTime_BG:
return ETimingStatNames::TotalTime_BG;
//Intentional fallthrough for ChunkDBDownloadTime
case ETimingStatNames::ChunkDBDownloadTime_Real:
case ETimingStatNames::ChunkDBDownloadTime_FG:
case ETimingStatNames::ChunkDBDownloadTime_BG:
return ETimingStatNames::ChunkDBDownloadTime_BG;
//Intentional fallthrough for InstallTime
case ETimingStatNames::InstallTime_Real:
case ETimingStatNames::InstallTime_FG:
case ETimingStatNames::InstallTime_BG:
return ETimingStatNames::InstallTime_BG;
//Intentional fallthrough for PSOTime
case ETimingStatNames::PSOTime_Real:
case ETimingStatNames::PSOTime_FG:
case ETimingStatNames::PSOTime_BG:
return ETimingStatNames::PSOTime_BG;
default:
break;
}
ensureAlwaysMsgf(false, TEXT("Missing PersistentStats::ETimingStatNames GetAssociatedBGTimerName entry! Missing Entry as Int: %s"), *LexToString(InTimerType));
return ETimingStatNames::NumStatNames;
}
bool FPersistentStatsBase::LoadStatsFromDisk()
{
if (!bHasLoadedFromDisk)
{
FString JSONStringOnDisk;
if (FPaths::FileExists(GetFullPathForStatFile()))
{
FFileHelper::LoadFileToString(JSONStringOnDisk, *GetFullPathForStatFile());
}
if (!JSONStringOnDisk.IsEmpty())
{
bHasLoadedFromDisk = FromJson(JSONStringOnDisk);
if (bHasLoadedFromDisk)
{
OnLoadingDataFromDisk();
}
return bHasLoadedFromDisk;
}
}
return false;
}
bool FPersistentStatsBase::SaveStatsToDisk()
{
bIsDirty = false;
return FFileHelper::SaveStringToFile(ToJson(), *GetFullPathForStatFile());
}
void FPersistentStatsBase::ResetStats(const FString& NewAnalyticsSessionID)
{
TimingStatsMap.Reset();
CountStatMap.Reset();
AnalyticsSessionID = NewAnalyticsSessionID;
bIsDirty = true;
}
bool FPersistentStatsBase::HasTimingStat(ETimingStatNames StatToCheck) const
{
const FPersistentTimerData* FoundStat = TimingStatsMap.Find(LexToString(StatToCheck));
return (nullptr != FoundStat);
}
bool FPersistentStatsBase::HasCountStat(ECountStatNames StatToCheck) const
{
const int* FoundStat = CountStatMap.Find(LexToString(StatToCheck));
return (nullptr != FoundStat);
}
const FPersistentTimerData* FPersistentStatsBase::GetTimingStatData(ETimingStatNames StatToGet) const
{
return TimingStatsMap.Find(LexToString(StatToGet));
}
const int* FPersistentStatsBase::GetCountStatData(ECountStatNames StatToGet) const
{
return CountStatMap.Find(LexToString(StatToGet));
}
void FPersistentStatsBase::IncrementCountStat(PersistentStats::ECountStatNames StatToUpdate)
{
int& StatCount = CountStatMap.FindOrAdd(LexToString(StatToUpdate));
++StatCount;
bIsDirty = true;
}
bool FPersistentStatsBase::IsTimingStatStarted(PersistentStats::ETimingStatNames StatToUpdate) const
{
bool HasStarted = false;
if (HasTimingStat(StatToUpdate))
{
const FPersistentTimerData* FoundStat = GetTimingStatData(StatToUpdate);
if (ensureAlwaysMsgf((nullptr != FoundStat), TEXT("Missing FInstallBundlePersistentTimingData but returned true from HasTimingStat For Stat:%s!"), *LexToString(StatToUpdate)))
{
HasStarted = (FoundStat->LastUpdateTime != 0.);
}
}
return HasStarted;
}
void FPersistentStatsBase::StartTimingStat(PersistentStats::ETimingStatNames StatToUpdate)
{
if (!IsTimingStatStarted(StatToUpdate))
{
FPersistentTimerData& FoundStat = TimingStatsMap.FindOrAdd(LexToString(StatToUpdate));
FoundStat.LastUpdateTime = FPlatformTime::Seconds();
}
//if this stat was already updated then instead of losing the time since its last update by
//starting it again lets just update it to keep that time
else
{
UpdateTimingStat(StatToUpdate);
}
bIsDirty = true;
}
void FPersistentStatsBase::StopTimingStat(PersistentStats::ETimingStatNames StatToUpdate, bool UpdateTimerOnStop /* = true */)
{
//Only want to actually update the timer if we have started it (otherwise the update won't do anything and will ensure)
if (UpdateTimerOnStop && IsTimingStatStarted(StatToUpdate))
{
UpdateTimingStat(StatToUpdate);
}
FPersistentTimerData& FoundStat = TimingStatsMap.FindOrAdd(LexToString(StatToUpdate));
FoundStat.LastUpdateTime = 0.;
bIsDirty = true;
}
void FPersistentStatsBase::UpdateTimingStat(PersistentStats::ETimingStatNames StatToUpdate)
{
if (ensureAlwaysMsgf(IsTimingStatStarted(StatToUpdate), TEXT("Calling UpdateTimingStat on a stat that hasn't been started! %s"), *LexToString(StatToUpdate)))
{
FPersistentTimerData& FoundStat = TimingStatsMap.FindOrAdd(LexToString(StatToUpdate));
const double CurrentTime = FPlatformTime::Seconds();
const double TimeSinceUpdate = CurrentTime - FoundStat.LastUpdateTime;
if (ensureAlwaysMsgf((TimeSinceUpdate > 0.f), TEXT("Logic Error! Invalid saved LastUpdateTime for Stat %s!"), *LexToString(StatToUpdate)))
{
FoundStat.CurrentValue += TimeSinceUpdate;
}
FoundStat.LastUpdateTime = CurrentTime;
bIsDirty = true;
}
}
void FPersistentStatsBase::UpdateAllActiveTimers()
{
for (uint8 TimingStatNameIndex = 0; TimingStatNameIndex < (uint8)PersistentStats::ETimingStatNames::NumStatNames; ++TimingStatNameIndex)
{
PersistentStats::ETimingStatNames EnumForIndex = (PersistentStats::ETimingStatNames)TimingStatNameIndex;
if (IsTimingStatStarted(EnumForIndex))
{
UpdateTimingStat(EnumForIndex);
}
}
}
void FPersistentStatsBase::StopAllActiveTimers()
{
for (uint8 TimingStatIndex = 0; TimingStatIndex < static_cast<uint8>(ETimingStatNames::NumStatNames); ++TimingStatIndex)
{
ETimingStatNames TimingStatAsEnum = static_cast<ETimingStatNames>(TimingStatIndex);
if (IsTimingStatStarted(TimingStatAsEnum))
{
StopTimingStat(TimingStatAsEnum);
}
}
}
void FPersistentStatsBase::StatsBegin(const FString& ExpectedAnalyticsID, bool bForceResetData /* = false */)
{
bIsActive = true;
LoadStatsFromDisk();
//If our Analytics ID doesn't match our expected we need to reset the data as we have started a new persistent session
if (bForceResetData || !AnalyticsSessionID.Equals(ExpectedAnalyticsID))
{
ResetStats(ExpectedAnalyticsID);
}
//Immediately save here so we don't risk reloading the same stale data
//if we don't make it to an update
SaveStatsToDisk();
}
void FPersistentStatsBase::StatsEnd(bool bStopAllActiveTimers /* = true */)
{
bIsActive = false;
if (bStopAllActiveTimers)
{
StopAllActiveTimers();
}
//Immediately save here as we only look to update active dirty bundles, and since
//this likely won't be changed anymore we might as well save it out now
SaveStatsToDisk();
}
void FPersistentStatsBase::OnLoadingDataFromDisk()
{
HandleTimerStatsAfterDataLoad();
}
void FPersistentStatsBase::HandleTimerStatsAfterDataLoad()
{
//Go through all timing stats and handle each one accordingly
//All Real timers should be updated after load without being stopped.
//All FG Timers we should stop these timers without updating them so that they don't accrue time from backgrounding
//All BG timers Should be stopped, but update their timers on stopping so they accrue time from being inactive
/*
Handle Real Timers
*/
if (IsTimingStatStarted(ETimingStatNames::TotalTime_Real))
{
UpdateTimingStat(ETimingStatNames::TotalTime_Real);
}
if (IsTimingStatStarted(ETimingStatNames::ChunkDBDownloadTime_Real))
{
UpdateTimingStat(ETimingStatNames::ChunkDBDownloadTime_Real);
}
if (IsTimingStatStarted(ETimingStatNames::InstallTime_Real))
{
UpdateTimingStat(ETimingStatNames::InstallTime_Real);
}
if (IsTimingStatStarted(ETimingStatNames::PSOTime_Real))
{
UpdateTimingStat(ETimingStatNames::PSOTime_Real);
}
/*
Handle Foreground Timers
*/
if (IsTimingStatStarted(ETimingStatNames::TotalTime_FG))
{
StopTimingStat(ETimingStatNames::TotalTime_FG, false);
}
if (IsTimingStatStarted(ETimingStatNames::ChunkDBDownloadTime_FG))
{
StopTimingStat(ETimingStatNames::ChunkDBDownloadTime_FG, false);
}
if (IsTimingStatStarted(ETimingStatNames::InstallTime_FG))
{
StopTimingStat(ETimingStatNames::InstallTime_FG, false);
}
if (IsTimingStatStarted(ETimingStatNames::PSOTime_FG))
{
StopTimingStat(ETimingStatNames::PSOTime_FG, false);
}
/*
Handle Background Timers
*/
if (IsTimingStatStarted(ETimingStatNames::TotalTime_BG))
{
StopTimingStat(ETimingStatNames::TotalTime_BG, true);
}
if (IsTimingStatStarted(ETimingStatNames::ChunkDBDownloadTime_BG))
{
StopTimingStat(ETimingStatNames::ChunkDBDownloadTime_BG, true);
}
if (IsTimingStatStarted(ETimingStatNames::InstallTime_BG))
{
StopTimingStat(ETimingStatNames::InstallTime_BG, true);
}
if (IsTimingStatStarted(ETimingStatNames::PSOTime_BG))
{
StopTimingStat(ETimingStatNames::PSOTime_BG, true);
}
}
void FSessionPersistentStats::AddRequiredBundles(const TArray<FString>& RequiredBundlesToAdd)
{
for (const FString& BundleName : RequiredBundlesToAdd)
{
RequiredBundles.AddUnique(BundleName);
}
bIsDirty = true;
}
void FSessionPersistentStats::AddRequiredBundles(const TArray<FName>& RequiredBundlesToAdd)
{
for (FName BundleName : RequiredBundlesToAdd)
{
RequiredBundles.AddUnique(BundleName.ToString());
}
bIsDirty = true;
}
void FSessionPersistentStats::GetRequiredBundles(TArray<FString>& OutRequiredBundles) const
{
OutRequiredBundles.Empty();
for (const FString& BundleName : RequiredBundles)
{
OutRequiredBundles.Add(BundleName);
}
}
void FSessionPersistentStats::ResetRequiredBundles(const TArray<FString>& NewRequiredBundles /* = TArray<FString>() */)
{
RequiredBundles.Empty();
AddRequiredBundles(NewRequiredBundles);
bIsDirty = true;
}
const FString FBundlePersistentStats::GetFullPathForStatFile() const
{
return FPaths::Combine(FPlatformMisc::GamePersistentDownloadDir(), TEXT("PersistentStats"), TEXT("BundleStats"), (BundleName + TEXT(".json")));
}
const FString FSessionPersistentStats::GetFullPathForStatFile() const
{
return FPaths::Combine(FPlatformMisc::GamePersistentDownloadDir(), TEXT("PersistentStats"), TEXT("SessionStats"), (SessionName + TEXT(".json")));
}
FPersistentStatContainerBase::FPersistentStatContainerBase()
: PerBundlePersistentStatMap()
, SessionPersistentStatMap()
, TickHandle()
, OnApp_EnteringForegroundHandle()
, OnApp_EnteringBackgroundHandle()
, TimerAutoUpdateTimeRemaining(10.0f)
, TimerDirtyStatUpdateTimeRemaining(10.0f)
, bShouldAutoUpdateTimersInTick(true)
, TimerAutoUpdateRate(10.0f)
, bShouldSaveDirtyStatsOnTick(true)
, DirtyStatSaveToDiskRate(5.f)
, bShouldAutoHandleFGBGStats(true)
{
InitializeBase();
}
FPersistentStatContainerBase::~FPersistentStatContainerBase()
{
ShutdownBase();
}
void FPersistentStatContainerBase::InitializeBase()
{
//Load Settings from Config
{
GConfig->GetBool(TEXT("InstallBundleManager.PersistentStatSettings"), TEXT("bShouldAutoUpdateTimersInTick"), bShouldAutoUpdateTimersInTick, GEngineIni);
GConfig->GetFloat(TEXT("InstallBundleManager.PersistentStatSettings"), TEXT("TimerAutoUpdateRate"), TimerAutoUpdateRate, GEngineIni);
GConfig->GetBool(TEXT("InstallBundleManager.PersistentStatSettings"), TEXT("bShouldSaveDirtyStatsOnTick"), bShouldSaveDirtyStatsOnTick, GEngineIni);
GConfig->GetFloat(TEXT("InstallBundleManager.PersistentStatSettings"), TEXT("DirtyStatSaveToDiskRate"), DirtyStatSaveToDiskRate, GEngineIni);
GConfig->GetBool(TEXT("InstallBundleManager.PersistentStatSettings"), TEXT("bShouldAutoHandleFGBGStats"), bShouldAutoHandleFGBGStats, GEngineIni);
//Reset timers so they follow the new loaded-in value
ResetTimerUpdate();
ResetDirtyStatUpdate();
}
//Setup Delegates (Needs to happen after config to have AutoUpdate settings loaded)
{
//Only setup a tick function if we would use it
if ((bShouldAutoUpdateTimersInTick || bShouldSaveDirtyStatsOnTick) && !TickHandle.IsValid())
{
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FPersistentStatContainerBase::Tick));
}
//Only setup Foreground/Background delegates if we should be using them to swap stats
if (bShouldAutoHandleFGBGStats)
{
if (!OnApp_EnteringForegroundHandle.IsValid())
{
OnApp_EnteringForegroundHandle = FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(this, &FPersistentStatContainerBase::OnApp_EnteringForeground);
}
if (!OnApp_EnteringBackgroundHandle.IsValid())
{
OnApp_EnteringBackgroundHandle = FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(this, &FPersistentStatContainerBase::OnApp_EnteringBackground);
}
}
}
}
void FPersistentStatContainerBase::ShutdownBase()
{
if (TickHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
TickHandle.Reset();
}
if (OnApp_EnteringForegroundHandle.IsValid())
{
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Remove(OnApp_EnteringForegroundHandle);
OnApp_EnteringForegroundHandle.Reset();
}
if (OnApp_EnteringBackgroundHandle.IsValid())
{
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Remove(OnApp_EnteringBackgroundHandle);
OnApp_EnteringBackgroundHandle.Reset();
}
}
bool FPersistentStatContainerBase::Tick(float dt)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FPersistentStatContainerBase_Tick);
if (bShouldAutoUpdateTimersInTick)
{
//Only update all active timers every TimerStat_ResetTimerValue seconds
TimerAutoUpdateTimeRemaining -= dt;
if (TimerAutoUpdateTimeRemaining < 0.0f)
{
ResetTimerUpdate();
UpdateAllBundlesActiveTimers();
UpdateAllSessionActiveTimers();
}
}
if (bShouldSaveDirtyStatsOnTick)
{
TimerDirtyStatUpdateTimeRemaining -= dt;
if (TimerDirtyStatUpdateTimeRemaining < 0.0f)
{
ResetDirtyStatUpdate();
SaveAllDirtyStatsToDisk();
}
}
//go ahead and always Tick once we start
return true;
}
void FPersistentStatContainerBase::OnTimerStartedForStat(FPersistentStatsBase& BundleStatForTimer, ETimingStatNames TimerStarted)
{
//If we are auto handling FG/BG stats and we have a real timer, start the _FG version with the _Real version
if (bShouldAutoHandleFGBGStats && IsTimerReal(TimerStarted))
{
BundleStatForTimer.StartTimingStat(GetAssociatedFGTimerName(TimerStarted));
//We should also check to make sure the _BG version is stopped if it is running
const ETimingStatNames BGTimingStat = GetAssociatedBGTimerName(TimerStarted);
if (BundleStatForTimer.IsTimingStatStarted(BGTimingStat))
{
BundleStatForTimer.StopTimingStat(BGTimingStat, true);
}
}
}
void FPersistentStatContainerBase::OnTimerStoppedForStat(FPersistentStatsBase& BundleStatForTimer, ETimingStatNames TimerStarted)
{
//If we are auto handling FG/BG stats and we have a real timer, stop the _FG and _BG version with the _Real version
if (bShouldAutoHandleFGBGStats && IsTimerReal(TimerStarted))
{
BundleStatForTimer.StopTimingStat(GetAssociatedFGTimerName(TimerStarted));
BundleStatForTimer.StopTimingStat(GetAssociatedBGTimerName(TimerStarted));
}
}
void FPersistentStatContainerBase::ResetTimerUpdate()
{
TimerAutoUpdateTimeRemaining = TimerAutoUpdateRate;
}
void FPersistentStatContainerBase::ResetDirtyStatUpdate()
{
TimerDirtyStatUpdateTimeRemaining = DirtyStatSaveToDiskRate;
}
void FPersistentStatContainerBase::SaveAllDirtyStatsToDisk()
{
TArray<FName> AllBundleStatNames;
PerBundlePersistentStatMap.GetKeys(AllBundleStatNames);
for (FName& BundleName : AllBundleStatNames)
{
FBundlePersistentStats* BundleStats = PerBundlePersistentStatMap.Find(BundleName);
check(BundleStats);
if (BundleStats->IsDirty())
{
BundleStats->SaveStatsToDisk();
}
}
TArray<FString> AllSessionStatNames;
SessionPersistentStatMap.GetKeys(AllSessionStatNames);
for (const FString& SessionName : AllSessionStatNames)
{
FSessionPersistentStats* SessionStats = SessionPersistentStatMap.Find(SessionName);
check(SessionStats);
if (SessionStats->IsDirty())
{
SessionStats->SaveStatsToDisk();
}
}
}
void FPersistentStatContainerBase::RemoveSessionStats(const FString& SessionName)
{
SessionPersistentStatMap.Remove(SessionName);
}
void FPersistentStatContainerBase::RemoveBundleStats(FName BundleName)
{
PerBundlePersistentStatMap.Remove(BundleName);
}
void FPersistentStatContainerBase::StartBundlePersistentStatTracking(FName BundleName, const FString& ExpectedAnalyticsID /* = FString() */, bool bForceResetStatData /* = false */)
{
//Use the base expected analytics ID if one was not passed in
const FString ExpectedAnalyticsToUse = ExpectedAnalyticsID.IsEmpty() ? FPersistentStatsBase::GetBaseExpectedAnalyticsID() : ExpectedAnalyticsID;
FBundlePersistentStats& FoundBundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
FoundBundleStats.StatsBegin(ExpectedAnalyticsToUse, bForceResetStatData);
}
void FPersistentStatContainerBase::StartSessionPersistentStatTracking(const FString& SessionName, const TArray<FName>& RequiredBundles /* = TArray<FName>() */, const FString& ExpectedAnalyticsID /* = FString() */, bool bForceResetStatData /* = false */)
{
//Use the base expected analytics ID if one was not passed in
const FString ExpectedAnalyticsToUse = ExpectedAnalyticsID.IsEmpty() ? FPersistentStatsBase::GetBaseExpectedAnalyticsID() : ExpectedAnalyticsID;
FSessionPersistentStats& FoundSessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
FoundSessionStats.StatsBegin(ExpectedAnalyticsToUse, bForceResetStatData);
//Also append starting required bundles as we may have new ones from the ones already in data
FoundSessionStats.AddRequiredBundles(RequiredBundles);
//Go ahead and load data for all bundles in our RequiredBundles list while we are starting our Session
LoadRequiredBundleDataFromDiskForSession(SessionName);
}
void FPersistentStatContainerBase::StopBundlePersistentStatTracking(FName BundleName, bool bStopAllActiveTimers /* = true */)
{
FBundlePersistentStats* FoundBundleStats = PerBundlePersistentStatMap.Find(BundleName);
if (nullptr != FoundBundleStats)
{
FoundBundleStats->StatsEnd(bStopAllActiveTimers);
}
}
void FPersistentStatContainerBase::StopSessionPersistentStatTracking(const FString& SessionName, bool bStopAllActiveTimers /* = true */)
{
FSessionPersistentStats* FoundSessionStats = SessionPersistentStatMap.Find(SessionName);
if (nullptr != FoundSessionStats)
{
FoundSessionStats->StatsEnd(bStopAllActiveTimers);
}
}
void FPersistentStatContainerBase::LoadRequiredBundleDataFromDiskForSession(const FString& SessionName)
{
FSessionPersistentStats* FoundSessionStats = SessionPersistentStatMap.Find(SessionName);
if (nullptr != FoundSessionStats)
{
TArray<FString> RequiredBundles;
FoundSessionStats->GetRequiredBundles(RequiredBundles);
for (const FString& Bundle : RequiredBundles)
{
FBundlePersistentStats* FoundBundleStats = PerBundlePersistentStatMap.Find(*Bundle);
if (nullptr == FoundBundleStats)
{
FBundlePersistentStats NewBundleStats = FBundlePersistentStats(*Bundle);
NewBundleStats.LoadStatsFromDisk();
PerBundlePersistentStatMap.Emplace(*Bundle, MoveTemp(NewBundleStats));
}
else
{
FoundBundleStats->LoadStatsFromDisk();
}
}
}
}
void FPersistentStatContainerBase::StartBundlePersistentStatTimer(FName BundleName, ETimingStatNames TimerToStart)
{
FBundlePersistentStats& FoundBundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
FoundBundleStats.StartTimingStat(TimerToStart);
ensureAlwaysMsgf(FoundBundleStats.IsActive(), TEXT("Invalid attempt to start %s on bundle %s that hasn't yet had StartBundlePersistentStatTracking called on it! Should always start tracking before using persistent stats!"), *LexToString(TimerToStart), *(BundleName.ToString()));
OnTimerStartedForStat(FoundBundleStats, TimerToStart);
}
void FPersistentStatContainerBase::StartSessionPersistentStatTimer(const FString& SessionName, ETimingStatNames TimerToStart)
{
FSessionPersistentStats& FoundSessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
FoundSessionStats.StartTimingStat(TimerToStart);
ensureAlwaysMsgf(FoundSessionStats.IsActive(), TEXT("Invalid attempt to start %s on session %s that hasn't yet had StartBundlePersistentStatTracking called on it! Should always start tracking before using persistent stats!"), *LexToString(TimerToStart), *SessionName);
OnTimerStartedForStat(FoundSessionStats, TimerToStart);
}
void FPersistentStatContainerBase::StopBundlePersistentStatTimer(FName BundleName, ETimingStatNames TimerToStop)
{
FBundlePersistentStats& FoundBundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
FoundBundleStats.StopTimingStat(TimerToStop);
OnTimerStoppedForStat(FoundBundleStats, TimerToStop);
}
void FPersistentStatContainerBase::StopSessionPersistentStatTimer(const FString& SessionName, ETimingStatNames TimerToStop)
{
FSessionPersistentStats& FoundSessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
FoundSessionStats.StopTimingStat(TimerToStop);
OnTimerStoppedForStat(FoundSessionStats, TimerToStop);
}
void FPersistentStatContainerBase::UpdateBundlePersistentStatTimer(FName BundleName, ETimingStatNames TimerToUpdate)
{
FBundlePersistentStats& FoundBundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
FoundBundleStats.UpdateTimingStat(TimerToUpdate);
}
void FPersistentStatContainerBase::UpdateSessionPersistentStatTimer(const FString& SessionName, ETimingStatNames TimerToUpdate)
{
FSessionPersistentStats& FoundSessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
FoundSessionStats.UpdateTimingStat(TimerToUpdate);
}
void FPersistentStatContainerBase::IncrementBundlePersistentCounter(FName BundleName, ECountStatNames CounterToUpdate)
{
FBundlePersistentStats& FoundBundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
FoundBundleStats.IncrementCountStat(CounterToUpdate);
ensureAlwaysMsgf(FoundBundleStats.IsActive(), TEXT("Invalid attempt to increment %s on bundle %s that hasn't yet had StartBundlePersistentStatTracking called on it! Should always start tracking before using persistent stats!"), *LexToString(CounterToUpdate), *(BundleName.ToString()));
}
void FPersistentStatContainerBase::IncrementSessionPersistentCounter(const FString& SessionName, ECountStatNames CounterToUpdate)
{
FSessionPersistentStats& FoundSessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
FoundSessionStats.IncrementCountStat(CounterToUpdate);
ensureAlwaysMsgf(FoundSessionStats.IsActive(), TEXT("Invalid attempt to increment %s on session %s that hasn't yet had StartBundlePersistentStatTracking called on it! Should always start tracking before using persistent stats!"), *LexToString(CounterToUpdate), *SessionName);
}
void FPersistentStatContainerBase::OnApp_EnteringBackground()
{
SCOPED_ENTER_BACKGROUND_EVENT(STAT_InstallBundle_OnApp_EnteringBackground);
OnBackground_HandleBundleStats();
OnBackground_HandleSessionStats();
}
void FPersistentStatContainerBase::OnApp_EnteringForeground()
{
OnForeground_HandleBundleStats();
OnForeground_HandleSessionStats();
}
void FPersistentStatContainerBase::OnBackground_HandleBundleStats()
{
for (TPair<FName, FBundlePersistentStats>& BundlePair : PerBundlePersistentStatMap)
{
//Only bother updating bundles listed as active
if (BundlePair.Value.IsActive())
{
UpdateStatsForBackground(BundlePair.Value);
}
}
}
void FPersistentStatContainerBase::OnForeground_HandleBundleStats()
{
for (TPair<FName, FBundlePersistentStats>& BundlePair : PerBundlePersistentStatMap)
{
//Only bother updating bundles listed as active
if (BundlePair.Value.IsActive())
{
UpdateStatsForForeground(BundlePair.Value);
}
}
}
void FPersistentStatContainerBase::OnBackground_HandleSessionStats()
{
for (TPair<FString, FSessionPersistentStats>& SessionPair : SessionPersistentStatMap)
{
//Only bother updating sessions listed as active
if (SessionPair.Value.IsActive())
{
UpdateStatsForBackground(SessionPair.Value);
}
}
}
void FPersistentStatContainerBase::OnForeground_HandleSessionStats()
{
for (TPair<FString, FSessionPersistentStats>& SessionPair : SessionPersistentStatMap)
{
//Only bother updating sessions listed as active
if (SessionPair.Value.IsActive())
{
UpdateStatsForForeground(SessionPair.Value);
}
}
}
void FPersistentStatContainerBase::UpdateStatsForBackground(FPersistentStatsBase& StatToUpdate)
{
StatToUpdate.IncrementCountStat(ECountStatNames::NumBackgrounded);
//Always handle ActiveTotalTime as this isn't dependent on what stage of the process we are in
if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::TotalTime_FG))
{
StatToUpdate.StartTimingStat(ETimingStatNames::TotalTime_BG);
StatToUpdate.StopTimingStat(ETimingStatNames::TotalTime_FG);
}
//Besides the ActiveTotalTime above, we should only be in 1 of the following states at a time, so only handle the appropriate swap
if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::ChunkDBDownloadTime_FG))
{
StatToUpdate.StartTimingStat(ETimingStatNames::ChunkDBDownloadTime_BG);
StatToUpdate.StopTimingStat(ETimingStatNames::ChunkDBDownloadTime_FG);
}
else if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::InstallTime_FG))
{
StatToUpdate.StartTimingStat(ETimingStatNames::InstallTime_BG);
StatToUpdate.StopTimingStat(ETimingStatNames::InstallTime_FG);
}
else if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::PSOTime_FG))
{
StatToUpdate.StartTimingStat(ETimingStatNames::PSOTime_BG);
StatToUpdate.StopTimingStat(ETimingStatNames::PSOTime_FG);
}
}
void FPersistentStatContainerBase::UpdateStatsForForeground(FPersistentStatsBase& StatToUpdate)
{
StatToUpdate.IncrementCountStat(ECountStatNames::NumResumedFromBackground);
//Always handle ActiveTotalTime as this isn't dependent on what stage of the process we are in
if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::TotalTime_BG))
{
StatToUpdate.StopTimingStat(ETimingStatNames::TotalTime_BG);
StatToUpdate.StartTimingStat(ETimingStatNames::TotalTime_FG);
}
//Besides the ActiveTotalTime above, we should only be in 1 of the following states at a time, so only handle the appropriate swap
if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::ChunkDBDownloadTime_BG))
{
StatToUpdate.StopTimingStat(ETimingStatNames::ChunkDBDownloadTime_BG);
StatToUpdate.StartTimingStat(ETimingStatNames::ChunkDBDownloadTime_FG);
}
else if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::InstallTime_BG))
{
StatToUpdate.StopTimingStat(ETimingStatNames::InstallTime_BG);
StatToUpdate.StartTimingStat(ETimingStatNames::InstallTime_FG);
}
else if (StatToUpdate.IsTimingStatStarted(ETimingStatNames::PSOTime_BG))
{
StatToUpdate.StopTimingStat(ETimingStatNames::PSOTime_BG);
StatToUpdate.StartTimingStat(ETimingStatNames::PSOTime_FG);
}
}
void FPersistentStatContainerBase::UpdateAllBundlesActiveTimers()
{
TArray<FName> BundleNames;
PerBundlePersistentStatMap.GetKeys(BundleNames);
for (FName BundleName : BundleNames)
{
InstallBundleUtil::PersistentStats::FBundlePersistentStats& BundleStats = PerBundlePersistentStatMap.FindOrAdd(BundleName, FBundlePersistentStats(BundleName));
BundleStats.UpdateAllActiveTimers();
}
}
void FPersistentStatContainerBase::UpdateAllSessionActiveTimers()
{
TArray<FString> SessionNames;
SessionPersistentStatMap.GetKeys(SessionNames);
for (const FString& SessionName : SessionNames)
{
InstallBundleUtil::PersistentStats::FSessionPersistentStats& SessionStats = SessionPersistentStatMap.FindOrAdd(SessionName, FSessionPersistentStats(SessionName));
SessionStats.UpdateAllActiveTimers();
}
}
const FString FPersistentStatsBase::GetBaseExpectedAnalyticsID()
{
const FString BaseExpectedAnalyticsID = FPlatformMisc::GetDeviceId() + TEXT("_") + FApp::GetBuildVersion();
return BaseExpectedAnalyticsID;
}
const FBundlePersistentStats* FPersistentStatContainerBase::GetBundleStat(FName BundleName) const
{
return PerBundlePersistentStatMap.Find(BundleName);
}
const FSessionPersistentStats* FPersistentStatContainerBase::GetSessionStat(const FString& SessionName) const
{
return SessionPersistentStatMap.Find(SessionName);
}
}
}