You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#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]
348 lines
12 KiB
C++
348 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "Compression/CompressedBuffer.h"
|
|
#include "Containers/StringView.h"
|
|
#include "Features/IModularFeature.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "Templates/UniquePtr.h"
|
|
#include "Virtualization/VirtualizationSystem.h"
|
|
|
|
struct FIoHash;
|
|
|
|
namespace UE::Virtualization
|
|
{
|
|
|
|
/** Describes the result of a IVirtualizationBackend::Push operation */
|
|
enum class EPushResult
|
|
{
|
|
/** The push failed, the backend should print an error message to 'LogVirtualization'.*/
|
|
Failed = 0,
|
|
/** The payload already exists in the backend and does not need to be pushed. */
|
|
PayloadAlreadyExisted,
|
|
/** The payload was successfully pushed to the backend. */
|
|
Success
|
|
};
|
|
|
|
/**
|
|
* The interface to derive from to create a new backend implementation.
|
|
*
|
|
* Note that virtualization backends are instantiated FVirtualizationManager via
|
|
* IVirtualizationBackendFactory so each new backend derived from IVirtualizationBackend
|
|
* will also need a factory derived from IVirtualizationBackendFactory. You can either do
|
|
* this manually or use the helper macro 'UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY' to
|
|
* generate the code for you.
|
|
*
|
|
*/
|
|
class IVirtualizationBackend
|
|
{
|
|
public:
|
|
/** Enum detailing which operations a backend can support */
|
|
enum class EOperations : uint8
|
|
{
|
|
/** Supports no operations, this should only occur when debug settings are applied */
|
|
None = 0,
|
|
/** Supports only push operations */
|
|
Push = 1 << 0,
|
|
/** Supports only pull operations */
|
|
Pull = 1 << 1,
|
|
};
|
|
|
|
FRIEND_ENUM_CLASS_FLAGS(EOperations);
|
|
|
|
/** Status of the backends connection to its services */
|
|
enum class EConnectionStatus
|
|
{
|
|
/** No connection attempt has been made */
|
|
None,
|
|
/** The previous connection attempt ended with an error */
|
|
Error,
|
|
/** The connection has been made successfully */
|
|
Connected,
|
|
};
|
|
|
|
protected:
|
|
|
|
IVirtualizationBackend(FStringView InConfigName, FStringView InDebugName, EOperations InSupportedOperations)
|
|
: SupportedOperations(InSupportedOperations)
|
|
, DebugDisabledOperations(EOperations::None)
|
|
, ConfigName(InConfigName)
|
|
, DebugName(InDebugName)
|
|
{
|
|
checkf(InSupportedOperations != EOperations::None, TEXT("Cannot create a backend with no supported operations!"));
|
|
}
|
|
|
|
public:
|
|
virtual ~IVirtualizationBackend() = default;
|
|
|
|
/**
|
|
* This will be called during the setup of the backend hierarchy. The entry config file
|
|
* entry that caused the backend to be created will be passed to the method so that any
|
|
* additional settings may be parsed from it.
|
|
* Take care to clearly log any error that occurs so that the end user has a clear way
|
|
* to fix them.
|
|
*
|
|
* @param ConfigEntry The entry for the backend from the config ini file that may
|
|
* contain additional settings.
|
|
* @return Returning false indicates that initialization failed in a way
|
|
* that the backend will not be able to function correctly.
|
|
*/
|
|
virtual bool Initialize(const FString& ConfigEntry) = 0;
|
|
|
|
/**
|
|
* Attempt to connect the backend to its services if not already connected.
|
|
*/
|
|
virtual void Connect()
|
|
{
|
|
if (ConnectionStatus != EConnectionStatus::Connected)
|
|
{
|
|
ConnectionStatus = OnConnect();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the connection status of the backend
|
|
*
|
|
* @return @see EConnectionStatus
|
|
*/
|
|
EConnectionStatus GetConnectionStatus() const
|
|
{
|
|
return ConnectionStatus;
|
|
}
|
|
|
|
/**
|
|
* The backend will attempt to store the given payload by what ever method the backend uses.
|
|
* NOTE: It is assumed that the virtualization manager will run all appropriate validation
|
|
* on the payload and it's id and that the inputs to PushData can be trusted.
|
|
*
|
|
* @param Id The Id of the payload
|
|
* @param Payload A potentially compressed buffer representing the payload
|
|
* @return The result of the push operation
|
|
*/
|
|
virtual EPushResult PushData(const FIoHash& Id, const FCompressedBuffer& Payload, const FString& PackageContext) = 0;
|
|
|
|
virtual bool PushData(TArrayView<FPushRequest> Requests)
|
|
{
|
|
// TODO: Improve the error codes in the future
|
|
for (FPushRequest& Request : Requests)
|
|
{
|
|
EPushResult Result = PushData(Request.GetIdentifier(), Request.GetPayload(), Request.GetContext());
|
|
switch (Result)
|
|
{
|
|
case EPushResult::Failed:
|
|
Request.SetStatus(FPushRequest::EStatus::Failed);
|
|
return false;
|
|
|
|
case EPushResult::PayloadAlreadyExisted:
|
|
// falls through
|
|
case EPushResult::Success:
|
|
Request.SetStatus(FPushRequest::EStatus::Success);
|
|
break;
|
|
|
|
default:
|
|
Request.SetStatus(FPushRequest::EStatus::Failed);
|
|
checkNoEntry();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The backend will attempt to retrieve the given payload by what ever method the backend uses.
|
|
* NOTE: It is assumed that the virtualization manager will validate the returned payload to
|
|
* make sure that it matches the requested id so there is no need for each backend to do this/
|
|
*
|
|
* @param Id The Id of a payload to try and pull from the backend.
|
|
*
|
|
* @return A valid FCompressedBuffer containing the payload if the pull
|
|
* operation succeeded and a null FCompressedBuffer
|
|
* if it did not.
|
|
*/
|
|
virtual FCompressedBuffer PullData(const FIoHash& Id) = 0;
|
|
|
|
/**
|
|
* Checks if a payload exists in the backends storage.
|
|
*
|
|
* @param Id The identifier of the payload to check
|
|
*
|
|
* @return True if the backend storage already contains the payload, otherwise false
|
|
*/
|
|
virtual bool DoesPayloadExist(const FIoHash& Id) = 0;
|
|
|
|
/**
|
|
* Checks if a number of payload exists in the backends storage.
|
|
*
|
|
* @param[in] PayloadIds An array of FIoHash that should be checked
|
|
* @param[out] OutResults An array to contain the result, true if the payload
|
|
* exists in the backends storage, false if not.
|
|
* This array will be resized to match the size of PayloadIds.
|
|
*
|
|
* @return True if the operation completed without error, otherwise false
|
|
*/
|
|
virtual bool DoPayloadsExist(TArrayView<const FIoHash> PayloadIds, TArray<bool>& OutResults)
|
|
{
|
|
// This is the default implementation that just calls ::DoesExist on each FIoHash in the
|
|
// array, one at a time.
|
|
// Backends may override this with their own implementations if it can be done with less
|
|
// overhead by performing the check on the entire batch instead.
|
|
|
|
OutResults.SetNum(PayloadIds.Num());
|
|
|
|
for (int32 Index = 0; Index < PayloadIds.Num(); ++Index)
|
|
{
|
|
OutResults[Index] = DoesPayloadExist(PayloadIds[Index]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given operation is supported, this is set when the backend is created
|
|
* and should not change over it's life time.
|
|
*/
|
|
bool IsOperationSupported(EOperations Operation) const
|
|
{
|
|
return EnumHasAnyFlags(SupportedOperations, Operation);
|
|
}
|
|
|
|
/** Enable or disable the given operation based on the 'bIsDisabled' parameter */
|
|
void SetOperationDebugState(EOperations Operation, bool bIsDisabled)
|
|
{
|
|
if (bIsDisabled)
|
|
{
|
|
EnumAddFlags(DebugDisabledOperations, Operation);
|
|
|
|
}
|
|
else
|
|
{
|
|
EnumRemoveFlags(DebugDisabledOperations, Operation);
|
|
}
|
|
}
|
|
|
|
/** Returns true if the given operation is disabled for debugging purposes */
|
|
bool IsOperationDebugDisabled(EOperations Operation) const
|
|
{
|
|
return EnumHasAnyFlags(DebugDisabledOperations, Operation);
|
|
}
|
|
|
|
/** Returns a string containing the name of the backend as it appears in the virtualization graph in the config file */
|
|
const FString& GetConfigName() const
|
|
{
|
|
return ConfigName;
|
|
}
|
|
|
|
/** Returns a string that can be used to identify the backend for debugging and logging purposes */
|
|
const FString& GetDebugName() const
|
|
{
|
|
return DebugName;
|
|
}
|
|
|
|
private:
|
|
|
|
/** Override to implement the backends connection code */
|
|
virtual EConnectionStatus OnConnect() = 0;
|
|
|
|
private:
|
|
|
|
/** The operations that this backend supports */
|
|
EOperations SupportedOperations;
|
|
|
|
EOperations DebugDisabledOperations;
|
|
|
|
/** The name assigned to the backend by the virtualization graph */
|
|
FString ConfigName;
|
|
|
|
/** Combination of the backend type and the name used to create it in the virtualization graph */
|
|
FString DebugName;
|
|
|
|
/** The status of the connection to the backends service */
|
|
EConnectionStatus ConnectionStatus = EConnectionStatus::None;
|
|
};
|
|
|
|
/**
|
|
* Derive from this interface to implement a factory to return a backend type.
|
|
* An instance of the factory should be created and then registered to
|
|
* IModularFeatures with the feature name "VirtualizationBackendFactory" to
|
|
* give 'FVirtualizationManager' access to it.
|
|
* The macro 'UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY' can be used to create
|
|
* a factory easily if you do not want to specialize the behaviour.
|
|
*/
|
|
class IVirtualizationBackendFactory : public IModularFeature
|
|
{
|
|
public:
|
|
/**
|
|
* Creates a new backend instance.
|
|
*
|
|
* @param ProjectName The name of the current project
|
|
* @param ConfigName The name given to the back end in the config ini file
|
|
* @return A new backend instance
|
|
*/
|
|
virtual TUniquePtr<IVirtualizationBackend> CreateInstance(FStringView ProjectName, FStringView ConfigName) = 0;
|
|
|
|
/** Returns the name used to identify the type in config ini files */
|
|
virtual FName GetName() = 0;
|
|
};
|
|
|
|
ENUM_CLASS_FLAGS(IVirtualizationBackend::EOperations);
|
|
|
|
/**
|
|
* This macro is used to generate a backend factories boilerplate code if you do not
|
|
* need anything more than the default behavior.
|
|
* As well as creating the class, a single instance will be created which will register the factory with
|
|
* 'IModularFeatures' so that it is ready for use.
|
|
*
|
|
* @param BackendClass The name of the class derived from 'IVirtualizationBackend' that the factory should create
|
|
* @param The name used in config ini files to reference this backend type.
|
|
*/
|
|
#define UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY(BackendClass, ConfigName) \
|
|
class F##BackendClass##Factory : public IVirtualizationBackendFactory \
|
|
{ \
|
|
public: \
|
|
F##BackendClass##Factory() { IModularFeatures::Get().RegisterModularFeature(FName("VirtualizationBackendFactory"), this); }\
|
|
virtual ~F##BackendClass##Factory() { IModularFeatures::Get().UnregisterModularFeature(FName("VirtualizationBackendFactory"), this); } \
|
|
private: \
|
|
virtual TUniquePtr<IVirtualizationBackend> CreateInstance(FStringView ProjectName, FStringView ConfigName) override \
|
|
{ \
|
|
return MakeUnique<BackendClass>(ProjectName, ConfigName, WriteToString<256>(#ConfigName, TEXT(" - "), ConfigName).ToString()); \
|
|
} \
|
|
virtual FName GetName() override { return FName(#ConfigName); } \
|
|
}; \
|
|
static F##BackendClass##Factory BackendClass##Factory##Instance;
|
|
|
|
#define UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY_LEGACY_IMPL(FactoryName, BackendClass, LegacyConfigName, ConfigName) \
|
|
class F##FactoryName : public IVirtualizationBackendFactory \
|
|
{ \
|
|
public: \
|
|
F##FactoryName(const TCHAR* InLegacyConfigName, const TCHAR* InNewConfigName) \
|
|
: StoredLegacyConfigName(InLegacyConfigName) \
|
|
, StoredNewConfigName(InNewConfigName) \
|
|
{ IModularFeatures::Get().RegisterModularFeature(FName("VirtualizationBackendFactory"), this); }\
|
|
virtual ~F##FactoryName() { IModularFeatures::Get().UnregisterModularFeature(FName("VirtualizationBackendFactory"), this); } \
|
|
private: \
|
|
virtual TUniquePtr<IVirtualizationBackend> CreateInstance(FStringView ProjectName, FStringView ConfigName) override \
|
|
{ \
|
|
UE_LOG(LogVirtualization, Warning, TEXT("Creating a backend via the legacy config name '%s' use '%s' instead"), *StoredLegacyConfigName, *StoredNewConfigName); \
|
|
return MakeUnique<BackendClass>(ProjectName, ConfigName, WriteToString<256>(#ConfigName, TEXT(" - "), ConfigName).ToString()); \
|
|
} \
|
|
virtual FName GetName() override { return FName(#LegacyConfigName); } \
|
|
FString StoredLegacyConfigName; \
|
|
FString StoredNewConfigName;\
|
|
}; \
|
|
static F##FactoryName FactoryName##Instance(TEXT(#LegacyConfigName), TEXT(#ConfigName));
|
|
|
|
/**
|
|
* This macro can be used to change the config name used to create backends while allowing older config file entries to continue working.
|
|
* If this factory is used to instantiate a backend then we will log a warning to the user so that they can update their config file.
|
|
*
|
|
* @param BackendClass The name of the class derived from 'IVirtualizationBackend' that the factory should create.
|
|
* @param LegacyConfigName The old name that 'ConfigName' is now replacing.
|
|
* @param ConfigName The name used in config ini files to reference this backend type.
|
|
*/
|
|
#define UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY_LEGACY(BackendClass, LegacyConfigName, ConfigName) \
|
|
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY_LEGACY_IMPL(BackendClass##LegacyConfigName##To##ConfigName##Factory, BackendClass, LegacyConfigName, ConfigName)
|
|
|
|
} // namespace UE::Virtualization
|