Files
UnrealEngineUWP/Engine/Source/Runtime/NetworkFile/Public/NetworkPlatformFile.h
paul chipchase ecdec9bf35 Add an UE5 specific version EUnrealEngineObjectUE5Version to be used for global changes instead of EUnrealEngineObjectUEVersion. By splitting and storing both version numbers we allow for hypothetical future UE4 changes that will not conflict when merged to UE5.
#rb CarlMagnus.Nordin
#rnx
#tests Ran overnight preflights on several platforms, opened/cooked/staged/ran the oldest version of InfiltratorDemo that can be downloaded (4.11)

### ObjectVersion
- Add a new version enum EUnrealEngineObjectUE5Version.
-- This version number starts at 1000 which leaves more than enough for for EUnrealEngineObjectUEVersion to be expanded
- Even though very few changes (if any at all) to EUnrealEngineObjectUE4Version are expected there is a static assert to make sure that EUnrealEngineObjectUEVersion::AUTOMATIC_VERSION never overtakes EUnrealEngineObjectUE5Version::INITIAL_VERSION.
- Add a struct FPackageFileVersion that wraps around the version numbers and is used to store them instead of raw int32 values which was done before. This should make it easier to add new version numbers in the future if we desire (although this will cause problems in places that serialize the struct directly)

### FPackageFileSummary
- Adding a new entry to CurrentLegacyFileVersion at value -8 which shows the UE5 version being added. This lets us make the changes without needing to submit anything to UE4 Main.
- When loading a package that does not have a UE5 version, it will remain at 0.
- Added ::IsFileVersionTooOld and ::IsFileVersionTooNew to replace hardcoded tests in the code base for version validity. This will make it easier to make changes in the future.
- A few months ago most of the accessors of the version number were deprecated in favour of a version that did not contain the Engine number (ie UE4Ver -> UEVer in Archive) but to work with these changes the renamed methods now will return or accept the version as FPackageFileVersion rather than int32.  The old UE4 methods will remain deprecated and direct licensees to use the new methods.

### Archive
- Now stores the version as a FPackageFileVersion rather than int32

### LinkerLoad
- Reports the larger version number if we detect a higher version number than we support. Note that this could cause an issue if the UE4 version is ever raised but helps keep the code simple.

### AssetData
- Need to add a new version here to manage existing data that only has the UE4 version

### EditorDomain
- We do not need to version the format, we can just invalidate existing editor domain entries via EditorDomainVersion

### EditorServer
- When reporting that a package is too old we report the UE4 version as that is the only version that can be older than VER_UE4_OLDEST_LOADABLE_PACKAGE
- When reporting that a package is too new it can be either the UE4 or the UE5 version so we print them together "UE4Ver|UE5Ver"

### ContentCommandlets
- The min and max resave versions have been kept as a single value, you will not be able to resave against different UE4 and UE5 versions at the same time. It doesn't seem like a useful feature and would greatly increase the complexity of the code.
- We will also only report the file version as a single value.

### ManifestUObject
- This class was setting an older obsolete version on purpose to try and maintain compatibility with older clients so we need to provide a way to create an older UE4 only version that will leave the UE5 version as unset.

### NetworkPlatformFile
- I was unable to test the code path in FNetworkPlatformFile::ProcessServerCachedFilesResponse as I am unsure how to run the game in a mode that will actually use it.
- When reading an older "CookedVersion.txt" that was saved with a single version, the reads will fail and this will count as a version change in the code so that all of the existing files will be deleted. The existing code would not give the user a log message when this happens and given the very small time window where this might happen caused by this change I have opted to leave this alone and not add any additional logging.
- If we do detect a version mismatch we will still only log the version number as a single version.

### CookOnTheFlyServer
- We now add each version number to the IniVersionMap rather than merge the version and license version as a key/value pair. This allows us to a) use both the UE4 and UE5 version numbers b) we now log a warning that the version values don't match when it is changed, previously since it was a key value we would log a warning about an additional setting instead.
-- I also added "vs" to the log message when values are mismatched to make the space between the two values being printed clearer.

#ROBOMERGE-OWNER: paul.chipchase
#ROBOMERGE-AUTHOR: paul.chipchase
#ROBOMERGE-SOURCE: CL 17549459 via CL 17550236 via CL 17550238 via CL 17550582
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v870-17433530)
#ROBOMERGE[STARSHIP]: UE5-Main

[CL 17550583 by paul chipchase in ue5-release-engine-test branch]
2021-09-17 07:04:55 -04:00

391 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "NetworkMessage.h"
#include "ServerTOC.h"
#include "Misc/CoreMisc.h" // included for FSelfRegisteringExec
class FScopedEvent;
DECLARE_LOG_CATEGORY_EXTERN(LogNetworkPlatformFile, Log, All);
/**
* Wrapper to redirect the low level file system to a server
*/
class NETWORKFILE_API FNetworkPlatformFile : public IPlatformFile, public FSelfRegisteringExec
{
friend class FAsyncFileSync;
friend void ReadUnsolicitedFile(int32 InNumUnsolictedFiles, FNetworkPlatformFile& InNetworkFile, IPlatformFile& InInnerPlatformFile, FString& InServerEngineDir, FString& InServerProjectDir);
protected:
/**
* Initialize network platform file give the specified host IP
*
* @param Inner Inner platform file
* @param HostIP host IP address
* @return true if the initialization succeeded, false otherwise
*/
virtual bool InitializeInternal(IPlatformFile* Inner, const TCHAR* HostIP);
virtual void OnFileUpdated(const FString& LocalFilename);
public:
static const TCHAR* GetTypeName()
{
return TEXT("NetworkFile");
}
/** Constructor */
FNetworkPlatformFile();
/** Destructor */
virtual ~FNetworkPlatformFile();
virtual bool ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const override;
virtual bool Initialize(IPlatformFile* Inner, const TCHAR* CmdLine) override;
virtual void InitializeAfterSetActive() override;
virtual IPlatformFile* GetLowerLevel() override
{
return InnerPlatformFile;
}
virtual void SetLowerLevel(IPlatformFile* NewLowerLevel) override
{
InnerPlatformFile = NewLowerLevel;
}
virtual void GetTimeStampPair(const TCHAR* PathA, const TCHAR* PathB, FDateTime& OutTimeStampA, FDateTime& OutTimeStampB)
{
OutTimeStampA = GetTimeStamp(PathA);
OutTimeStampB = GetTimeStamp(PathB);
if (GetLowerLevel() && OutTimeStampA == FDateTime::MinValue() && OutTimeStampB == FDateTime::MinValue())
{
GetLowerLevel()->GetTimeStampPair(PathA, PathB, OutTimeStampA, OutTimeStampB);
}
}
virtual const TCHAR* GetName() const override
{
return FNetworkPlatformFile::GetTypeName();
}
virtual bool IsUsable()
{
return bIsUsable;
}
virtual bool FileExists(const TCHAR* Filename) override
{
FFileInfo Info;
GetFileInfo(Filename, Info);
return Info.FileExists;
}
virtual int64 FileSize(const TCHAR* Filename) override
{
FFileInfo Info;
GetFileInfo(Filename, Info);
return Info.Size;
}
virtual bool DeleteFile(const TCHAR* Filename) override;
virtual bool IsReadOnly(const TCHAR* Filename) override
{
FFileInfo Info;
GetFileInfo(Filename, Info);
return Info.ReadOnly;
}
virtual bool MoveFile(const TCHAR* To, const TCHAR* From) override;
virtual bool SetReadOnly(const TCHAR* Filename, bool bNewReadOnlyValue) override;
virtual FDateTime GetTimeStamp(const TCHAR* Filename) override
{
FFileInfo Info;
GetFileInfo(Filename, Info);
return Info.TimeStamp;
}
virtual void SetTimeStamp(const TCHAR* Filename, FDateTime DateTime) override;
virtual FDateTime GetAccessTimeStamp(const TCHAR* Filename) override
{
FFileInfo Info;
GetFileInfo(Filename, Info);
return Info.AccessTimeStamp;
}
virtual FString GetFilenameOnDisk(const TCHAR* Filename) override
{
return Filename;
}
virtual IFileHandle* OpenRead(const TCHAR* Filename, bool bAllowWrite = false) override;
virtual IFileHandle* OpenWrite(const TCHAR* Filename, bool bAppend = false, bool bAllowRead = false) override;
virtual bool DirectoryExists(const TCHAR* Directory) override;
virtual bool CreateDirectoryTree(const TCHAR* Directory) override;
virtual bool CreateDirectory(const TCHAR* Directory) override;
virtual bool DeleteDirectory(const TCHAR* Directory) override;
virtual FFileStatData GetStatData(const TCHAR* FilenameOrDirectory) override;
virtual bool IterateDirectory(const TCHAR* Directory, IPlatformFile::FDirectoryVisitor& Visitor) override;
virtual bool IterateDirectoryRecursively(const TCHAR* Directory, IPlatformFile::FDirectoryVisitor& Visitor) override;
virtual bool IterateDirectoryStat(const TCHAR* Directory, IPlatformFile::FDirectoryStatVisitor& Visitor) override;
virtual bool IterateDirectoryStatRecursively(const TCHAR* Directory, IPlatformFile::FDirectoryStatVisitor& Visitor) override;
virtual bool DeleteDirectoryRecursively(const TCHAR* Directory) override;
virtual bool CopyFile(const TCHAR* To, const TCHAR* From, EPlatformFileRead ReadFlags = EPlatformFileRead::None, EPlatformFileWrite WriteFlags = EPlatformFileWrite::None) override;
virtual FString ConvertToAbsolutePathForExternalAppForRead( const TCHAR* Filename ) override;
virtual FString ConvertToAbsolutePathForExternalAppForWrite( const TCHAR* Filename ) override;
virtual bool SendMessageToServer(const TCHAR* Message, IPlatformFile::IFileServerMessageHandler* Handler) override;
virtual void Tick() override;
virtual bool SendPayloadAndReceiveResponse(TArray<uint8>& In, TArray<uint8>& Out);
virtual bool ReceiveResponse(TArray<uint8>& Out);
bool SendReadMessage(uint8* Destination, int64 BytesToRead);
bool SendWriteMessage(const uint8* Source, int64 BytesToWrite);
static void ConvertServerFilenameToClientFilename(FString& FilenameToConvert, const FString& InServerEngineDir, const FString& InServerProjectDir, const FString& InServerEnginePlatformExtensionsDir, const FString& InServerProjectPlatformExtensionsDir);
virtual FString GetVersionInfo() const;
//////////////////////////////////////////////////////////////////////////
// FSelfRegisteringExec interface
virtual bool Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) override;
protected:
/**
* Send a heartbeat message to the file server. This will tell it we are alive, as well as
* get back a list of files that have been updated on the server (so we can toss our copy)
*/
virtual void PerformHeartbeat();
virtual void GetFileInfo(const TCHAR* Filename, FFileInfo& Info);
/**
* Convert the given filename from the server to the client version of it
* NOTE: Potentially modifies the input FString!!!!
*
* @param FilenameToConvert Upon input, the server version of the filename. After the call, the client version
*/
virtual void ConvertServerFilenameToClientFilename(FString& FilenameToConvert);
virtual void FillGetFileList(FNetworkFileArchive& Payload);
virtual void ProcessServerInitialResponse(FArrayReader& InResponse, FPackageFileVersion& OutServerPackageVersion, int32& OutServerPackageLicenseeVersion);
virtual void ProcessServerCachedFilesResponse(FArrayReader& InReponse, const FPackageFileVersion& ServerPackageVersion, const int32 ServerPackageLicenseeVersion );
private:
/**
* Returns whether the passed in extension is a video
* extension. Extensions with and without trailing dots are supported.
*
* @param Extension to test.
* @return True if Ext is a video extension. e.g. .mp4
*/
static bool IsMediaExtension(const TCHAR* Ext);
/**
* Returns whether the passed in extension is a an additional (but non asset) cooked file.
*
* @param Extension to test.
* @return True if Ext is a additional cooked file. e.g. .ubulk, .ufont
*/
static bool IsAdditionalCookedFileExtension(const TCHAR* Ext);
/**
* @return true if the path exists in a directory that should always use the local filesystem
* This version does not worry about initialization or thread safety, do not call directly
*/
bool IsInLocalDirectoryUnGuarded(const FString& Filename);
/**
* @return true if the path exists in a directory that should always use the local filesystem
*/
bool IsInLocalDirectory(const FString& Filename);
/**
* Given a filename, make sure the file exists on the local filesystem
*/
void EnsureFileIsLocal(const FString& Filename);
protected:
/**
* Does normal path standardization, and also any extra modifications to make string comparisons against
* the internal directory list work properly.
*/
void MakeStandardNetworkFilename(FString& Filename);
protected:
/** This is true after the DDC directories have been loaded from the DDC system */
bool bHasLoadedDDCDirectories;
/** The file interface to read/write local files with */
IPlatformFile* InnerPlatformFile;
/** This keeps track of what files have been "EnsureFileIsLocal'd" */
TSet<FString> CachedLocalFiles;
/** The server engine dir */
FString ServerEngineDir;
/** The server game dir */
FString ServerProjectDir;
/** The server engine platform extensions dir */
FString ServerEnginePlatformExtensionsDir;
/** The server project platform extensions dir */
FString ServerProjectPlatformExtensionsDir;
/** This is the "TOC" of the server */
FServerTOC ServerFiles;
/** Set of directories that should use the local filesystem */
TArray<FString> LocalDirectories;
FCriticalSection SynchronizationObject;
FCriticalSection LocalDirectoriesCriticalSection;
bool bIsUsable;
int32 FileServerPort;
// the connection flags are passed to the server during GetFileList
// the server may cache them
EConnectionFlags ConnectionFlags;
// Frequency to send heartbeats to server in seconds set to negative number to disable
float HeartbeatFrequency;
// some stats for messuring network platform file performance
double TotalWriteTime; // total non async time spent writing to disk
double TotalNetworkSyncTime; // total non async time spent syncing to network
int32 TotalFilesSynced; // total number files synced from network
int32 TotalUnsolicitedPackages; // total number unsolicited files synced
int32 TotalFilesFoundLocally;
int32 UnsolicitedPackagesHits; // total number of hits from waiting on unsolicited packages
int32 UnsolicitedPackageWaits; // total number of waits on unsolicited packages
double TotalTimeSpentInUnsolicitedPackages; // total time async processing unsolicited packages
double TotalWaitForAsyncUnsolicitedPackages; // total time spent waiting for unsolicited packages
private:
/* Unsolicitied files events */
FScopedEvent *FinishedAsyncNetworkReadUnsolicitedFiles;
FScopedEvent *FinishedAsyncWriteUnsolicitedFiles;
// Our network Transport.
class ITransport* Transport;
static FString MP4Extension;
static FString BulkFileExtension;
static FString ExpFileExtension;
static FString FontFileExtension;
};
class SOCKETS_API FNetworkFileHandle : public IFileHandle
{
FNetworkPlatformFile& Network;
FString Filename;
int64 FilePos;
int64 Size;
bool bWritable;
bool bReadable;
public:
FNetworkFileHandle(FNetworkPlatformFile& InNetwork, const TCHAR* InFilename, int64 InFilePos, int64 InFileSize, bool bWriting)
: Network(InNetwork)
, Filename(InFilename)
, FilePos(InFilePos)
, Size(InFileSize)
, bWritable(bWriting)
, bReadable(!bWriting)
{
}
virtual int64 Tell() override
{
return FilePos;
}
virtual bool Seek(int64 NewPosition) override
{
if (NewPosition >= 0 && NewPosition <= Size)
{
FilePos = NewPosition;
return true;
}
return false;
}
virtual bool SeekFromEnd(int64 NewPositionRelativeToEnd = 0) override
{
return Seek(Size + NewPositionRelativeToEnd);
}
virtual bool Read(uint8* Destination, int64 BytesToRead) override
{
bool Result = false;
if (bReadable && BytesToRead >= 0 && BytesToRead + FilePos <= Size)
{
if (BytesToRead == 0)
{
Result = true;
}
else
{
Result = Network.SendReadMessage(Destination, BytesToRead);
if (Result)
{
FilePos += BytesToRead;
}
}
}
return Result;
}
virtual bool Write(const uint8* Source, int64 BytesToWrite) override
{
bool Result = false;
if (bWritable && BytesToWrite >= 0)
{
if (BytesToWrite == 0)
{
Result = true;
}
else
{
Result = Network.SendWriteMessage(Source, BytesToWrite);
if (Result)
{
FilePos += BytesToWrite;
Size = FMath::Max<int64>(FilePos, Size);
}
}
}
return Result;
}
virtual bool Flush(const bool bFullFlush = false) override
{
return false;
}
virtual bool Truncate(int64 NewSize) override
{
return false;
}
};