2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-07-09 02:14:11 -04:00
# include "BundlePrereqCombinedStatusHelper.h"
# include "Containers/Ticker.h"
2022-08-26 01:45:41 -04:00
# include "InstallBundleManagerPrivate.h"
2022-10-11 02:52:57 -04:00
# include "InstallBundleUtils.h"
2020-09-01 14:07:48 -04:00
# include "Stats/Stats.h"
2021-05-12 18:10:03 -04:00
# include "Algo/Transform.h"
2019-07-09 02:14:11 -04:00
2022-10-11 02:52:57 -04:00
const TCHAR * LexToString ( FInstallBundleCombinedProgressTracker : : ECombinedBundleStatus Status )
{
static const TCHAR * Strings [ ] =
{
TEXT ( " Unknown " ) ,
TEXT ( " Initializing " ) ,
TEXT ( " Updating " ) ,
TEXT ( " Finishing " ) ,
TEXT ( " Finished " ) ,
TEXT ( " Count " )
} ;
static_assert ( InstallBundleUtil : : CastToUnderlying ( FInstallBundleCombinedProgressTracker : : ECombinedBundleStatus : : Count ) = = UE_ARRAY_COUNT ( Strings ) - 1 , " " ) ;
return Strings [ InstallBundleUtil : : CastToUnderlying ( Status ) ] ;
}
2023-11-02 19:26:08 -04:00
FInstallBundleCombinedProgressTracker : : FInstallBundleCombinedProgressTracker ( bool bAutoTick /*= true*/ , TUniqueFunction < void ( const FCombinedProgress & ) > InOnTick /*= nullptr*/ )
: OnTick ( MoveTemp ( InOnTick ) )
2019-07-09 02:14:11 -04:00
{
2021-05-12 18:10:03 -04:00
SetupDelegates ( bAutoTick ) ;
2019-07-09 02:14:11 -04:00
}
2020-10-29 13:38:15 -04:00
FInstallBundleCombinedProgressTracker : : ~ FInstallBundleCombinedProgressTracker ( )
2019-07-09 02:14:11 -04:00
{
CleanUpDelegates ( ) ;
}
2020-10-29 13:38:15 -04:00
FInstallBundleCombinedProgressTracker : : FInstallBundleCombinedProgressTracker ( const FInstallBundleCombinedProgressTracker & Other )
2019-07-12 00:18:00 -04:00
{
* this = Other ;
}
2020-10-29 13:38:15 -04:00
FInstallBundleCombinedProgressTracker : : FInstallBundleCombinedProgressTracker ( FInstallBundleCombinedProgressTracker & & Other )
2019-07-12 00:18:00 -04:00
{
* this = MoveTemp ( Other ) ;
}
2020-10-29 13:38:15 -04:00
FInstallBundleCombinedProgressTracker & FInstallBundleCombinedProgressTracker : : operator = ( const FInstallBundleCombinedProgressTracker & Other )
2019-07-12 00:18:00 -04:00
{
if ( this ! = & Other )
{
//Just copy all this data
RequiredBundleNames = Other . RequiredBundleNames ;
BundleStatusCache = Other . BundleStatusCache ;
CachedBundleWeights = Other . CachedBundleWeights ;
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress = Other . CurrentCombinedProgress ;
2019-07-12 00:18:00 -04:00
InstallBundleManager = Other . InstallBundleManager ;
//Don't copy TickHandle as we want to setup our own here
2021-05-12 18:10:03 -04:00
SetupDelegates ( Other . TickHandle . IsValid ( ) ) ;
2019-07-12 00:18:00 -04:00
}
return * this ;
}
2020-10-29 13:38:15 -04:00
FInstallBundleCombinedProgressTracker & FInstallBundleCombinedProgressTracker : : operator = ( FInstallBundleCombinedProgressTracker & & Other )
2019-07-12 00:18:00 -04:00
{
if ( this ! = & Other )
{
//Just copy small data
2021-05-12 18:10:03 -04:00
CurrentCombinedProgress = Other . CurrentCombinedProgress ;
2019-07-12 00:18:00 -04:00
InstallBundleManager = Other . InstallBundleManager ;
//Move bigger data
RequiredBundleNames = MoveTemp ( Other . RequiredBundleNames ) ;
BundleStatusCache = MoveTemp ( Other . BundleStatusCache ) ;
CachedBundleWeights = MoveTemp ( Other . CachedBundleWeights ) ;
//Prevent other from having callbacks now that its information is gone
Other . CleanUpDelegates ( ) ;
//Don't copy TickHandle as we want to setup our own here
2021-05-12 18:10:03 -04:00
SetupDelegates ( Other . TickHandle . IsValid ( ) ) ;
2019-07-12 00:18:00 -04:00
}
return * this ;
}
2021-05-12 18:10:03 -04:00
void FInstallBundleCombinedProgressTracker : : SetupDelegates ( bool bAutoTick )
2019-07-09 02:14:11 -04:00
{
2019-07-12 00:18:00 -04:00
CleanUpDelegates ( ) ;
2020-10-29 13:38:15 -04:00
IInstallBundleManager : : InstallBundleCompleteDelegate . AddRaw ( this , & FInstallBundleCombinedProgressTracker : : OnBundleInstallComplete ) ;
IInstallBundleManager : : PausedBundleDelegate . AddRaw ( this , & FInstallBundleCombinedProgressTracker : : OnBundleInstallPauseChanged ) ;
2021-05-12 18:10:03 -04:00
if ( bAutoTick )
{
2021-08-16 11:05:18 -04:00
TickHandle = FTSTicker : : GetCoreTicker ( ) . AddTicker ( FTickerDelegate : : CreateRaw ( this , & FInstallBundleCombinedProgressTracker : : Tick ) ) ;
2021-05-12 18:10:03 -04:00
}
2019-07-09 02:14:11 -04:00
}
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : CleanUpDelegates ( )
2019-07-09 02:14:11 -04:00
{
2019-08-08 00:48:21 -04:00
IInstallBundleManager : : InstallBundleCompleteDelegate . RemoveAll ( this ) ;
2019-12-09 13:52:45 -05:00
IInstallBundleManager : : PausedBundleDelegate . RemoveAll ( this ) ;
2019-07-09 02:14:11 -04:00
if ( TickHandle . IsValid ( ) )
{
2021-08-16 11:05:18 -04:00
FTSTicker : : GetCoreTicker ( ) . RemoveTicker ( TickHandle ) ;
2019-07-09 02:14:11 -04:00
TickHandle . Reset ( ) ;
}
}
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : SetBundlesToTrackFromContentState ( const FInstallBundleCombinedContentState & BundleContentState , TArrayView < FName > BundlesToTrack )
2019-07-09 02:14:11 -04:00
{
RequiredBundleNames . Empty ( ) ;
CachedBundleWeights . Empty ( ) ;
2019-07-10 16:52:53 -04:00
BundleStatusCache . Empty ( ) ;
2021-05-12 18:10:03 -04:00
2023-05-18 21:07:41 -04:00
//Go through all bundles until we hit a non-zero weight bundle.
//This is to help catch instances where we pass in all zero weight bundles to track and need
//to thus calculate their weight dynamically based on everything having even weight
bool bAreAllBundlesZeroWeight = true ;
for ( const TPair < FName , FInstallBundleContentState > & IndividualBundlePair : BundleContentState . IndividualBundleStates )
{
const FInstallBundleContentState & BundleState = IndividualBundlePair . Value ;
2024-08-08 16:23:23 -04:00
if ( BundleState . Weight > SMALL_NUMBER )
2023-05-18 21:07:41 -04:00
{
bAreAllBundlesZeroWeight = false ;
break ;
}
}
2020-10-29 13:38:15 -04:00
bool bBundleNeedsUpdate = false ;
2019-10-31 14:19:21 -04:00
float TotalWeight = 0.0f ;
for ( const FName & Bundle : BundlesToTrack )
2019-07-09 02:14:11 -04:00
{
2019-11-07 15:35:09 -05:00
const FInstallBundleContentState * BundleState = BundleContentState . IndividualBundleStates . Find ( Bundle ) ;
if ( ensureAlwaysMsgf ( BundleState , TEXT ( " Trying to track unknown bundle %s " ) , * Bundle . ToString ( ) ) )
2019-10-31 14:19:21 -04:00
{
2023-05-18 21:07:41 -04:00
//Filter out any bundles with effectively 0 weight (unless all bundles are 0 weight)
if ( ! bAreAllBundlesZeroWeight & & ( BundleState - > Weight < = SMALL_NUMBER ) )
2023-04-22 01:07:57 -04:00
{
continue ;
}
2019-10-31 14:19:21 -04:00
//Track if we need any kind of bundle updates
2020-06-23 18:40:00 -04:00
if ( BundleState - > State = = EInstallBundleInstallState : : NotInstalled | | BundleState - > State = = EInstallBundleInstallState : : NeedsUpdate )
2019-10-31 14:19:21 -04:00
{
bBundleNeedsUpdate = true ;
}
//Save required bundles and their weights
RequiredBundleNames . Add ( Bundle ) ;
2023-05-18 21:07:41 -04:00
//If all bundles are zero weight, just treat this weight as 1 so everything ends up with 1 weight and is evenly distributed
CachedBundleWeights . FindOrAdd ( Bundle ) = bAreAllBundlesZeroWeight ? 1.0f : BundleState - > Weight ;
2019-11-07 15:35:09 -05:00
TotalWeight + = BundleState - > Weight ;
2019-10-31 14:19:21 -04:00
}
2019-07-09 02:14:11 -04:00
}
2019-10-31 14:19:21 -04:00
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . bBundleRequiresUpdate = bBundleNeedsUpdate ;
2021-05-12 18:10:03 -04:00
2019-10-31 14:19:21 -04:00
if ( TotalWeight > 0.0f )
2019-07-09 02:14:11 -04:00
{
2019-10-31 14:19:21 -04:00
for ( TPair < FName , float > & BundleWeightPair : CachedBundleWeights )
{
BundleWeightPair . Value / = TotalWeight ;
}
}
// If no bundles to track, we are done
if ( RequiredBundleNames . Num ( ) = = 0 )
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . ProgressPercent = 1.0f ;
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Finished ;
2019-07-09 02:14:11 -04:00
}
2021-05-12 18:10:03 -04:00
2019-07-09 02:14:11 -04:00
//Go ahead and calculate initial values from the Bundle Cache
UpdateBundleCache ( ) ;
}
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : UpdateBundleCache ( )
2019-07-09 02:14:11 -04:00
{
//if we haven't already set this up, lets try to set it now
if ( nullptr = = InstallBundleManager )
{
2019-08-08 00:48:21 -04:00
InstallBundleManager = IInstallBundleManager : : GetPlatformInstallBundleManager ( ) ;
2019-07-09 02:14:11 -04:00
}
2020-10-29 13:38:15 -04:00
TSharedPtr < IInstallBundleManager > PinnedBundleManger = InstallBundleManager . Pin ( ) ;
if ( ensureAlwaysMsgf ( ( nullptr ! = PinnedBundleManger ) , TEXT ( " Invalid InstallBundleManager during UpdateBundleCache! Needs to be valid during run! " ) ) )
2019-07-09 02:14:11 -04:00
{
for ( FName & BundleName : RequiredBundleNames )
{
2020-10-29 13:38:15 -04:00
TOptional < FInstallBundleProgress > BundleProgress = PinnedBundleManger - > GetBundleProgress ( BundleName ) ;
2019-07-09 02:14:11 -04:00
//Copy progress to the cache as long as we have progress to copy.
if ( BundleProgress . IsSet ( ) )
{
2019-12-09 13:52:45 -05:00
BundleStatusCache . Add ( BundleName , BundleProgress . GetValue ( ) ) ;
2019-07-09 02:14:11 -04:00
}
}
}
}
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : UpdateCombinedStatus ( )
2019-07-09 02:14:11 -04:00
{
2019-10-31 14:19:21 -04:00
if ( RequiredBundleNames . Num ( ) = = 0 )
return ;
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . ProgressPercent = GetCombinedProgressPercent ( ) ;
2019-07-09 02:14:11 -04:00
EInstallBundleStatus EarliestBundleState = EInstallBundleStatus : : Count ;
2019-08-22 22:50:45 -04:00
EInstallBundlePauseFlags CombinedPauseFlags = EInstallBundlePauseFlags : : None ;
2019-07-09 02:14:11 -04:00
bool bIsAnythingPaused = false ;
2019-07-17 10:18:29 -04:00
bool bIsAnythingFinishing = false ;
2019-07-09 02:14:11 -04:00
2019-07-15 21:03:47 -04:00
//if we don't yet have a bundle status cache entry for a particular requirement
//then we can't yet tell what work is required on that bundle yet. We need to go ahead and make sure we don't
//show a status like "Installed" before we know what state that bundle is in. Make sure we show at LEAST
2023-04-22 01:07:57 -04:00
//updating in that case, so start with Downloading since that is the first Updating case.
//However if all bundle progress is finished, don't just sit showing 100% and Updating when we could potentially
//be showing Finishing progress
2019-07-15 21:03:47 -04:00
if ( ( BundleStatusCache . Num ( ) < RequiredBundleNames . Num ( ) )
2023-04-22 01:07:57 -04:00
& & ( BundleStatusCache . Num ( ) > 0 )
& & ( CurrentCombinedProgress . ProgressPercent < 1.0f ) )
2019-07-15 21:03:47 -04:00
{
2019-12-12 15:05:38 -05:00
EarliestBundleState = EInstallBundleStatus : : Updating ;
2019-07-15 21:03:47 -04:00
}
2019-07-17 10:18:29 -04:00
float EarliestFinishingPercent = 1.0f ;
2019-12-12 15:05:38 -05:00
for ( const TPair < FName , FInstallBundleProgress > & BundlePair : BundleStatusCache )
2019-07-09 02:14:11 -04:00
{
if ( BundlePair . Value . Status < EarliestBundleState )
{
EarliestBundleState = BundlePair . Value . Status ;
}
2019-07-17 10:18:29 -04:00
if ( ! bIsAnythingFinishing & & BundlePair . Value . Status = = EInstallBundleStatus : : Finishing )
{
EarliestFinishingPercent = BundlePair . Value . Finishing_Percent ;
bIsAnythingFinishing = true ;
}
2019-07-09 02:14:11 -04:00
2019-07-17 10:18:29 -04:00
bIsAnythingPaused = bIsAnythingPaused | | BundlePair . Value . PauseFlags ! = EInstallBundlePauseFlags : : None ;
2019-08-22 22:50:45 -04:00
CombinedPauseFlags | = BundlePair . Value . PauseFlags ;
2019-07-09 02:14:11 -04:00
}
//if we have any paused bundles, and we have any bundle that isn't finished installed, we are Paused
//if everything is installed ignore the pause flags as we completed after pausing the bundles
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . bIsPaused = ( bIsAnythingPaused & & ( EarliestBundleState < EInstallBundleStatus : : Ready ) ) ;
if ( CurrentCombinedProgress . bIsPaused )
2019-08-22 22:50:45 -04:00
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedPauseFlags = CombinedPauseFlags ;
2019-08-22 22:50:45 -04:00
}
else
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedPauseFlags = EInstallBundlePauseFlags : : None ;
2019-08-22 22:50:45 -04:00
}
2019-07-09 02:14:11 -04:00
2019-07-15 21:03:47 -04:00
//if the bundle does not need an update, all the phases we go through don't support pausing (Mounting ,Compiling Shaders, etc)
//Otherwise start with True and override those specific cases bellow
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . bDoesCurrentStateSupportPausing = CurrentCombinedProgress . bBundleRequiresUpdate ;
2019-07-09 02:14:11 -04:00
2019-07-23 11:02:18 -04:00
if ( ( EarliestBundleState = = EInstallBundleStatus : : Requested ) | | ( EarliestBundleState = = EInstallBundleStatus : : Count ) )
2019-07-09 02:14:11 -04:00
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Initializing ;
2019-07-09 02:14:11 -04:00
}
2019-12-12 15:05:38 -05:00
else if ( EarliestBundleState < = EInstallBundleStatus : : Updating )
2019-07-09 02:14:11 -04:00
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Updating ;
2019-07-09 02:14:11 -04:00
}
2019-07-17 10:18:29 -04:00
else if ( EarliestBundleState < = EInstallBundleStatus : : Finishing )
2019-07-09 02:14:11 -04:00
{
2019-07-20 02:33:10 -04:00
//Handles the case where one of our Bundles was finishing and we have finished everything else.
//Now just shows our earliest bundle that is finishing.
2019-07-17 10:18:29 -04:00
if ( bIsAnythingFinishing )
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Finishing ;
CurrentCombinedProgress . ProgressPercent = EarliestFinishingPercent ;
2019-07-17 10:18:29 -04:00
}
else
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Updating ;
2019-07-17 10:18:29 -04:00
}
2019-07-09 02:14:11 -04:00
}
2019-12-12 15:05:38 -05:00
else if ( EarliestBundleState = = EInstallBundleStatus : : Ready )
2019-07-09 02:14:11 -04:00
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Finished ;
CurrentCombinedProgress . bDoesCurrentStateSupportPausing = false ;
2019-07-09 02:14:11 -04:00
}
else
{
2020-10-29 13:38:15 -04:00
CurrentCombinedProgress . CombinedStatus = ECombinedBundleStatus : : Unknown ;
2019-07-09 02:14:11 -04:00
}
}
2020-10-29 13:38:15 -04:00
float FInstallBundleCombinedProgressTracker : : GetCombinedProgressPercent ( ) const
2019-07-09 02:14:11 -04:00
{
float AllBundleProgressPercent = 0.f ;
ensureAlwaysMsgf ( ( CachedBundleWeights . Num ( ) > = BundleStatusCache . Num ( ) ) , TEXT ( " Missing Cache Entries for BundleWeights!Any missing bundles will have 0 for their progress! " ) ) ;
2019-12-12 15:05:38 -05:00
for ( const TPair < FName , FInstallBundleProgress > & BundleStatusPair : BundleStatusCache )
2019-07-09 02:14:11 -04:00
{
2019-10-31 14:19:21 -04:00
const float * FoundWeight = CachedBundleWeights . Find ( BundleStatusPair . Key ) ;
2019-07-09 02:14:11 -04:00
if ( ensureAlwaysMsgf ( ( nullptr ! = FoundWeight ) , TEXT ( " Found missing entry for BundleWeight! Bundle %s does not have a weight entry! " ) , * ( BundleStatusPair . Key . ToString ( ) ) ) )
{
2019-12-12 14:46:31 -05:00
AllBundleProgressPercent + = ( ( * FoundWeight ) * BundleStatusPair . Value . Install_Percent ) ;
2019-07-09 02:14:11 -04:00
}
}
2020-10-22 19:19:16 -04:00
return FMath : : Clamp ( AllBundleProgressPercent , 0.f , 1.0f ) ;
2019-07-09 02:14:11 -04:00
}
2020-10-29 13:38:15 -04:00
bool FInstallBundleCombinedProgressTracker : : Tick ( float dt )
2019-07-09 02:14:11 -04:00
{
2020-09-01 14:07:48 -04:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FBundlePrereqCombinedStatusHelper_Tick ) ;
2019-07-09 02:14:11 -04:00
UpdateBundleCache ( ) ;
UpdateCombinedStatus ( ) ;
2023-11-02 19:26:08 -04:00
if ( OnTick )
{
OnTick ( CurrentCombinedProgress ) ;
}
2019-07-09 02:14:11 -04:00
//just always keep ticking
return true ;
}
2020-10-29 13:38:15 -04:00
const FInstallBundleCombinedProgressTracker : : FCombinedProgress & FInstallBundleCombinedProgressTracker : : GetCurrentCombinedProgress ( ) const
2019-07-09 02:14:11 -04:00
{
2020-10-29 13:38:15 -04:00
return CurrentCombinedProgress ;
2019-07-09 02:14:11 -04:00
}
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : OnBundleInstallComplete ( FInstallBundleRequestResultInfo CompletedBundleInfo )
2019-07-09 02:14:11 -04:00
{
const FName CompletedBundleName = CompletedBundleInfo . BundleName ;
const bool bBundleCompletedSuccessfully = ( CompletedBundleInfo . Result = = EInstallBundleResult : : OK ) ;
2019-07-10 16:52:53 -04:00
const bool bWasRequiredBundle = RequiredBundleNames . Contains ( CompletedBundleInfo . BundleName ) ;
2019-07-09 02:14:11 -04:00
2019-07-10 16:52:53 -04:00
if ( bBundleCompletedSuccessfully & & bWasRequiredBundle )
2019-07-09 02:14:11 -04:00
{
//Make sure our BundleCache shows this as finished all the way
2019-12-12 15:05:38 -05:00
FInstallBundleProgress & BundleCacheInfo = BundleStatusCache . FindOrAdd ( CompletedBundleName ) ;
BundleCacheInfo . Status = EInstallBundleStatus : : Ready ;
2019-07-09 02:14:11 -04:00
2020-10-29 13:38:15 -04:00
TSharedPtr < IInstallBundleManager > PinnedBundleManger = InstallBundleManager . Pin ( ) ;
if ( ensureAlwaysMsgf ( ( nullptr ! = PinnedBundleManger ) , TEXT ( " Invalid InstallBundleManager during OnBundleInstallComplete! Needs to be valid during run! " ) ) )
2019-07-09 02:14:11 -04:00
{
2020-10-29 13:38:15 -04:00
TOptional < FInstallBundleProgress > BundleProgress = PinnedBundleManger - > GetBundleProgress ( CompletedBundleName ) ;
if ( ensureAlwaysMsgf ( BundleProgress . IsSet ( ) , TEXT ( " Expected to find BundleProgress for completed bundle, but did not. Leaving old progress values " ) ) )
{
BundleCacheInfo = BundleProgress . GetValue ( ) ;
}
2019-07-09 02:14:11 -04:00
}
}
}
2019-08-22 22:50:45 -04:00
2019-12-09 13:52:45 -05:00
// It's not really necessary to have this, but it allows for a fallback if GetBundleProgress() returns null.
// Normally that shouldn't happen, but right now its handy while I refactor bundle progress.
2020-10-29 13:38:15 -04:00
void FInstallBundleCombinedProgressTracker : : OnBundleInstallPauseChanged ( FInstallBundlePauseInfo PauseInfo )
2019-12-09 13:52:45 -05:00
{
const bool bWasRequiredBundle = RequiredBundleNames . Contains ( PauseInfo . BundleName ) ;
if ( bWasRequiredBundle )
{
2019-12-12 15:05:38 -05:00
FInstallBundleProgress & BundleCacheInfo = BundleStatusCache . FindOrAdd ( PauseInfo . BundleName ) ;
2019-12-09 13:52:45 -05:00
BundleCacheInfo . PauseFlags = PauseInfo . PauseFlags ;
}
}