2021-05-17 07:48:16 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-12-02 06:21:36 -05:00
# include "VirtualizationSourceControlBackend.h"
2021-05-17 07:48:16 -04:00
2022-01-18 04:50:38 -05:00
# include "Containers/Ticker.h"
2022-11-03 14:18:47 -04:00
# include "HAL/Event.h"
2021-10-25 20:05:28 -04:00
# include "HAL/FileManager.h"
2022-02-02 02:21:24 -05:00
# include "IO/IoHash.h"
2021-05-17 07:48:16 -04:00
# include "ISourceControlModule.h"
# include "ISourceControlProvider.h"
2023-05-11 06:35:58 -04:00
# include "Interfaces/IPluginManager.h"
2022-01-18 04:50:38 -05:00
# include "Logging/MessageLog.h"
2023-06-21 11:53:01 -04:00
# include "Misc/CommandLine.h"
2022-03-01 09:05:41 -05:00
# include "Misc/FileHelper.h"
2021-05-17 07:48:16 -04:00
# include "Misc/Parse.h"
2022-03-01 09:05:41 -05:00
# include "Misc/PathViews.h"
2021-10-25 20:05:28 -04:00
# include "Misc/Paths.h"
# include "Misc/ScopeExit.h"
2023-06-20 04:26:06 -04:00
# include "SVirtualizationRevisionControlConnectionDialog.h"
# include "SourceControlHelpers.h"
2022-03-09 07:43:59 -05:00
# include "SourceControlInitSettings.h"
2021-05-17 07:48:16 -04:00
# include "SourceControlOperations.h"
2023-05-11 06:35:58 -04:00
# include "VirtualizationManager.h"
2021-05-17 07:48:16 -04:00
# include "VirtualizationSourceControlUtilities.h"
# include "VirtualizationUtilities.h"
// When the SourceControl module (or at least the perforce source control module) is thread safe we
// can enable this and stop using the hacky work around 'TryToDownloadFileFromBackgroundThread'
# define IS_SOURCE_CONTROL_THREAD_SAFE 0
2022-01-18 04:50:38 -05:00
# define LOCTEXT_NAMESPACE "Virtualization"
2021-05-17 07:48:16 -04:00
namespace UE : : Virtualization
{
2022-05-03 09:39:48 -04:00
/**
2022-07-22 11:01:51 -04:00
* A quick and dirty implementation of a std : : counting_semaphore that we can use to
2022-05-03 09:39:48 -04:00
* limit the number of threads that can create a new perforce connection when pulling or
* pushing payloads .
*
* In the worst case scenario where a user needs to pull all of their payloads from the source
* control backend rather than a faster backend we need to make sure that they will not overwhelm
* their server with requests .
* In the future we can use this sort of limit to help gather requests from many threads into a
* single batch request from the server which will be much more efficient than the current ' one
* payload , one request ' system . Although we might want to consider gathering multiple requests
* at a higher level so that all backends can work on the same batching principle .
*/
class FSemaphore
{
public :
UE_NONCOPYABLE ( FSemaphore ) ;
enum class EAcquireResult
{
/** The acquire was a success and the thread can continue */
Success ,
/** The wait event failed, the semaphore object is no longer safe */
EventFailed
} ;
2023-01-19 07:58:12 -05:00
enum class EFlags : uint32
{
None = 0 ,
PrioritizeGameThread = 1 < < 0
} ;
FRIEND_ENUM_CLASS_FLAGS ( EFlags ) ;
2022-05-03 09:39:48 -04:00
FSemaphore ( ) = delete ;
2023-01-19 07:58:12 -05:00
explicit FSemaphore ( int32 InitialCount , EFlags Options )
2022-05-03 09:39:48 -04:00
: WaitEvent ( EEventMode : : ManualReset )
, Counter ( InitialCount )
, DebugCount ( 0 )
{
2023-01-19 07:58:12 -05:00
bPrioritizeGameThread = EnumHasAnyFlags ( Options , EFlags : : PrioritizeGameThread ) ;
2022-05-03 09:39:48 -04:00
}
~ FSemaphore ( )
{
checkf ( DebugCount = = 0 , TEXT ( " '%d' threads are still waiting on the UE::Virtualization::FSemaphore being destroyed " ) ) ;
}
/** Will block until the calling thread can pass through the semaphore. Note that it might return an error if the WaitEvent fails */
EAcquireResult Acquire ( )
{
2023-01-19 07:58:12 -05:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FSemaphore : : Acquire ) ;
2022-05-03 09:39:48 -04:00
CriticalSection . Lock ( ) ;
DebugCount + + ;
2023-01-19 07:58:12 -05:00
if ( bPrioritizeGameThread & & IsInGameThread ( ) )
{
return AcquireFromGameThread ( ) ;
}
2022-05-03 09:39:48 -04:00
while ( Counter - - < = 0 )
{
Counter + + ;
WaitEvent - > Reset ( ) ;
CriticalSection . Unlock ( ) ;
if ( ! WaitEvent - > Wait ( ) )
{
- - DebugCount ;
return EAcquireResult : : EventFailed ;
}
CriticalSection . Lock ( ) ;
}
CriticalSection . Unlock ( ) ;
return EAcquireResult : : Success ;
}
void Release ( )
{
FScopeLock _ ( & CriticalSection ) ;
Counter + + ;
WaitEvent - > Trigger ( ) ;
- - DebugCount ;
}
private :
2023-01-19 07:58:12 -05:00
inline EAcquireResult AcquireFromGameThread ( )
{
if ( Counter - - > 0 )
{
CriticalSection . Unlock ( ) ;
}
else
{
WaitEvent - > Reset ( ) ;
CriticalSection . Unlock ( ) ;
if ( ! WaitEvent - > Wait ( ) )
{
- - DebugCount ;
return EAcquireResult : : EventFailed ;
}
}
return EAcquireResult : : Success ;
}
2022-05-03 09:39:48 -04:00
FEventRef WaitEvent ;
FCriticalSection CriticalSection ;
std : : atomic < int32 > Counter ;
std : : atomic < int32 > DebugCount ;
2023-01-19 07:58:12 -05:00
bool bPrioritizeGameThread ;
2022-05-03 09:39:48 -04:00
} ;
2023-01-19 07:58:12 -05:00
ENUM_CLASS_FLAGS ( FSemaphore : : EFlags ) ;
2022-05-03 09:39:48 -04:00
/** Structure to make it easy to acquire/release a FSemaphore for a given scope */
struct FSemaphoreScopeLock
{
FSemaphoreScopeLock ( FSemaphore * InSemaphore )
: Semaphore ( InSemaphore )
{
if ( Semaphore ! = nullptr )
{
Semaphore - > Acquire ( ) ;
}
}
~ FSemaphoreScopeLock ( )
{
if ( Semaphore ! = nullptr )
{
Semaphore - > Release ( ) ;
}
}
private :
FSemaphore * Semaphore ;
} ;
2023-03-13 11:10:53 -04:00
/**
* This utility can be used to try and find the P4PORT ( server address ) that the provider
* is using . The source control api doesn ' t actually have a way to do this at the moment ,
* but we can get a general status text of the connection from which we can attempt to
* parse the port .
*/
[[nodiscard]] FString GetPortFromProvider ( ISourceControlProvider * SCCProvider )
{
if ( SCCProvider ! = nullptr )
{
const FString Status = SCCProvider - > GetStatusText ( ) . ToString ( ) ;
FString Port ;
if ( FParse : : Value ( * Status , TEXT ( " Port: " ) , Port ) )
{
return Port ;
}
}
return FString ( TEXT ( " Unknown " ) ) ;
}
2022-03-01 09:05:41 -05:00
/** Utility function to create a directory to submit payloads from. */
2023-02-24 09:58:54 -05:00
[[nodiscard]] static bool TryCreateSubmissionSessionDirectory ( FStringView SessionDirectoryPath , FStringView IgnoreFileName )
2022-03-01 09:05:41 -05:00
{
// Write out an ignore file to the submission directory (will create the directory if needed)
{
TStringBuilder < 260 > IgnoreFilePath ;
2023-02-24 09:58:54 -05:00
FPathViews : : Append ( IgnoreFilePath , SessionDirectoryPath , IgnoreFileName ) ;
2022-03-01 09:05:41 -05:00
// A very basic .p4ignore file that should make sure that we are only submitting valid .upayload files.
//
// Since the file should only exist while we are pushing payloads, it is not expected that anyone will need
// to read the file. Due to this we only include the bare essentials in terms of documentation.
TStringBuilder < 512 > FileContents ;
FileContents < < TEXT ( " # Ignore all files \n * \n \n " ) ;
FileContents < < TEXT ( " # Allow.payload files as long as they are the expected 3 directories deep \n !*/*/*/*.upayload \n \n " ) ;
if ( ! FFileHelper : : SaveStringToFile ( FileContents , IgnoreFilePath . ToString ( ) ) )
{
return false ;
}
}
return true ;
}
2021-11-18 14:37:34 -05:00
/** Builds a changelist description to be used when submitting a payload to source control */
2022-08-16 15:31:11 -04:00
static void CreateDescription ( const FString & ProjectName , TArrayView < const FPushRequest * > & FileRequests , TStringBuilder < 512 > & OutDescription )
2021-10-25 20:05:28 -04:00
{
// TODO: Maybe make writing out the project name an option or allow for a codename to be set via ini file?
2021-11-18 14:37:34 -05:00
OutDescription < < TEXT ( " Submitted for project: " ) ;
2022-03-08 11:22:45 -05:00
OutDescription < < ProjectName ;
2021-10-25 20:05:28 -04:00
2021-12-08 02:19:42 -05:00
bool bInitialNewline = false ;
for ( const FPushRequest * Request : FileRequests )
2021-11-18 14:37:34 -05:00
{
2022-05-04 08:58:50 -04:00
if ( ! Request - > GetContext ( ) . IsEmpty ( ) )
2021-12-08 02:19:42 -05:00
{
if ( ! bInitialNewline )
{
OutDescription < < TEXT ( " \n " ) ;
bInitialNewline = true ;
}
2022-05-04 08:58:50 -04:00
OutDescription < < TEXT ( " \n " ) < < Request - > GetIdentifier ( ) < < " \t : " < < Request - > GetContext ( ) ;
2021-12-08 02:19:42 -05:00
}
2021-11-18 14:37:34 -05:00
}
2021-10-25 20:05:28 -04:00
}
2022-03-14 12:03:48 -04:00
[[nodiscard]] static ECommandResult : : Type GetDepotPathStates ( ISourceControlProvider & SCCProvider , const TArray < FString > & DepotPaths , TArray < FSourceControlStateRef > & OutStates )
{
TSharedRef < FUpdateStatus , ESPMode : : ThreadSafe > UpdateOperation = ISourceControlOperation : : Create < FUpdateStatus > ( ) ;
UpdateOperation - > SetRequireDirPathEndWithSeparator ( true ) ;
ECommandResult : : Type Result = SCCProvider . Execute ( UpdateOperation , DepotPaths ) ;
if ( Result ! = ECommandResult : : Succeeded )
{
return Result ;
}
return SCCProvider . GetState ( DepotPaths , OutStates , EStateCacheUsage : : Use ) ;
}
2022-11-01 15:10:19 -04:00
/**
* If the only reason we failed an operation is because of missing depot files then there
* is no point retrying that operation however other kinds of errors , such as connection
* problems , might be not be encountered if we try again .
*
* @ param ResultInfo The results of a failed operation
* @ return True if there were no errors ( as we don ' t really know what went wrong )
* or if there errors not relating to missing files , otherwise false .
*/
[[nodiscard]] static bool ShouldRetryOperation ( const FSourceControlResultInfo & ResultInfo )
2022-04-14 05:32:56 -04:00
{
2022-11-01 15:10:19 -04:00
if ( ResultInfo . ErrorMessages . IsEmpty ( ) )
{
return true ;
}
2022-04-14 05:32:56 -04:00
// Ideally we'd parse for this sort of thing in the source control module itself and return an error enum
for ( const FText & ErrorTest : ResultInfo . ErrorMessages )
{
2022-11-01 15:10:19 -04:00
if ( ErrorTest . ToString ( ) . Find ( " - no such file(s). " ) = = INDEX_NONE )
2022-04-14 05:32:56 -04:00
{
return true ;
}
}
return false ;
}
2022-03-08 11:22:45 -05:00
FSourceControlBackend : : FSourceControlBackend ( FStringView ProjectName , FStringView ConfigName , FStringView InDebugName )
2022-04-21 03:15:13 -04:00
: IVirtualizationBackend ( ConfigName , InDebugName , EOperations : : Push | EOperations : : Pull )
2022-03-08 11:22:45 -05:00
, ProjectName ( ProjectName )
2021-05-17 07:48:16 -04:00
{
2021-12-02 06:21:36 -05:00
}
bool FSourceControlBackend : : Initialize ( const FString & ConfigEntry )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : Initialize ) ;
2022-04-14 05:32:56 -04:00
if ( ! TryApplySettingsFromConfigFiles ( ConfigEntry ) )
2022-03-01 09:05:41 -05:00
{
return false ;
}
Add a number of ways for the VA backend connections to be changed to lazy initialize on first use.
#rb Devin.Doucette
#jira UE-161599
#rnx
#preflight 6303c8d65a5d4e4624e7bf52
- There are some use cases that require the VA system to be initialized and configured correctly but would prefer that the backend connections only run if absolutely needed (usually when a payload is pulled or pushed for the first time), this change provides four different ways of doing this:
-- Setting [Core.VirtualizationModule]LazyInitConnections=true in the Engine ini file
-- Setting the define 'UE_VIRTUALIZATION_CONNECTION_LAZY_INIT' to 1 in a programs .target.cs
-- Running with the commandline option -VA-LazyInitConnections
-- Setting the cvar 'VA.LazyInitConnections' to 1 (only works if it is set before the VA system is initialized, changing it mid editor via the console does nothing)
--- Note that after the config file, each setting there only opts into lazy initializing the connections, setting the cvar to 0 for example will not prevent the cmdline from opting in etc.
- In the future we will allow the connection code to run async, so the latency can be hidden behind the editor loading, but for the current use case we are taking the minimal approach.
-- This means we only support the backend being in 3 states. No connection has been made yet, the connection is broken and the connection is working.
-- To keep things simple we only record if we have attempted to connect the backends or not. We don't check individual backends nor do we try to reconnect failed ones etc. This is all scheduled for a future work item.
- If the connections are not initialized when the VA system is, we wait until the first time someone calls one of the virtualization methods that will actually use a connection: Push/Pull/Query
-- We try connecting all of the backends at once, even if they won't be used in the call to keep things simple.
- Only the source control backend makes use of the connection system. The horde storage (http) backend could take advantage too, but it is currently unused and most likely going to just be deleted so there seemed little point updating it.
- If we try to run an operation on an unconnected backend we only log to verbose. This is to maintain existing behaviour where a failed backend would not be mounted at all. This logging will likely be revisited in a future work item.
[CL 21511855 by paul chipchase in ue5-main branch]
2022-08-23 13:01:15 -04:00
return true ;
}
IVirtualizationBackend : : EConnectionStatus FSourceControlBackend : : OnConnect ( )
{
2022-08-29 14:44:54 -04:00
// First check that the 'PerforceSourceControl' plugin exists and is enabled so that we can give specific
// error messages if not.
// We could try to force enable the plugin at this point, in the same way that the stand alone tool does
// but it is probably better to inform the user and have them add/enable the plugin for their target explicitly
// rather than have us do it behind the scenes.
// This is only expected to be a problem when first enabling VA for a project/target and not something a user
// will experience day to day.
TSharedPtr < IPlugin > P4Plugin = IPluginManager : : Get ( ) . FindPlugin ( " PerforceSourceControl " ) ;
if ( ! P4Plugin )
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to find the 'PerforceSourceControl' plugin " ) , * GetDebugName ( ) ) ;
return IVirtualizationBackend : : EConnectionStatus : : Error ;
}
if ( ! P4Plugin - > IsEnabled ( ) )
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] The 'PerforceSourceControl' plugin is disabled for this target " ) , * GetDebugName ( ) ) ;
return IVirtualizationBackend : : EConnectionStatus : : Error ;
}
2023-06-20 04:26:06 -04:00
FString Port = ServerAddress ;
FString UserName = TEXT ( " " ) ;
bool bSaveSettings = false ; // Initially we will try to reason the perforce settings from the ini file and global environment
// so we don't want to save these values to the users ini files.
while ( true )
{
// TODO: At the moment we cannot get back error messages from ISourceControlProvider::Init, nor can we get back info about
// the actual login details used, so for the initial dialog we will just have to go with the settings we know and not
// give the user any details about the connection problem.
FText ErrorMessage ;
IVirtualizationBackend : : EConnectionStatus ConnectionResult = OnConnectInternal ( Port , UserName , bSaveSettings , ErrorMessage ) ;
if ( ConnectionResult = = IVirtualizationBackend : : EConnectionStatus : : Connected )
{
if ( bSaveSettings )
{
check ( : : IsInGameThread ( ) ) ;
GConfig - > Flush ( false , SourceControlHelpers : : GetSettingsIni ( ) ) ;
}
return IVirtualizationBackend : : EConnectionStatus : : Connected ;
}
# if UE_VA_WITH_SLATE
// If the local ini settings are ignored then there is no point saving correct settings given by the user.
// They will need to fix their root problem instead.
// TODO: Maybe give a bespoke error at this point?
if ( bUseRetryConnectionDialog & & bUseLocalIniFileSettings )
{
SRevisionControlConnectionDialog : : FResult DialogResult = SRevisionControlConnectionDialog : : RunDialog ( Port , UserName ) ;
if ( ! DialogResult . bShouldRetry )
{
OnConnectionError ( ErrorMessage ) ;
return IVirtualizationBackend : : EConnectionStatus : : Error ;
}
Port = DialogResult . Port ;
UserName = DialogResult . UserName ;
bSaveSettings = true ; // Now we have input from the user, so we should save it for future sessions
}
else
# endif
{
OnConnectionError ( ErrorMessage ) ;
return IVirtualizationBackend : : EConnectionStatus : : Error ;
}
}
}
IVirtualizationBackend : : EConnectionStatus FSourceControlBackend : : OnConnectInternal ( FStringView Port , FStringView Username , bool bSaveConnectionSettings , FText & OutErrorMessage )
{
2022-03-09 07:43:59 -05:00
// We do not want the connection to have a client workspace so explicitly set it to empty
FSourceControlInitSettings SCCSettings ( FSourceControlInitSettings : : EBehavior : : OverrideExisting ) ;
2023-03-15 10:17:32 -04:00
2023-06-20 04:26:06 -04:00
FSourceControlInitSettings : : EConfigBehavior IniBehavior = bSaveConnectionSettings ? FSourceControlInitSettings : : EConfigBehavior : : ReadWrite
: FSourceControlInitSettings : : EConfigBehavior : : ReadOnly ;
if ( ! bUseLocalIniFileSettings )
{
IniBehavior = FSourceControlInitSettings : : EConfigBehavior : : None ;
}
2023-03-15 10:17:32 -04:00
SCCSettings . SetConfigBehavior ( IniBehavior ) ;
2022-09-23 20:05:59 -04:00
2023-06-20 04:26:06 -04:00
if ( ! Port . IsEmpty ( ) )
2022-09-21 16:29:44 -04:00
{
2023-06-20 04:26:06 -04:00
SCCSettings . AddSetting ( TEXT ( " P4Port " ) , Port ) ;
2022-09-21 16:29:44 -04:00
}
2023-06-20 04:26:06 -04:00
if ( ! Username . IsEmpty ( ) )
{
SCCSettings . AddSetting ( TEXT ( " P4User " ) , Username ) ;
}
2022-03-09 07:43:59 -05:00
SCCSettings . AddSetting ( TEXT ( " P4Client " ) , TEXT ( " " ) ) ;
2021-12-02 06:21:36 -05:00
2022-03-09 07:43:59 -05:00
SCCProvider = ISourceControlModule : : Get ( ) . CreateProvider ( FName ( " Perforce " ) , TEXT ( " Virtualization " ) , SCCSettings ) ;
if ( ! SCCProvider . IsValid ( ) )
2021-05-17 07:48:16 -04:00
{
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to create a perforce revision control provider " ) , * GetDebugName ( ) ) ;
Add a number of ways for the VA backend connections to be changed to lazy initialize on first use.
#rb Devin.Doucette
#jira UE-161599
#rnx
#preflight 6303c8d65a5d4e4624e7bf52
- There are some use cases that require the VA system to be initialized and configured correctly but would prefer that the backend connections only run if absolutely needed (usually when a payload is pulled or pushed for the first time), this change provides four different ways of doing this:
-- Setting [Core.VirtualizationModule]LazyInitConnections=true in the Engine ini file
-- Setting the define 'UE_VIRTUALIZATION_CONNECTION_LAZY_INIT' to 1 in a programs .target.cs
-- Running with the commandline option -VA-LazyInitConnections
-- Setting the cvar 'VA.LazyInitConnections' to 1 (only works if it is set before the VA system is initialized, changing it mid editor via the console does nothing)
--- Note that after the config file, each setting there only opts into lazy initializing the connections, setting the cvar to 0 for example will not prevent the cmdline from opting in etc.
- In the future we will allow the connection code to run async, so the latency can be hidden behind the editor loading, but for the current use case we are taking the minimal approach.
-- This means we only support the backend being in 3 states. No connection has been made yet, the connection is broken and the connection is working.
-- To keep things simple we only record if we have attempted to connect the backends or not. We don't check individual backends nor do we try to reconnect failed ones etc. This is all scheduled for a future work item.
- If the connections are not initialized when the VA system is, we wait until the first time someone calls one of the virtualization methods that will actually use a connection: Push/Pull/Query
-- We try connecting all of the backends at once, even if they won't be used in the call to keep things simple.
- Only the source control backend makes use of the connection system. The horde storage (http) backend could take advantage too, but it is currently unused and most likely going to just be deleted so there seemed little point updating it.
- If we try to run an operation on an unconnected backend we only log to verbose. This is to maintain existing behaviour where a failed backend would not be mounted at all. This logging will likely be revisited in a future work item.
[CL 21511855 by paul chipchase in ue5-main branch]
2022-08-23 13:01:15 -04:00
return IVirtualizationBackend : : EConnectionStatus : : Error ;
2021-12-02 06:21:36 -05:00
}
2021-05-17 07:48:16 -04:00
2023-03-13 11:10:53 -04:00
// Will attempt to make initial connection to the server, it doesn't return any error values we can check against but
// it will output problems to the log file.
2023-06-20 04:26:06 -04:00
const bool bForceConnection = true ;
SCCProvider - > Init ( bForceConnection ) ;
Add a number of ways for the VA backend connections to be changed to lazy initialize on first use.
#rb Devin.Doucette
#jira UE-161599
#rnx
#preflight 6303c8d65a5d4e4624e7bf52
- There are some use cases that require the VA system to be initialized and configured correctly but would prefer that the backend connections only run if absolutely needed (usually when a payload is pulled or pushed for the first time), this change provides four different ways of doing this:
-- Setting [Core.VirtualizationModule]LazyInitConnections=true in the Engine ini file
-- Setting the define 'UE_VIRTUALIZATION_CONNECTION_LAZY_INIT' to 1 in a programs .target.cs
-- Running with the commandline option -VA-LazyInitConnections
-- Setting the cvar 'VA.LazyInitConnections' to 1 (only works if it is set before the VA system is initialized, changing it mid editor via the console does nothing)
--- Note that after the config file, each setting there only opts into lazy initializing the connections, setting the cvar to 0 for example will not prevent the cmdline from opting in etc.
- In the future we will allow the connection code to run async, so the latency can be hidden behind the editor loading, but for the current use case we are taking the minimal approach.
-- This means we only support the backend being in 3 states. No connection has been made yet, the connection is broken and the connection is working.
-- To keep things simple we only record if we have attempted to connect the backends or not. We don't check individual backends nor do we try to reconnect failed ones etc. This is all scheduled for a future work item.
- If the connections are not initialized when the VA system is, we wait until the first time someone calls one of the virtualization methods that will actually use a connection: Push/Pull/Query
-- We try connecting all of the backends at once, even if they won't be used in the call to keep things simple.
- Only the source control backend makes use of the connection system. The horde storage (http) backend could take advantage too, but it is currently unused and most likely going to just be deleted so there seemed little point updating it.
- If we try to run an operation on an unconnected backend we only log to verbose. This is to maintain existing behaviour where a failed backend would not be mounted at all. This logging will likely be revisited in a future work item.
[CL 21511855 by paul chipchase in ue5-main branch]
2022-08-23 13:01:15 -04:00
2023-03-13 11:10:53 -04:00
if ( ! SCCProvider - > IsAvailable ( ) )
2022-08-19 19:15:42 -04:00
{
2023-06-20 04:26:06 -04:00
OutErrorMessage = FText ( LOCTEXT ( " FailedSourceControlConnection " , " Failed to connect to revision control backend, see the Message Log 'Revision Control' errors for details. \n The revision control backend will be unable to pull payloads, is your revision control config set up correctly? " ) ) ;
2023-03-13 11:10:53 -04:00
return IVirtualizationBackend : : EConnectionStatus : : Error ;
2022-01-18 04:50:38 -05:00
}
2021-12-02 06:21:36 -05:00
// When a source control depot is set up a file named 'payload_metainfo.txt' should be submitted to it's root.
// This allows us to check for the existence of the file to confirm that the depot root is indeed valid.
2023-04-13 10:11:47 -04:00
const FString PayloadMetaInfoPath = WriteToString < 512 > ( DepotPathRoot , TEXT ( " payload_metainfo.txt " ) ) . ToString ( ) ;
2021-05-17 07:48:16 -04:00
2023-03-13 11:10:53 -04:00
FSharedBuffer MetaInfoBuffer ;
2021-05-17 07:48:16 -04:00
# if IS_SOURCE_CONTROL_THREAD_SAFE
2021-12-02 06:21:36 -05:00
TSharedRef < FDownloadFile , ESPMode : : ThreadSafe > DownloadCommand = ISourceControlOperation : : Create < FDownloadFile > ( ) ;
2022-03-09 07:43:59 -05:00
if ( SCCProvider - > Execute ( DownloadCommand , PayloadMetaInfoPath , EConcurrency : : Synchronous ) ! = ECommandResult : : Succeeded )
2021-12-02 06:21:36 -05:00
{
2021-05-17 07:48:16 -04:00
# else
2021-12-02 06:21:36 -05:00
TSharedRef < FDownloadFile , ESPMode : : ThreadSafe > DownloadCommand = ISourceControlOperation : : Create < FDownloadFile > ( ) ;
2023-03-13 11:10:53 -04:00
if ( SCCProvider - > TryToDownloadFileFromBackgroundThread ( DownloadCommand , PayloadMetaInfoPath ) )
2021-12-02 06:21:36 -05:00
{
2021-05-17 07:48:16 -04:00
# endif //IS_SOURCE_CONTROL_THREAD_SAFE
2023-03-13 11:10:53 -04:00
MetaInfoBuffer = DownloadCommand - > GetFileData ( PayloadMetaInfoPath ) ;
}
2021-12-02 06:21:36 -05:00
if ( MetaInfoBuffer . IsNull ( ) )
{
2023-06-20 04:26:06 -04:00
OutErrorMessage = FText : : Format ( LOCTEXT ( " FailedMetaInfo " , " Failed to find '{0}' on server '{1}' \n The revision control backend will be unable to pull payloads, is your revision control config set up correctly? " ) ,
2023-03-13 11:10:53 -04:00
FText : : FromString ( PayloadMetaInfoPath ) ,
FText : : FromString ( GetPortFromProvider ( SCCProvider . Get ( ) ) ) ) ;
2022-01-18 04:50:38 -05:00
Add a number of ways for the VA backend connections to be changed to lazy initialize on first use.
#rb Devin.Doucette
#jira UE-161599
#rnx
#preflight 6303c8d65a5d4e4624e7bf52
- There are some use cases that require the VA system to be initialized and configured correctly but would prefer that the backend connections only run if absolutely needed (usually when a payload is pulled or pushed for the first time), this change provides four different ways of doing this:
-- Setting [Core.VirtualizationModule]LazyInitConnections=true in the Engine ini file
-- Setting the define 'UE_VIRTUALIZATION_CONNECTION_LAZY_INIT' to 1 in a programs .target.cs
-- Running with the commandline option -VA-LazyInitConnections
-- Setting the cvar 'VA.LazyInitConnections' to 1 (only works if it is set before the VA system is initialized, changing it mid editor via the console does nothing)
--- Note that after the config file, each setting there only opts into lazy initializing the connections, setting the cvar to 0 for example will not prevent the cmdline from opting in etc.
- In the future we will allow the connection code to run async, so the latency can be hidden behind the editor loading, but for the current use case we are taking the minimal approach.
-- This means we only support the backend being in 3 states. No connection has been made yet, the connection is broken and the connection is working.
-- To keep things simple we only record if we have attempted to connect the backends or not. We don't check individual backends nor do we try to reconnect failed ones etc. This is all scheduled for a future work item.
- If the connections are not initialized when the VA system is, we wait until the first time someone calls one of the virtualization methods that will actually use a connection: Push/Pull/Query
-- We try connecting all of the backends at once, even if they won't be used in the call to keep things simple.
- Only the source control backend makes use of the connection system. The horde storage (http) backend could take advantage too, but it is currently unused and most likely going to just be deleted so there seemed little point updating it.
- If we try to run an operation on an unconnected backend we only log to verbose. This is to maintain existing behaviour where a failed backend would not be mounted at all. This logging will likely be revisited in a future work item.
[CL 21511855 by paul chipchase in ue5-main branch]
2022-08-23 13:01:15 -04:00
return IVirtualizationBackend : : EConnectionStatus : : Error ;
2021-05-17 07:48:16 -04:00
}
2021-12-02 06:21:36 -05:00
// Currently we do not do anything with the payload meta info, in the future we could structure
// it's format to include more information that might be worth logging or something.
// But for now being able to pull the payload meta info path at least shows that we can use the
// depot.
Add a number of ways for the VA backend connections to be changed to lazy initialize on first use.
#rb Devin.Doucette
#jira UE-161599
#rnx
#preflight 6303c8d65a5d4e4624e7bf52
- There are some use cases that require the VA system to be initialized and configured correctly but would prefer that the backend connections only run if absolutely needed (usually when a payload is pulled or pushed for the first time), this change provides four different ways of doing this:
-- Setting [Core.VirtualizationModule]LazyInitConnections=true in the Engine ini file
-- Setting the define 'UE_VIRTUALIZATION_CONNECTION_LAZY_INIT' to 1 in a programs .target.cs
-- Running with the commandline option -VA-LazyInitConnections
-- Setting the cvar 'VA.LazyInitConnections' to 1 (only works if it is set before the VA system is initialized, changing it mid editor via the console does nothing)
--- Note that after the config file, each setting there only opts into lazy initializing the connections, setting the cvar to 0 for example will not prevent the cmdline from opting in etc.
- In the future we will allow the connection code to run async, so the latency can be hidden behind the editor loading, but for the current use case we are taking the minimal approach.
-- This means we only support the backend being in 3 states. No connection has been made yet, the connection is broken and the connection is working.
-- To keep things simple we only record if we have attempted to connect the backends or not. We don't check individual backends nor do we try to reconnect failed ones etc. This is all scheduled for a future work item.
- If the connections are not initialized when the VA system is, we wait until the first time someone calls one of the virtualization methods that will actually use a connection: Push/Pull/Query
-- We try connecting all of the backends at once, even if they won't be used in the call to keep things simple.
- Only the source control backend makes use of the connection system. The horde storage (http) backend could take advantage too, but it is currently unused and most likely going to just be deleted so there seemed little point updating it.
- If we try to run an operation on an unconnected backend we only log to verbose. This is to maintain existing behaviour where a failed backend would not be mounted at all. This logging will likely be revisited in a future work item.
[CL 21511855 by paul chipchase in ue5-main branch]
2022-08-23 13:01:15 -04:00
return IVirtualizationBackend : : EConnectionStatus : : Connected ;
2021-12-02 06:21:36 -05:00
}
2023-04-25 08:47:06 -04:00
bool FSourceControlBackend : : PullData ( TArrayView < FPullRequest > Requests , EPullFlags Flags , FText & OutErrors )
2021-12-02 06:21:36 -05:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PullData ) ;
2022-10-21 22:27:40 -04:00
TArray < FString > DepotPaths ;
DepotPaths . Reserve ( Requests . Num ( ) ) ;
for ( FPullRequest & Request : Requests )
{
TStringBuilder < 512 > DepotPath ;
CreateDepotPath ( Request . GetIdentifier ( ) , DepotPath ) ;
DepotPaths . Add ( DepotPath . ToString ( ) ) ;
}
2021-12-02 06:21:36 -05:00
2022-05-03 09:39:48 -04:00
// TODO: When multiple threads are blocked waiting on this we could gather X payloads together and make a single
// batch request on the same connection, which should be a lot faster with less overhead.
// Although ideally this backend will not get hit very often.
FSemaphoreScopeLock _ ( ConcurrentConnectionLimit . Get ( ) ) ;
2022-04-14 05:32:56 -04:00
int32 Retries = 0 ;
2023-04-25 08:47:06 -04:00
bool bConnectionFailed = false ;
2022-04-14 05:32:56 -04:00
while ( Retries < RetryCount )
{
// Only warn if the backend is configured to retry
if ( Retries ! = 0 )
{
2022-10-21 22:27:40 -04:00
// UE_LOG(LogVirtualization, Warning, TEXT("[%s] Failed to download '%s' retrying (%d/%d) in %dms..."), *GetDebugName(), DepotPath.ToString(), Retries, RetryCount, RetryWaitTimeMS);
2023-05-08 09:40:17 -04:00
FPlatformProcess : : SleepNoStats ( static_cast < float > ( RetryWaitTimeMS ) * 0.001f ) ;
2022-04-14 05:32:56 -04:00
}
2021-05-17 07:48:16 -04:00
# if IS_SOURCE_CONTROL_THREAD_SAFE
2022-04-14 05:32:56 -04:00
TSharedRef < FDownloadFile , ESPMode : : ThreadSafe > DownloadCommand = ISourceControlOperation : : Create < FDownloadFile > ( FDownloadFile : : EVerbosity : : None ) ;
2022-11-01 15:10:19 -04:00
const bool bOperationSuccess = SCCProvider - > Execute ( DownloadCommand , DepotPaths , EConcurrency : : Synchronous ) = = ECommandResult : : Succeeded ;
2021-05-17 07:48:16 -04:00
# else
2022-04-14 05:32:56 -04:00
TSharedRef < FDownloadFile > DownloadCommand = ISourceControlOperation : : Create < FDownloadFile > ( FDownloadFile : : EVerbosity : : None ) ;
2022-11-01 15:10:19 -04:00
const bool bOperationSuccess = SCCProvider - > TryToDownloadFileFromBackgroundThread ( DownloadCommand , DepotPaths ) ;
2021-05-17 07:48:16 -04:00
# endif
2022-11-01 15:10:19 -04:00
// Check and assign what payloads we did find, even if the download command failed we might have been able to download some
// files and since we found them we might as well return them.
for ( int32 Index = 0 ; Index < Requests . Num ( ) ; + + Index )
{
// The payload was created by FCompressedBuffer::Compress so we can return it as a FCompressedBuffer.
FSharedBuffer Buffer = DownloadCommand - > GetFileData ( DepotPaths [ Index ] ) ;
2023-04-25 09:32:46 -04:00
if ( ! Buffer . IsNull ( ) )
{
Requests [ Index ] . SetPayload ( FCompressedBuffer : : FromCompressed ( Buffer ) ) ;
}
2022-11-01 15:10:19 -04:00
}
if ( bOperationSuccess )
{
return true ;
}
2022-04-14 05:32:56 -04:00
// If this was the first try then check to see if the error being returns is that the file does not exist
// in the depot. If it does not exist then there is no point in us retrying and we can error out at this point.
2022-11-01 15:10:19 -04:00
if ( Retries = = 0 & & ! ShouldRetryOperation ( DownloadCommand - > GetResultInfo ( ) ) )
2022-04-14 05:32:56 -04:00
{
2022-11-01 15:10:19 -04:00
return false ;
2022-04-14 05:32:56 -04:00
}
2022-10-21 22:27:40 -04:00
2023-04-25 08:47:06 -04:00
bConnectionFailed = DownloadCommand - > GetResultInfo ( ) . DidConnectionFail ( ) ;
2022-04-14 05:32:56 -04:00
Retries + + ;
}
2023-04-25 08:47:06 -04:00
if ( bConnectionFailed )
{
TMap < ISourceControlProvider : : EStatus , FString > SCPStatus = SCCProvider - > GetStatus ( ) ;
OutErrors = FText : : Format ( LOCTEXT ( " VA_SCP " , " Failed to connect to perforce server '{0}' with username '{1}' " ) ,
FText : : FromString ( SCPStatus [ ISourceControlProvider : : EStatus : : Port ] ) ,
FText : : FromString ( SCPStatus [ ISourceControlProvider : : EStatus : : User ] ) ) ;
}
2022-11-01 15:10:19 -04:00
return false ;
2021-12-02 06:21:36 -05:00
}
2021-05-17 07:48:16 -04:00
2022-02-02 02:21:24 -05:00
bool FSourceControlBackend : : DoesPayloadExist ( const FIoHash & Id )
2021-12-02 06:21:36 -05:00
{
TArray < bool > Result ;
2022-02-02 02:21:24 -05:00
if ( FSourceControlBackend : : DoPayloadsExist ( MakeArrayView < const FIoHash > ( & Id , 1 ) , Result ) )
2021-05-17 07:48:16 -04:00
{
2021-12-02 06:21:36 -05:00
check ( Result . Num ( ) = = 1 ) ;
return Result [ 0 ] ;
2021-05-17 07:48:16 -04:00
}
2021-12-02 06:21:36 -05:00
else
2021-12-01 11:13:31 -05:00
{
2021-12-02 06:21:36 -05:00
return false ;
2021-12-01 11:13:31 -05:00
}
2021-12-02 06:21:36 -05:00
}
2021-12-01 11:13:31 -05:00
2023-04-14 10:13:46 -04:00
bool FSourceControlBackend : : PushData ( TArrayView < FPushRequest > Requests , EPushFlags Flags )
2021-12-08 02:19:42 -05:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData ) ;
2022-08-15 10:18:14 -04:00
// First check to see which payloads actually need pushing, if we are lucky we might
// not need to even bother with workspace manipulation if all payloads already exist.
FSemaphoreScopeLock _ ( ConcurrentConnectionLimit . Get ( ) ) ;
2022-08-16 15:31:11 -04:00
// TODO: At some point FVirtualizationManager will do this so we know there cannot be duplicate
// payload requests, at which point we can rework this code.
// We create a new array of requests that we know are unique along with a map of the request
// results that we can use to set the original requests statuses at the end.
2022-09-30 15:47:04 -04:00
TMap < FIoHash , FPushResult > PayloadStatusMap ;
2022-08-16 15:31:11 -04:00
PayloadStatusMap . Reserve ( Requests . Num ( ) ) ;
TArray < const FPushRequest * > UniqueRequests ;
UniqueRequests . Reserve ( Requests . Num ( ) ) ;
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : FindUniquePayloads ) ;
for ( const FPushRequest & Request : Requests )
{
if ( ! PayloadStatusMap . Contains ( Request . GetIdentifier ( ) ) )
{
2022-09-30 15:47:04 -04:00
PayloadStatusMap . Add ( Request . GetIdentifier ( ) , FPushResult : : GetAsError ( ) ) ;
2022-08-16 15:31:11 -04:00
UniqueRequests . Add ( & Request ) ;
}
}
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Found %d unique payload(s) " ) , * GetDebugName ( ) , UniqueRequests . Num ( ) ) ;
}
TArray < const FPushRequest * > RequestsToPush ;
RequestsToPush . Reserve ( UniqueRequests . Num ( ) ) ;
2022-08-15 10:18:14 -04:00
2023-04-17 07:45:19 -04:00
// Note that we do not check EPushFlags::Force here and always check if the payloads are already in
// source control as we do not want multiple revisions.
// This might change at some point if we want to try changing the compression codec of the stored
// payloads.
2022-08-15 10:18:14 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : CheckIfPayloadsAlreadyExist ) ;
2022-08-16 15:31:11 -04:00
const int32 NumBatches = FMath : : DivideAndRoundUp < int32 > ( UniqueRequests . Num ( ) , MaxBatchCount ) ;
2022-08-15 10:18:14 -04:00
TArray < FIoHash > PayloadIdentifiers ;
PayloadIdentifiers . Reserve ( MaxBatchCount ) ;
TArray < bool > PayloadResults ;
PayloadResults . Reserve ( MaxBatchCount ) ;
for ( int32 BatchIndex = 0 ; BatchIndex < NumBatches ; + + BatchIndex )
{
PayloadIdentifiers . Reset ( 0 ) ;
PayloadResults . Reset ( 0 ) ;
const int32 BatchStart = BatchIndex * MaxBatchCount ;
2022-08-16 15:31:11 -04:00
const int32 BatchEnd = FMath : : Min ( ( BatchIndex + 1 ) * MaxBatchCount , UniqueRequests . Num ( ) ) ;
2022-08-15 10:18:14 -04:00
for ( int32 ReqIndex = BatchStart ; ReqIndex < BatchEnd ; + + ReqIndex )
{
2022-08-16 15:31:11 -04:00
PayloadIdentifiers . Add ( UniqueRequests [ ReqIndex ] - > GetIdentifier ( ) ) ;
2022-08-15 10:18:14 -04:00
}
if ( ! DoPayloadsExist ( PayloadIdentifiers , PayloadResults ) )
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to find the current file state for payloads " ) , * GetDebugName ( ) ) ;
return false ;
}
check ( PayloadIdentifiers . Num ( ) = = PayloadResults . Num ( ) ) ;
2022-08-16 15:31:11 -04:00
for ( int32 Index = 0 ; Index < PayloadResults . Num ( ) ; + + Index )
2022-08-15 10:18:14 -04:00
{
2022-08-16 15:31:11 -04:00
const FPushRequest * Request = UniqueRequests [ BatchStart + Index ] ;
2022-08-15 10:18:14 -04:00
if ( PayloadResults [ Index ] )
{
2022-09-30 15:47:04 -04:00
PayloadStatusMap [ Request - > GetIdentifier ( ) ] = FPushResult : : GetAsAlreadyExists ( ) ;
2022-08-15 10:18:14 -04:00
}
else
{
2022-08-16 15:31:11 -04:00
RequestsToPush . Add ( Request ) ;
2022-08-15 10:18:14 -04:00
}
}
}
}
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Determined that %d payload(s) require submission to revision control " ) , * GetDebugName ( ) , RequestsToPush . Num ( ) ) ;
2022-08-15 10:18:14 -04:00
if ( RequestsToPush . IsEmpty ( ) )
{
2022-08-16 15:31:11 -04:00
for ( FPushRequest & Request : Requests )
{
2022-09-30 15:47:04 -04:00
Request . SetResult ( PayloadStatusMap [ Request . GetIdentifier ( ) ] ) ;
2022-08-16 15:31:11 -04:00
}
2022-08-15 10:18:14 -04:00
return true ;
}
2021-12-08 02:19:42 -05:00
// TODO: Consider creating one workspace and one temp dir per session rather than per push.
// Although this would require more checking on start up to check for lingering workspaces
// and directories in case of editor crashes.
// We'd also need to remove each submitted file from the workspace after submission so that
// we can delete the local file
// We cannot easily submit files from within the project root due to p4 ignore rules
// so we will use the user temp directory instead. We append a guid to the root directory
// to avoid potentially conflicting with other editor processes that might be running.
const FGuid SessionGuid = FGuid : : NewGuid ( ) ;
2022-03-01 09:05:41 -05:00
2022-08-15 10:18:14 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Started payload submission session '%s' for '%d' payload(s) " ) , * GetDebugName ( ) , * LexToString ( SessionGuid ) , RequestsToPush . Num ( ) ) ;
2022-03-01 09:05:41 -05:00
TStringBuilder < 260 > SessionDirectory ;
FPathViews : : Append ( SessionDirectory , SubmissionRootDir , SessionGuid ) ;
2023-02-24 09:58:54 -05:00
if ( ! TryCreateSubmissionSessionDirectory ( SessionDirectory , IgnoreFileName ) )
2022-03-01 09:05:41 -05:00
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to created directory '%s' to submit payloads from " ) , * GetDebugName ( ) , SessionDirectory . ToString ( ) ) ;
return false ;
}
2022-05-05 03:11:20 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Created directory '%s' to submit payloads from " ) , * GetDebugName ( ) , SessionDirectory . ToString ( ) ) ;
2021-12-08 02:19:42 -05:00
ON_SCOPE_EXIT
{
// Clean up the payload file from disk and the temp directories, but we do not need to give errors if any of these operations fail.
2022-03-01 09:05:41 -05:00
IFileManager : : Get ( ) . DeleteDirectory ( SessionDirectory . ToString ( ) , false , true ) ;
2021-12-08 02:19:42 -05:00
} ;
TStringBuilder < 64 > WorkspaceName ;
2022-02-25 04:59:08 -05:00
WorkspaceName < < TEXT ( " VASubmission- " ) < < SessionGuid ;
2021-12-08 02:19:42 -05:00
// Create a temp workspace so that we can submit the payload from
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : CreateWorkspace ) ;
2022-03-01 09:05:41 -05:00
TSharedRef < FCreateWorkspace > CreateWorkspaceCommand = ISourceControlOperation : : Create < FCreateWorkspace > ( WorkspaceName , SessionDirectory ) ;
2021-12-08 02:19:42 -05:00
2022-08-10 08:51:05 -04:00
if ( ClientStream . IsEmpty ( ) )
{
TStringBuilder < 512 > DepotMapping ;
2023-04-13 10:11:47 -04:00
DepotMapping < < DepotPathRoot < < TEXT ( " ... " ) ;
2021-12-08 02:19:42 -05:00
2022-08-10 08:51:05 -04:00
TStringBuilder < 128 > ClientMapping ;
ClientMapping < < TEXT ( " // " ) < < WorkspaceName < < TEXT ( " /... " ) ;
2021-12-08 02:19:42 -05:00
2022-08-10 08:51:05 -04:00
CreateWorkspaceCommand - > AddNativeClientViewMapping ( DepotMapping , ClientMapping ) ;
}
else
{
CreateWorkspaceCommand - > SetStream ( ClientStream ) ;
}
2021-12-08 02:19:42 -05:00
if ( bUsePartitionedClient )
{
CreateWorkspaceCommand - > SetType ( FCreateWorkspace : : EType : : Partitioned ) ;
}
2022-11-23 12:08:16 -05:00
CreateWorkspaceCommand - > SetDescription ( TEXT ( " This workspace was autogenerated when submitting virtualized payloads to revision control " ) ) ;
2022-02-25 04:59:08 -05:00
2022-03-09 07:43:59 -05:00
if ( SCCProvider - > Execute ( CreateWorkspaceCommand ) ! = ECommandResult : : Succeeded )
2021-12-08 02:19:42 -05:00
{
2022-08-10 08:51:05 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to create temp workspace '%s' to use for payload submission " ) ,
2021-12-08 02:19:42 -05:00
* GetDebugName ( ) ,
WorkspaceName . ToString ( ) ) ;
return false ;
}
}
ON_SCOPE_EXIT
{
// Remove the temp workspace mapping
2022-03-09 07:43:59 -05:00
if ( SCCProvider - > Execute ( ISourceControlOperation : : Create < FDeleteWorkspace > ( WorkspaceName ) ) ! = ECommandResult : : Succeeded )
2021-12-08 02:19:42 -05:00
{
UE_LOG ( LogVirtualization , Warning , TEXT ( " [%s] Failed to remove temp workspace '%s' please delete manually " ) , * GetDebugName ( ) , WorkspaceName . ToString ( ) ) ;
}
} ;
FString OriginalWorkspace ;
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : SwitchWorkspace ) ;
FSourceControlResultInfo SwitchToNewWorkspaceInfo ;
2022-03-09 07:43:59 -05:00
if ( SCCProvider - > SwitchWorkspace ( WorkspaceName , SwitchToNewWorkspaceInfo , & OriginalWorkspace ) ! = ECommandResult : : Succeeded )
2021-12-08 02:19:42 -05:00
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to switch to temp workspace '%s' when trying to submit payloads " ) ,
* GetDebugName ( ) ,
WorkspaceName . ToString ( ) ) ;
return false ;
}
}
ON_SCOPE_EXIT
{
FSourceControlResultInfo SwitchToOldWorkspaceInfo ;
2022-03-09 07:43:59 -05:00
if ( SCCProvider - > SwitchWorkspace ( OriginalWorkspace , SwitchToOldWorkspaceInfo , nullptr ) ! = ECommandResult : : Succeeded )
2021-12-08 02:19:42 -05:00
{
2022-08-12 08:45:08 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to restore the original workspace '%s' " ) , * GetDebugName ( ) , * OriginalWorkspace ) ;
2021-12-08 02:19:42 -05:00
}
} ;
2022-08-15 10:18:14 -04:00
const int32 NumBatches = FMath : : DivideAndRoundUp < int32 > ( RequestsToPush . Num ( ) , MaxBatchCount ) ;
2022-05-05 03:11:20 -04:00
2022-08-15 10:18:14 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Splitting the submission into '%d' batches " ) , * GetDebugName ( ) , NumBatches ) ;
2022-05-05 03:11:20 -04:00
for ( int32 BatchIndex = 0 ; BatchIndex < NumBatches ; + + BatchIndex )
2021-12-08 02:19:42 -05:00
{
2022-05-05 03:11:20 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Processing batch %d/%d... " ) , * GetDebugName ( ) , BatchIndex + 1 , NumBatches ) ;
const int32 BatchStart = BatchIndex * MaxBatchCount ;
2022-08-15 10:18:14 -04:00
const int32 BatchEnd = FMath : : Min ( ( BatchIndex + 1 ) * MaxBatchCount , RequestsToPush . Num ( ) ) ;
2022-05-05 03:11:20 -04:00
2022-08-16 15:31:11 -04:00
TArrayView < const FPushRequest * > RequestBatch ( & RequestsToPush [ BatchStart ] , BatchEnd - BatchStart ) ;
2022-05-05 03:11:20 -04:00
TArray < FString > FilesToSubmit ;
FilesToSubmit . Reserve ( RequestBatch . Num ( ) ) ;
// Write the payloads to disk so that they can be submitted (source control module currently requires the files to
// be on disk)
2021-12-08 02:19:42 -05:00
{
2022-05-05 03:11:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : CreateFiles ) ;
2022-08-15 10:18:14 -04:00
for ( const FPushRequest * Request : RequestBatch )
2022-05-05 03:11:20 -04:00
{
2022-08-15 10:18:14 -04:00
check ( Request ! = nullptr ) ;
2022-05-05 03:11:20 -04:00
TStringBuilder < 52 > LocalPayloadPath ;
2022-08-15 10:18:14 -04:00
Utils : : PayloadIdToPath ( Request - > GetIdentifier ( ) , LocalPayloadPath ) ;
2022-05-05 03:11:20 -04:00
TStringBuilder < 260 > PayloadFilePath ;
FPathViews : : Append ( PayloadFilePath , SessionDirectory , LocalPayloadPath ) ;
UE_LOG ( LogVirtualization , Verbose , TEXT ( " [%s] Writing payload to '%s' for submission " ) , * GetDebugName ( ) , PayloadFilePath . ToString ( ) ) ;
2022-08-15 10:18:14 -04:00
FCompressedBuffer Payload = Request - > GetPayload ( ) ;
2022-05-05 03:11:20 -04:00
if ( Payload . IsNull ( ) )
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to acquire payload '%s' contents to '%s' for writing " ) ,
* GetDebugName ( ) ,
2022-08-15 10:18:14 -04:00
* LexToString ( Request - > GetIdentifier ( ) ) ,
2022-05-05 03:11:20 -04:00
PayloadFilePath . ToString ( ) ) ;
return false ;
}
TUniquePtr < FArchive > FileAr ( IFileManager : : Get ( ) . CreateFileWriter ( PayloadFilePath . ToString ( ) ) ) ;
if ( ! FileAr )
{
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
Utils : : GetFormattedSystemError ( SystemErrorMsg ) ;
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to write payload '%s' contents to '%s' due to system error: %s " ) ,
* GetDebugName ( ) ,
2022-08-15 10:18:14 -04:00
* LexToString ( Request - > GetIdentifier ( ) ) ,
2022-05-05 03:11:20 -04:00
PayloadFilePath . ToString ( ) ,
SystemErrorMsg . ToString ( ) ) ;
return false ;
}
Payload . Save ( * FileAr ) ;
if ( ! FileAr - > Close ( ) )
{
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
Utils : : GetFormattedSystemError ( SystemErrorMsg ) ;
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to write payload '%s' contents to '%s' due to system error: %s " ) ,
* GetDebugName ( ) ,
2022-08-15 10:18:14 -04:00
* LexToString ( Request - > GetIdentifier ( ) ) ,
2022-05-05 03:11:20 -04:00
* PayloadFilePath ,
SystemErrorMsg .
ToString ( ) ) ;
return false ;
}
FilesToSubmit . Emplace ( MoveTemp ( PayloadFilePath ) ) ;
}
2021-12-08 02:19:42 -05:00
}
2022-05-05 03:11:20 -04:00
check ( RequestBatch . Num ( ) = = FilesToSubmit . Num ( ) ) ;
2021-12-08 02:19:42 -05:00
{
2022-05-05 03:11:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : AddFiles ) ;
2022-08-15 10:18:14 -04:00
if ( SCCProvider - > Execute ( ISourceControlOperation : : Create < FMarkForAdd > ( ) , FilesToSubmit ) ! = ECommandResult : : Succeeded )
2022-05-05 03:11:20 -04:00
{
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to mark the payload file for Add in revision control " ) , * GetDebugName ( ) ) ;
2022-05-05 03:11:20 -04:00
return false ;
}
2021-12-08 02:19:42 -05:00
}
2022-05-05 03:11:20 -04:00
// Now submit the payload
2021-12-08 02:19:42 -05:00
{
2022-05-05 03:11:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FSourceControlBackend : : PushData : : SubmitFiles ) ;
2021-12-08 02:19:42 -05:00
2022-05-05 03:11:20 -04:00
TSharedRef < FCheckIn , ESPMode : : ThreadSafe > CheckInOperation = ISourceControlOperation : : Create < FCheckIn > ( ) ;
TStringBuilder < 512 > Description ;
2022-08-15 10:18:14 -04:00
CreateDescription ( ProjectName , RequestBatch , Description ) ;
2022-05-05 03:11:20 -04:00
CheckInOperation - > SetDescription ( FText : : FromString ( Description . ToString ( ) ) ) ;
2022-08-15 10:18:14 -04:00
if ( SCCProvider - > Execute ( CheckInOperation , FilesToSubmit ) ! = ECommandResult : : Succeeded )
2022-05-05 03:11:20 -04:00
{
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to submit the payload file(s) to revision control " ) , * GetDebugName ( ) ) ;
2022-05-05 03:11:20 -04:00
return false ;
}
}
2022-08-16 15:31:11 -04:00
for ( const FPushRequest * Request : RequestBatch )
2022-05-05 03:11:20 -04:00
{
2022-09-30 15:47:04 -04:00
PayloadStatusMap [ Request - > GetIdentifier ( ) ] = FPushResult : : GetAsPushed ( ) ;
2022-05-05 03:11:20 -04:00
}
// Try to clean up the files from this batch
for ( const FString & FilePath : FilesToSubmit )
{
const bool bRequireExists = false ;
const bool bEvenReadOnly = true ;
const bool bQuiet = false ;
IFileManager : : Get ( ) . Delete ( * FilePath , bRequireExists , bEvenReadOnly , bQuiet ) ;
}
2021-12-08 02:19:42 -05:00
}
2022-08-16 15:31:11 -04:00
// Finally set all of the request statuses
for ( FPushRequest & Request : Requests )
{
2022-09-30 15:47:04 -04:00
Request . SetResult ( PayloadStatusMap [ Request . GetIdentifier ( ) ] ) ;
2022-08-16 15:31:11 -04:00
}
2021-12-08 02:19:42 -05:00
return true ;
}
2022-02-02 02:21:24 -05:00
bool FSourceControlBackend : : DoPayloadsExist ( TArrayView < const FIoHash > PayloadIds , TArray < bool > & OutResults )
2021-12-02 06:21:36 -05:00
{
TArray < FString > DepotPaths ;
DepotPaths . Reserve ( PayloadIds . Num ( ) ) ;
TArray < FSourceControlStateRef > PathStates ;
2022-02-02 02:21:24 -05:00
for ( const FIoHash & PayloadId : PayloadIds )
2021-12-01 11:13:31 -05:00
{
2022-02-02 02:21:24 -05:00
if ( ! PayloadId . IsZero ( ) )
2021-12-01 11:13:31 -05:00
{
2021-12-02 06:21:36 -05:00
TStringBuilder < 52 > LocalPayloadPath ;
Utils : : PayloadIdToPath ( PayloadId , LocalPayloadPath ) ;
2021-12-01 11:13:31 -05:00
2023-04-13 10:11:47 -04:00
DepotPaths . Emplace ( WriteToString < 512 > ( DepotPathRoot , LocalPayloadPath ) ) ;
2021-12-01 11:13:31 -05:00
}
}
2022-03-14 12:03:48 -04:00
ECommandResult : : Type Result = GetDepotPathStates ( * SCCProvider , DepotPaths , PathStates ) ;
2021-12-02 06:21:36 -05:00
if ( Result ! = ECommandResult : : Type : : Succeeded )
{
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to query the state of files in the revision control depot " ) , * GetDebugName ( ) ) ;
2021-12-02 06:21:36 -05:00
return false ;
}
2021-05-17 07:48:16 -04:00
2021-12-02 06:21:36 -05:00
check ( DepotPaths . Num ( ) = = PathStates . Num ( ) ) ; // We expect that all paths return a state
OutResults . SetNum ( PayloadIds . Num ( ) ) ;
int32 StatusIndex = 0 ;
for ( int32 Index = 0 ; Index < PayloadIds . Num ( ) ; + + Index )
{
2022-02-02 02:21:24 -05:00
if ( ! PayloadIds [ Index ] . IsZero ( ) )
2021-12-02 06:21:36 -05:00
{
OutResults [ Index ] = PathStates [ StatusIndex + + ] - > IsSourceControlled ( ) ;
}
}
return true ;
}
2022-04-14 05:32:56 -04:00
bool FSourceControlBackend : : TryApplySettingsFromConfigFiles ( const FString & ConfigEntry )
{
2023-04-13 10:11:47 -04:00
// If the depot root is within a perforce stream then we must specify which stream. This may be a virtual stream with a custom view.
2022-04-14 05:32:56 -04:00
{
2023-04-13 10:11:47 -04:00
FParse : : Value ( * ConfigEntry , TEXT ( " ClientStream= " ) , ClientStream ) ;
if ( ! ClientStream . IsEmpty ( ) )
2022-08-10 08:51:05 -04:00
{
2023-04-13 10:11:47 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Using client stream: '%s' " ) , * GetDebugName ( ) , * ClientStream ) ;
2022-08-10 08:51:05 -04:00
}
2023-04-13 10:11:47 -04:00
else
2022-08-10 08:51:05 -04:00
{
2023-04-13 10:11:47 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Not using client stream " ) , * GetDebugName ( ) ) ;
2022-08-10 08:51:05 -04:00
}
2022-07-20 06:10:35 -04:00
}
2023-04-13 10:11:47 -04:00
// We require that a valid depot root has been provided
{
if ( FParse : : Value ( * ConfigEntry , TEXT ( " DepotRoot= " ) , DepotPathRoot ) )
{
UE_LOG ( LogVirtualization , Warning , TEXT ( " [%s] Entry 'DepotRoot' is deprecated, replace with 'DepotPath' " ) , * GetDebugName ( ) ) ;
}
else
{
FParse : : Value ( * ConfigEntry , TEXT ( " DepotPath= " ) , DepotPathRoot ) ;
}
if ( DepotPathRoot . IsEmpty ( ) )
{
DepotPathRoot = ClientStream ;
}
if ( DepotPathRoot . IsEmpty ( ) )
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] 'DepotPath' was not found in the config file! " ) , * GetDebugName ( ) ) ;
return false ;
}
if ( ! DepotPathRoot . EndsWith ( TEXT ( " / " ) ) )
{
DepotPathRoot . AppendChar ( TEXT ( ' / ' ) ) ;
}
}
2022-04-14 05:32:56 -04:00
2022-09-21 16:29:44 -04:00
{
FParse : : Value ( * ConfigEntry , TEXT ( " Server= " ) , ServerAddress ) ;
2023-03-09 02:40:45 -05:00
if ( ! ServerAddress . IsEmpty ( ) )
2022-09-21 16:29:44 -04:00
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Using server address: '%s' " ) , * GetDebugName ( ) , * ServerAddress ) ;
}
else
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Will connect to the default server address " ) , * GetDebugName ( ) ) ;
}
}
2022-05-03 09:39:48 -04:00
// Check to see if we should use partitioned clients or not. This is a perforce specific optimization to make the workspace churn cheaper on the server
2022-04-14 05:32:56 -04:00
{
2022-05-03 09:39:48 -04:00
FParse : : Bool ( * ConfigEntry , TEXT ( " UsePartitionedClient= " ) , bUsePartitionedClient ) ;
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Using partitioned clients: '%s' " ) , * GetDebugName ( ) , bUsePartitionedClient ? TEXT ( " true " ) : TEXT ( " false " ) ) ;
2022-04-14 05:32:56 -04:00
}
2022-05-03 09:39:48 -04:00
// Allow the source control backend to retry failed pulls
2022-04-14 05:32:56 -04:00
{
2022-05-03 09:39:48 -04:00
int32 RetryCountIniFile = INDEX_NONE ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " RetryCount= " ) , RetryCountIniFile ) )
{
RetryCount = RetryCountIniFile ;
}
int32 RetryWaitTimeMSIniFile = INDEX_NONE ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " RetryWaitTime= " ) , RetryWaitTimeMSIniFile ) )
{
RetryWaitTimeMS = RetryWaitTimeMSIniFile ;
}
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Will retry failed downloads attempts %d time(s) with a gap of %dms betwen them " ) , * GetDebugName ( ) , RetryCount , RetryWaitTimeMS ) ;
2022-04-14 05:32:56 -04:00
}
2022-05-03 09:39:48 -04:00
// Allow the number of concurrent connections to be limited
{
int32 MaxLimit = 8 ; // We use the UGS max of 8 as the default
FParse : : Value ( * ConfigEntry , TEXT ( " MaxConnections= " ) , MaxLimit ) ;
if ( MaxLimit ! = INDEX_NONE )
{
2023-01-19 07:58:12 -05:00
ConcurrentConnectionLimit = MakeUnique < FSemaphore > ( MaxLimit , FSemaphore : : EFlags : : PrioritizeGameThread ) ;
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Limted to %d concurrent revision control connections " ) , * GetDebugName ( ) , MaxLimit ) ;
2022-05-03 09:39:48 -04:00
}
else
{
2022-11-23 12:08:16 -05:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Has no limit to it's concurrent revision control connections " ) , * GetDebugName ( ) ) ;
2022-05-03 09:39:48 -04:00
}
2022-05-05 03:11:20 -04:00
}
// Check for the optional BatchCount parameter
{
int32 MaxBatchCountIniFile = 0 ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " MaxBatchCount= " ) , MaxBatchCountIniFile ) )
{
MaxBatchCount = MaxBatchCountIniFile ;
}
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Will push payloads in batches of up to %d payloads(s) at a time " ) , * GetDebugName ( ) , MaxBatchCount ) ;
}
2022-04-14 05:32:56 -04:00
2022-07-07 11:37:36 -04:00
// Check to see if connection error notification pop ups should be shown or not
{
FParse : : Bool ( * ConfigEntry , TEXT ( " SuppressNotifications= " ) , bSuppressNotifications ) ;
if ( bSuppressNotifications )
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Connection pop up warnings are supressed " ) , * GetDebugName ( ) ) ;
}
else
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Connection pop up warnings will be shown " ) , * GetDebugName ( ) ) ;
}
}
2023-03-15 10:17:32 -04:00
{
FParse : : Bool ( * ConfigEntry , TEXT ( " UseLocalIniFileSettings= " ) , bUseLocalIniFileSettings ) ;
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Reading settings from local SourceControlSettings.ini is %s " ) , * GetDebugName ( ) , bUseLocalIniFileSettings ? TEXT ( " enabled " ) : TEXT ( " disabled " ) ) ;
}
2023-06-20 04:26:06 -04:00
{
FParse : : Bool ( * ConfigEntry , TEXT ( " UseRetryConnectionDialog= " ) , bUseRetryConnectionDialog ) ;
2023-06-21 11:53:01 -04:00
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " VADisableDialog " ) ) )
{
bUseRetryConnectionDialog = false ;
}
2023-06-20 04:26:06 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Showing a reconnect dialog on initial failure %s " ) , * GetDebugName ( ) , bUseRetryConnectionDialog ? TEXT ( " enabled " ) : TEXT ( " disabled " ) ) ;
}
2023-02-24 09:58:54 -05:00
{
// TODO: We should just extract this from the perforce environment but that requires extending
// the source control api.
// Letting the backend define the ignore filename to use is a quicker work around
FParse : : Value ( * ConfigEntry , TEXT ( " IgnoreFile= " ) , IgnoreFileName ) ;
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Using '%s' as the p4 ignore file name " ) , * GetDebugName ( ) , * IgnoreFileName ) ;
}
2022-04-14 05:32:56 -04:00
if ( ! FindSubmissionWorkingDir ( ConfigEntry ) )
{
return false ;
}
return true ;
}
2022-02-02 02:21:24 -05:00
void FSourceControlBackend : : CreateDepotPath ( const FIoHash & PayloadId , FStringBuilderBase & OutPath )
2021-12-02 06:21:36 -05:00
{
TStringBuilder < 52 > PayloadPath ;
Utils : : PayloadIdToPath ( PayloadId , PayloadPath ) ;
2023-04-13 10:11:47 -04:00
OutPath < < DepotPathRoot < < PayloadPath ;
2021-12-02 06:21:36 -05:00
}
2021-05-17 07:48:16 -04:00
2022-03-01 09:05:41 -05:00
bool FSourceControlBackend : : FindSubmissionWorkingDir ( const FString & ConfigEntry )
{
// Note regarding path lengths.
// During submission each payload path will be 90 characters in length which will then be appended to
// the SubmissionWorkingDir
2023-02-24 08:50:29 -05:00
FString WorkingDirFromIniFile ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " WorkingDir= " ) , WorkingDirFromIniFile ) & & ! WorkingDirFromIniFile . IsEmpty ( ) )
2022-03-01 09:05:41 -05:00
{
2023-02-24 08:50:29 -05:00
TStringBuilder < 512 > ExpandedPath ;
if ( Utils : : ExpandEnvironmentVariables ( WorkingDirFromIniFile , ExpandedPath ) )
{
SubmissionRootDir = ExpandedPath ;
}
else
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Failed to correctly expand 'WorkingDir=%s' " ) , * GetDebugName ( ) , * WorkingDirFromIniFile ) ;
}
2022-03-01 09:05:41 -05:00
}
2023-02-24 08:50:29 -05:00
if ( SubmissionRootDir . IsEmpty ( ) )
{
SubmissionRootDir = FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " UE-VirtualizationWorkingDir " ) ) ;
if ( ! SubmissionRootDir . IsEmpty ( ) )
{
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Found Environment Variable: UE-VirtualizationWorkingDir " ) , * GetDebugName ( ) ) ;
}
}
if ( SubmissionRootDir . IsEmpty ( ) )
2022-03-01 09:05:41 -05:00
{
bool bSubmitFromTempDir = false ;
2023-02-24 08:50:29 -05:00
if ( FParse : : Bool ( * ConfigEntry , TEXT ( " SubmitFromTempDir= " ) , bSubmitFromTempDir ) )
{
UE_LOG ( LogVirtualization , Warning , TEXT ( " [%s] Found legacy ini file setting 'SubmitFromTempDir' use '-WorkingDir=$(Temp)/UnrealEngine/VASubmission' instead! " ) , * GetDebugName ( ) ) ;
}
2022-03-01 09:05:41 -05:00
TStringBuilder < 260 > PathBuilder ;
if ( bSubmitFromTempDir )
{
FPathViews : : Append ( PathBuilder , FPlatformProcess : : UserTempDir ( ) , TEXT ( " UnrealEngine/VASubmission " ) ) ;
}
else
{
FPathViews : : Append ( PathBuilder , FPaths : : ProjectSavedDir ( ) , TEXT ( " VASubmission " ) ) ;
}
SubmissionRootDir = PathBuilder ;
}
2023-02-24 08:50:29 -05:00
FPaths : : NormalizeDirectoryName ( SubmissionRootDir ) ;
2022-03-01 09:05:41 -05:00
if ( IFileManager : : Get ( ) . DirectoryExists ( * SubmissionRootDir ) | | IFileManager : : Get ( ) . MakeDirectory ( * SubmissionRootDir ) )
{
2022-04-14 05:32:56 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Setting '%s' as the working directory " ) , * GetDebugName ( ) , * SubmissionRootDir ) ;
2022-03-01 09:05:41 -05:00
return true ;
}
else
{
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
Utils : : GetFormattedSystemError ( SystemErrorMsg ) ;
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to set the working directory to '%s' due to %s " ) , * GetDebugName ( ) , * SubmissionRootDir , SystemErrorMsg . ToString ( ) ) ;
SubmissionRootDir . Empty ( ) ;
return false ;
}
}
2023-01-30 19:23:14 -05:00
void FSourceControlBackend : : OnConnectionError ( FText Message )
2022-01-18 04:50:38 -05:00
{
2023-01-30 19:23:14 -05:00
// We don't know when or where this error might occur. We can currently only create
// FMessageLog on the game thread or risk slate asserts, and if we raise a
// FMessageLog::Notify too early in the editor loading process it won't be shown
// correctly.
// To avoid this we push errors to the next editor tick, which will be on the
// GameThread and at a point where notification will work. In addition if we do need
// to defer the error message until next tick (rather than just the notification) then
// we will write it to the log at the point it is raised rather than the point it is
2023-05-11 06:35:58 -04:00
// deferred to in an attempt to make the log more readable.
2022-01-18 04:50:38 -05:00
2023-05-11 06:35:58 -04:00
if ( Message . IsEmpty ( ) )
{
return ;
2023-01-30 19:23:14 -05:00
}
2022-01-18 04:50:38 -05:00
2023-05-11 06:35:58 -04:00
TSharedRef < FTokenizedMessage > TokenizedMsg = FTokenizedMessage : : Create ( EMessageSeverity : : Error ) ;
TokenizedMsg - > AddToken ( FTextToken : : Create ( Message ) ) ;
FString ConnectionHelpUrl = FVirtualizationManager : : GetConnectionHelpUrl ( ) ;
if ( ! ConnectionHelpUrl . IsEmpty ( ) )
2022-07-07 11:37:36 -04:00
{
2023-05-11 06:35:58 -04:00
TokenizedMsg - > AddToken ( FURLToken : : Create ( ConnectionHelpUrl , LOCTEXT ( " URL " , " Additional connection help " ) ) ) ;
}
if ( : : IsInGameThread ( ) )
{
// If we are on the game thread we can post the error message immediately
FMessageLog Log ( " LogVirtualization " ) ;
Log . AddMessage ( TokenizedMsg ) ;
}
else
{
// We can only send a FMessageLog on the GameThread so for now just log the error
// and we can send it to the FMessageLog system next tick
UE_LOG ( LogVirtualization , Error , TEXT ( " %s " ) , * Message . ToString ( ) ) ;
auto Callback = [ TokenizedMsg , bShouldNotify = ! bSuppressNotifications ] ( float Delta ) - > bool
2023-01-30 19:23:14 -05:00
{
FMessageLog Log ( " LogVirtualization " ) ;
Log . SuppressLoggingToOutputLog ( ) ;
2023-05-11 06:35:58 -04:00
Log . AddMessage ( TokenizedMsg ) ;
2023-01-30 19:23:14 -05:00
return false ;
2023-05-11 06:35:58 -04:00
} ;
2023-01-30 19:23:14 -05:00
FTSTicker : : GetCoreTicker ( ) . AddTicker ( FTickerDelegate : : CreateLambda ( MoveTemp ( Callback ) ) ) ;
2022-07-07 11:37:36 -04:00
}
2023-05-11 06:35:58 -04:00
if ( ! bSuppressNotifications )
{
// Add the notification to the ticker so that if this is called during editor startup it will only
// be issued on the first frame, increasing the chance that the user will see it.
auto NotificationCallback = [ ] ( float Delta ) - > bool
{
FMessageLog Notification ( " LogVirtualization " ) ; ;
Notification . SuppressLoggingToOutputLog ( ) ;
Notification . Notify ( LOCTEXT ( " ConnectionError " , " Asset virtualization connection errors were encountered, see the message log for more info " ) ) ;
return false ;
} ;
FTSTicker : : GetCoreTicker ( ) . AddTicker ( FTickerDelegate : : CreateLambda ( MoveTemp ( NotificationCallback ) ) ) ;
}
2022-01-18 04:50:38 -05:00
}
2022-08-12 02:27:28 -04:00
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY ( FSourceControlBackend , P4SourceControl ) ;
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY_LEGACY ( FSourceControlBackend , SourceControl , P4SourceControl ) ;
2021-05-17 07:48:16 -04:00
} // namespace UE::Virtualization
2022-01-18 04:50:38 -05:00
# undef LOCTEXT_NAMESPACE