You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden
#rb none
==========================
MAJOR FEATURES + CHANGES
==========================
Change 3386262 on 2017/04/10 by Ben.Marsh
Add app-local deployment of DirectX components that are no longer included with newer versions of Windows by default (XAudio 2.7, XInput 1.3). Also add a one-click button to the packaging settings to include the default app-local dependencies, rather than having to specify the path.
Change 3386999 on 2017/04/10 by Ben.Marsh
Plugins: Add support for explicit dependencies from one plugin onto another. Required plugins can be configured in an identical manner to project files, by adding a "Plugins" key to the .uplugin file. Dependencies are automatically built and loaded, and the plugin browser will warn if you try to disable a plugin that something else has a dependency on.
Change 3387073 on 2017/04/10 by Ben.Marsh
Move FLightPropagationRuntimeSettings into the Renderer module, to remove engine dependency on a plugin.
Change 3387988 on 2017/04/11 by Steve.Robb
Comments added to clarify the role of DestructItem and DestructItems.
Change 3388085 on 2017/04/11 by Ben.Marsh
UBT: Fix bEnabled flag on plugin references being ignored. Now collect up all the plugin references in order of priority before creating plugin instances for them. Fixes CIS fail for UT.
Change 3390048 on 2017/04/12 by Richard.Hinckley
#jira UE-43876
Fixed description of Streaming settings (within Project Settings).
Change 3390697 on 2017/04/12 by Steve.Robb
CLASS_PointersDefaultToAutoWeak and CLASS_PointersDefaultToWeak removed.
Change 3390711 on 2017/04/12 by Steve.Robb
AGRESSIVE_ARRAY_FORCEINLINE removed.
Change 3392167 on 2017/04/13 by Robert.Manuszewski
UObject can be added to GC cluster only if all of its Outers can also be added to it.
Fixing asserts caused by components that are added to GC clusters even if their owner actors that can't be in GC clusters.
#jira UE-42948
Change 3392309 on 2017/04/13 by Robert.Manuszewski
When adding objects to clusters after these clusters have been created it's possible to come across objects that are already in the cluster we're adding the object to so instead of crashing, allow it.
Change 3392620 on 2017/04/13 by Ben.Marsh
UGS: Only check for updates every 5 minutes.
Change 3392623 on 2017/04/13 by Ben.Marsh
UGS: Only poll for new changes every 60 seconds.
Change 3392744 on 2017/04/13 by Ben.Marsh
UGS: Query changelist descriptions individually to determine whether changes affect code or content, to hopefully reduce Perforce server load.
Change 3392874 on 2017/04/13 by Ben.Marsh
UGS: Allow specifying regexes in the project config file which filters which changes to be displayed. Useful for changes submitted by build machines, updates to collections, etc...
Change 3392878 on 2017/04/13 by Ben.Marsh
Update UGS to version 1.96
Change 3395635 on 2017/04/17 by Ben.Marsh
UAT: Prefix log output from executing UAT commands through BuildGraph with the name of that command.
Change 3395655 on 2017/04/17 by Ben.Marsh
UAT: Add a command for syncing a DDC over the network (SyncDDC). Allows specifying a maximum size to copy, number of days worth of modified files to copy, and time limit not to be exceeded.
Change 3396989 on 2017/04/17 by Wes.Hunt
CrashReporter configurable tweaks.
* Added QueueWaitingTimeAlertThreshold (used to be hardcoded to 1 min).
- When the queue waiting time gets beyond this many seconds, trigger a slack alert message. Default is 10 min.
- Zero means never alert.
* Added DiskSpaceAvailableAlertInterval (used to be hardcoded to 1 day).
- Interval by which to report disk space availability.
- Default is never (Zero)
* Updated config file to match production config.
#codereview:jin.zhang
Change 3397656 on 2017/04/18 by Ben.Marsh
UBT: Allow modules to opt-out of getting the default include paths from being added, by setting bAddDefaultIncludePaths = false from their build.cs file.
Change 3397677 on 2017/04/18 by Robert.Manuszewski
PR #3167 : Adding more descriptive error text to DetatchLinker error check (by rooneym)
Change 3397722 on 2017/04/18 by Robert.Manuszewski
PR #2252: Increase linker reporting for failed imports (Contributed by FineRedMist)
Change 3397739 on 2017/04/18 by Richard.Hinckley
#jira UE-44100
Fixed SanitizePackageName() to remove double-slash, triple-slash, etc. from package names. Also updated CreatePackage() to call SanitizePackageName() before creating.
Change 3398023 on 2017/04/18 by Ben.Marsh
PR #3105: Cook/package with editor and debugger attached (Contributed by projectgheist)
Change 3398095 on 2017/04/18 by Ben.Marsh
PR #3051: Generate map file from UAT (Contributed by projectgheist)
Change 3398212 on 2017/04/18 by Ben.Marsh
PR #2915: UE-38232: Removed duplicate stats (Contributed by projectgheist)
Change 3399304 on 2017/04/19 by Ben.Marsh
UGS: Prevent editor target files being removed when running custom tools.
Change 3399306 on 2017/04/19 by Robert.Manuszewski
Moved InitPropertiesFromCustomList to UbLueprintGeneratedClass and made it thread safe
Change 3399729 on 2017/04/19 by Steve.Robb
Simple optimization to TBitArray::RemoveAt() when all removed bits are at the end of the array.
RemoveAtSwap() now simply decrements the count instead of calling RemoveAt().
Checks for a positive count added to RemoveAt() and RemoveAtSwap().
Change 3399750 on 2017/04/19 by Jin.Zhang
Order branch alphabetically #RB
Change 3400186 on 2017/04/19 by Steve.Robb
Per-header generated code.
Change 3401458 on 2017/04/20 by Steve.Robb
Static log categories moved out of headers to prevent duplicates when the header is included multiple times.
#jira UE-37507
Change 3401657 on 2017/04/20 by Gil.Gribb
UE4 - Simplified and reworked lock free lists and the task graph bringing all platforms under the same scheme.
Change 3401735 on 2017/04/20 by Gil.Gribb
UE4 - Updated apple platform atomics with a new clang version which is intended to be shared among all clang platforms.
Change 3403362 on 2017/04/21 by Steve.Robb
Algo::Sort() fixed to support C arrays.
Size+count versions of Also::IsSorted() deprecated.
Algo::IsSortedBy() added.
Algo::FindBy() added to allow an element to be found by projection.
Simplifications and generalizations.
Change 3404017 on 2017/04/21 by Ben.Marsh
Fix issue where referenced plugin descriptors were missing from console builds, and prevent monolithic builds from offering to disable missing plugins.
Change 3405299 on 2017/04/24 by Steve.Robb
Clarified the class of the incompatible function in the error message about incompatible BP event specifiers.
#jira UE-35106
Change 3405302 on 2017/04/24 by Ben.Marsh
UBT: Allow excluding documentation from generated project files, by setting <ProjectFileGenerator><bIncludeDocumentation>false</bIncludeDocumentation></ProjectFileGenerator> in the XML configuration file.
Change 3405629 on 2017/04/24 by Ben.Marsh
Rename CPPEnvironment to CppCompileEnvironment, to reflect the class name.
Change 3406431 on 2017/04/24 by Ben.Marsh
UAT: Fix incorrect handling of P4SubmitOptions when multiple values are present.
Change 3406670 on 2017/04/24 by Ben.Marsh
UBT: Enable warnings for classes with virtual functions and no virtual destructor (C4265 on Windows, -fdelete-non-virtual-dtor on Clang).
Change 3407080 on 2017/04/25 by Gil.Gribb
UE4 - Critical fix: Propoerly disambiguate imports with the same name and the same outer name. This fixes an assert: LocalExportIndex.IsNull.
Change 3407486 on 2017/04/25 by Gil.Gribb
UE4 - Made changes so that servers, programs and non-engine executables do not create background or high priority threads.
Change 3407495 on 2017/04/25 by Gil.Gribb
UE4 - Tweaked out XBox and Windows low level file IO.
Change 3407497 on 2017/04/25 by Gil.Gribb
UE4 - Fixed bug in the pak precacher that would result in blocks being discarded too soon, which, in turn, resulted in redudnant reads.
Change 3407705 on 2017/04/25 by Ben.Marsh
Removing most of the junk in DotNETUtilities.
Change 3409701 on 2017/04/26 by Ben.Marsh
Disable another static analyzer warning for third party libraries.
Change 3410074 on 2017/04/26 by Daniel.Lamb
Network platform file runs heart beats and responds to modified file changes.
Cook on the fly server in the editor (COTS) now detects changes to content and notifies client.
Fixed issue with network platform file not using correct sandbox.
#test cook on the side shootergame
Change 3411131 on 2017/04/27 by Steve.Robb
TIsTriviallyDestructible now supports forward-declared enums.
Change 3411186 on 2017/04/27 by Steve.Robb
Fix for #includes in generated code for Within classes which are in a different module from the generated class.
Change 3411917 on 2017/04/27 by Steve.Robb
Fixes to pushing/popping the CPP macro.
Change 3411966 on 2017/04/27 by Steve.Robb
Include spam reduced in generated code.
Change 3412155 on 2017/04/27 by Ben.Marsh
Fix for PVS Studio warning: VFOVInRadians used instead of HFOVInRadians.
Change 3412223 on 2017/04/27 by Ben.Marsh
Fix for PVS-Studio warning: Calling SetHelperA.Num() twice.
Change 3412273 on 2017/04/27 by Ben.Marsh
Fix for PVS-Studio warning: Duplicated variable name.
Change 3412511 on 2017/04/27 by Ben.Marsh
PR #3462: Fixed PVS-Studio issues (Part 1) (Contributed by PaulEremeeff)
Change 3412582 on 2017/04/27 by Ben.Marsh
Fix for PVS-Studio warning: Incorrect variable name in copy/pasted code
Change 3413136 on 2017/04/28 by Robert.Manuszewski
Helper functions for dissolving specific GC clusters
Change 3413310 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Incorrect variable name in copy/pasted code.
Change 3413341 on 2017/04/28 by Gil.Gribb
UE4 - Add prestream capability to allow us to preload always loaded sublevels. Only turned on for Shootergame.
Change 3413351 on 2017/04/28 by Ben.Marsh
Include code analysis macros directly from Platform.h, so that macros are available to everything.
Change 3413352 on 2017/04/28 by Ben.Marsh
Fixing a few more PVS studio warnings.
Change 3413437 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Comparison is always true.
Change 3413759 on 2017/04/28 by Ben.Marsh
Suppressing warnings for PVS-Studio.
Change 3413784 on 2017/04/28 by Ben.Marsh
Fix PVS-Studio warning.
Change 3413898 on 2017/04/28 by Ben.Marsh
Fix PVS-Studio warning: Same conditional is checked twice.
Change 3413915 on 2017/04/28 by Ben.Marsh
Fix PVS-Studio warning: LHS of expression is identical to RHS.
Change 3413989 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: If CurrentGraph->SubGraphs.Num() == 1, it will always enter the first conditional block.
Change 3414053 on 2017/04/28 by Ben.Marsh
More PVS-Studio fixes.
Change 3414062 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Pointer to object goes out of scope without being freed.
Change 3414070 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Fix incorrect condition.
Change 3414071 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Array index is always zero.
Change 3414116 on 2017/04/28 by Ben.Marsh
BuildGraph: Allow marking compile tasks as unsuitable for use with the parallel executor, via an AllowParallelExecutor="false" attribute.
Change 3414160 on 2017/04/28 by Ben.Marsh
Add support for running PVS-Studio through UnrealBuildTool. To use, pass -StaticAnalyzer=PVSStudio to the build command line (similarly, the Visual C++ analyzer can now be invoked using -StaticAnalyzer=VisualCpp). A log file will be written to the Engine/Saved/PVS-Studio or <Project>/Saved/PVS-Studio directory containing diagnostics, which can be opened using the "unparsed output" filter in the PVS-Studio standalone application. High priority warnings are printed to stdout.
Change 3414237 on 2017/04/28 by Ben.Marsh
EC: Allow disabling and enabling the log preprocessor via special markers in the log.
To disable: <-- Suspend Log Parsing -->
To enable: <-- Resume Log Parsing -->
Change 3414343 on 2017/04/28 by Ben.Marsh
UBT: Exclude ThirdParty folders from PVS output.
Change 3414392 on 2017/04/28 by Ben.Marsh
Fix regular strings being casted to BSTRs; BSTRs have a hidden length prefix in the two bytes before the first character, so passing a regular TCHAR* is reading random memory.
Change 3414459 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Object goes out of scope without being freed.
Change 3414495 on 2017/04/28 by Ben.Marsh
Suppress some more PVS-Studio warnings.
Change 3414514 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Testing WorldType being equal to EditorPreview and not equal to Inactive is redundant; changing to match description in comment instead.
Change 3414526 on 2017/04/28 by Ben.Marsh
Fix for PVS-Studio warning: Variable assigned to itself has no effect.
Change 3415183 on 2017/04/29 by Ben.Marsh
Fix conflict in macro definitions for ENABLE_HTTP_FOR_NFS - rename the macro defined by NetworkFile to ENABLE_HTTP_FOR_NF. Hopefully fix CIS.
Change 3415765 on 2017/05/01 by Ben.Marsh
Suppressing PVS-Studio warning to get things building cleanly. Not sure if FContentHelper is being leaked or not.
Change 3415853 on 2017/05/01 by Ben.Marsh
EC: Fix jobs never completing if a "Sync & Build" step fails. Dependent jobs should evaluate their run conditions as soon as the parent step finishes, rather than waiting for child job steps to be created.
Change 3416138 on 2017/05/01 by Ben.Marsh
Fix Fortnite cook failures. Not sure what the exact problem is here, but my hunch is that discarded "const" causes blueprint compile failures due to not being able to connect output pins between nodes for overloaded functions, or something like that.
Change 3416309 on 2017/05/01 by Ben.Marsh
Build: Fix node names for static analysis.
Change 3416360 on 2017/05/01 by Ben.Marsh
UBT: Remove unused arguments to PrepForUATPackageOrDeploy for Windows.
Change 3416398 on 2017/05/01 by Daniel.Lamb
Cook on the fly NetworkFileServerConnection Remove FileModifiedCallback delegate when the connection is closed.
#test Cook on the side shootergame.
Change 3416826 on 2017/05/01 by Daniel.Lamb
Added callback to game when files are requested reload from networkfileserver.
Game will need to unload / reload effected objects.
Working on simple reload capability in shootergame.
#test Cook on the side shootergame with reloading
Change 3417983 on 2017/05/02 by Ben.Marsh
EC: Remove warning for lines not matching p4 tag syntax when running preflights; multi-line descriptions in shelved changelists break this pattern.
Change 3418747 on 2017/05/02 by Steve.Robb
Fix for const pointer properties.
Fix for UHT debugging manifest.
Test added for pointer properties.
Change 3420477 on 2017/05/03 by Gil.Gribb
UE4 - Removed check from windows async IO layer.
[CL 3421020 by Ben Marsh in Main branch]
1385 lines
44 KiB
C++
1385 lines
44 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NetworkPlatformFile.h"
|
|
#include "Templates/ScopedPointer.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ScopedEvent.h"
|
|
#include "HAL/ThreadSafeCounter.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Stats/StatsMisc.h"
|
|
#include "Stats/Stats.h"
|
|
#include "Async/AsyncWork.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/LocalTimestampDirectoryVisitor.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "Misc/PackageName.h"
|
|
|
|
#include "HTTPTransport.h"
|
|
#include "TCPTransport.h"
|
|
|
|
#include "HAL/IPlatformFileModule.h"
|
|
#include "UniquePtr.h"
|
|
|
|
#include "Object.h"
|
|
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
#include "Developer/PackageDependencyInfo/Public/PackageDependencyInfo.h"
|
|
#endif //WITH_UNREAL_DEVELOPER_TOOLS
|
|
|
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogNetworkPlatformFile);
|
|
|
|
FString FNetworkPlatformFile::MP4Extension = TEXT(".mp4");
|
|
FString FNetworkPlatformFile::BulkFileExtension = TEXT(".ubulk");
|
|
FString FNetworkPlatformFile::ExpFileExtension = TEXT(".uexp");
|
|
FString FNetworkPlatformFile::FontFileExtension = TEXT(".ufont");
|
|
|
|
FNetworkPlatformFile::FNetworkPlatformFile()
|
|
: bHasLoadedDDCDirectories(false)
|
|
, InnerPlatformFile(NULL)
|
|
, bIsUsable(false)
|
|
, FinishedAsyncNetworkReadUnsolicitedFiles(NULL)
|
|
, FinishedAsyncWriteUnsolicitedFiles(NULL)
|
|
, Transport(NULL)
|
|
{
|
|
}
|
|
|
|
bool FNetworkPlatformFile::ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const
|
|
{
|
|
FString HostIp;
|
|
return FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIp);
|
|
}
|
|
|
|
ITransport *CreateTransportForHostAddress(const FString &HostIp )
|
|
{
|
|
if ( HostIp.StartsWith(TEXT("tcp://")))
|
|
{
|
|
return new FTCPTransport();
|
|
}
|
|
|
|
if ( HostIp.StartsWith(TEXT("http://")))
|
|
{
|
|
#if ENABLE_HTTP_FOR_NF
|
|
return new FHTTPTransport();
|
|
#endif
|
|
}
|
|
|
|
// no transport specified assuming tcp
|
|
return new FTCPTransport();
|
|
}
|
|
|
|
bool FNetworkPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
|
|
{
|
|
bool bResult = false;
|
|
FString HostIpString;
|
|
if (FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIpString))
|
|
{
|
|
TArray<FString> HostIpList;
|
|
if (HostIpString.ParseIntoArray(HostIpList, TEXT("+"), true) > 0)
|
|
{
|
|
for (int32 HostIpIndex = 0; !bResult && HostIpIndex < HostIpList.Num(); ++HostIpIndex)
|
|
{
|
|
// Try to initialize with each of the IP addresses found in the command line until we
|
|
// get a working one.
|
|
|
|
// find the correct transport for this ip address
|
|
Transport = CreateTransportForHostAddress( HostIpList[HostIpIndex] );
|
|
|
|
UE_LOG(LogNetworkPlatformFile, Warning, TEXT("Created transport for %s."), *HostIpList[HostIpIndex]);
|
|
|
|
if ( Transport )
|
|
{
|
|
bResult = Transport->Initialize( *HostIpList[HostIpIndex] ) && InitializeInternal(Inner, *HostIpList[HostIpIndex]);
|
|
if (bResult)
|
|
break;
|
|
|
|
UE_LOG(LogNetworkPlatformFile, Warning, TEXT("Failed to initialize %s."), *HostIpList[HostIpIndex]);
|
|
|
|
// try a different host might be a different protocol
|
|
delete Transport;
|
|
}
|
|
Transport = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::InitializeInternal(IPlatformFile* Inner, const TCHAR* HostIP)
|
|
{
|
|
// This platform file requires an inner.
|
|
check(Inner != NULL);
|
|
InnerPlatformFile = Inner;
|
|
if (HostIP == NULL)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Error, TEXT("No Host IP specified in the commandline."));
|
|
bIsUsable = false;
|
|
return false;
|
|
}
|
|
|
|
// Save and Intermediate directories are always local
|
|
LocalDirectories.Add(FPaths::EngineDir() / TEXT("Binaries"));
|
|
LocalDirectories.Add(FPaths::EngineIntermediateDir());
|
|
LocalDirectories.Add(FPaths::GameDir() / TEXT("Binaries"));
|
|
LocalDirectories.Add(FPaths::GameIntermediateDir());
|
|
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Backup"));
|
|
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Config"));
|
|
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Logs"));
|
|
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Sandboxes"));
|
|
|
|
if (InnerPlatformFile->GetLowerLevel())
|
|
{
|
|
InnerPlatformFile->GetLowerLevel()->AddLocalDirectories(LocalDirectories);
|
|
}
|
|
else
|
|
{
|
|
InnerPlatformFile->AddLocalDirectories(LocalDirectories);
|
|
}
|
|
|
|
|
|
FNetworkFileArchive Payload(NFS_Messages::Heartbeat);
|
|
FArrayReader Out;
|
|
if (!SendPayloadAndReceiveResponse(Payload,Out))
|
|
bIsUsable = true;
|
|
|
|
|
|
// lets see we can test whether the server is up.
|
|
if (Out.Num())
|
|
{
|
|
FCommandLine::AddToSubprocessCommandline( *FString::Printf( TEXT("-FileHostIP=%s"), HostIP ) );
|
|
bIsUsable = true;
|
|
}
|
|
return bIsUsable;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SendPayloadAndReceiveResponse(TArray<uint8>& In, TArray<uint8>& Out)
|
|
{
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
if ( FinishedAsyncNetworkReadUnsolicitedFiles )
|
|
{
|
|
delete FinishedAsyncNetworkReadUnsolicitedFiles;
|
|
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
|
|
}
|
|
}
|
|
|
|
return Transport->SendPayloadAndReceiveResponse( In, Out );
|
|
}
|
|
|
|
bool FNetworkPlatformFile::ReceiveResponse(TArray<uint8> &Out )
|
|
{
|
|
return Transport->ReceiveResponse( Out );
|
|
}
|
|
|
|
|
|
void FNetworkPlatformFile::InitializeAfterSetActive()
|
|
{
|
|
double NetworkFileStartupTime = 0.0;
|
|
{
|
|
SCOPE_SECONDS_COUNTER(NetworkFileStartupTime);
|
|
|
|
// send the filenames and timestamps to the server
|
|
FNetworkFileArchive Payload(NFS_Messages::GetFileList);
|
|
FillGetFileList(Payload, false);
|
|
|
|
// send the directories over, and wait for a response
|
|
FArrayReader Response;
|
|
if (!SendPayloadAndReceiveResponse(Payload, Response))
|
|
{
|
|
delete Transport;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// receive the cooked version information
|
|
int32 ServerPackageVersion = 0;
|
|
int32 ServerPackageLicenseeVersion = 0;
|
|
ProcessServerInitialResponse(Response, ServerPackageVersion, ServerPackageLicenseeVersion);
|
|
|
|
|
|
/* The server root content directories */
|
|
TArray<FString> ServerRootContentDirectories;
|
|
Response << ServerRootContentDirectories;
|
|
|
|
// receive a list of the cache files and their timestamps
|
|
TMap<FString, FDateTime> ServerCachedFiles;
|
|
Response << ServerCachedFiles;
|
|
|
|
bool bDeleteAllFiles = true;
|
|
// Check the stored cooked version
|
|
FString CookedVersionFile = FPaths::GeneratedConfigDir() / TEXT("CookedVersion.txt");
|
|
|
|
if (InnerPlatformFile->FileExists(*CookedVersionFile) == true)
|
|
{
|
|
IFileHandle* FileHandle = InnerPlatformFile->OpenRead(*CookedVersionFile);
|
|
if (FileHandle != NULL)
|
|
{
|
|
int32 StoredPackageCookedVersion;
|
|
int32 StoredPackageCookedLicenseeVersion;
|
|
if (FileHandle->Read((uint8*)&StoredPackageCookedVersion, sizeof(int32)) == true)
|
|
{
|
|
if (FileHandle->Read((uint8*)&StoredPackageCookedLicenseeVersion, sizeof(int32)) == true)
|
|
{
|
|
if ((ServerPackageVersion == StoredPackageCookedVersion) &&
|
|
(ServerPackageLicenseeVersion == StoredPackageCookedLicenseeVersion))
|
|
{
|
|
bDeleteAllFiles = false;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Display,
|
|
TEXT("Engine version mismatch: Server %d.%d, Stored %d.%d\n"),
|
|
ServerPackageVersion, ServerPackageLicenseeVersion,
|
|
StoredPackageCookedVersion, StoredPackageCookedLicenseeVersion);
|
|
}
|
|
}
|
|
}
|
|
|
|
delete FileHandle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Cooked version file missing: %s\n"), *CookedVersionFile);
|
|
}
|
|
|
|
if (bDeleteAllFiles == true)
|
|
{
|
|
// Make sure the config file exists...
|
|
InnerPlatformFile->CreateDirectoryTree(*(FPaths::GeneratedConfigDir()));
|
|
// Update the cooked version file
|
|
IFileHandle* FileHandle = InnerPlatformFile->OpenWrite(*CookedVersionFile);
|
|
if (FileHandle != NULL)
|
|
{
|
|
FileHandle->Write((const uint8*)&ServerPackageVersion, sizeof(int32));
|
|
FileHandle->Write((const uint8*)&ServerPackageLicenseeVersion, sizeof(int32));
|
|
delete FileHandle;
|
|
}
|
|
}
|
|
|
|
// list of directories to skip
|
|
TArray<FString> DirectoriesToSkip;
|
|
TArray<FString> DirectoriesToNotRecurse;
|
|
// use the timestamp grabbing visitor to get all the content times
|
|
FLocalTimestampDirectoryVisitor Visitor(*InnerPlatformFile, DirectoriesToSkip, DirectoriesToNotRecurse, false);
|
|
|
|
/*TArray<FString> RootContentPaths;
|
|
FPackageName::QueryRootContentPaths(RootContentPaths); */
|
|
for (TArray<FString>::TConstIterator RootPathIt(ServerRootContentDirectories); RootPathIt; ++RootPathIt)
|
|
{
|
|
/*const FString& RootPath = *RootPathIt;
|
|
const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath);*/
|
|
const FString& ContentFolder = *RootPathIt;
|
|
InnerPlatformFile->IterateDirectory( *ContentFolder, Visitor);
|
|
}
|
|
|
|
// delete out of date files using the server cached files
|
|
for (TMap<FString, FDateTime>::TIterator It(ServerCachedFiles); It; ++It)
|
|
{
|
|
bool bDeleteFile = bDeleteAllFiles;
|
|
FString ServerFile = It.Key();
|
|
|
|
// Convert the filename to the client version
|
|
ConvertServerFilenameToClientFilename(ServerFile);
|
|
|
|
// Set it in the visitor file times list
|
|
// If there is any pathing difference (relative path, or whatever) between the server's filelist and the results
|
|
// of platform directory iteration then this will Add a new entry rather than override the existing one. This causes local file deletes
|
|
// and longer loads as we will never see the benefits of local device caching.
|
|
Visitor.FileTimes.Add(ServerFile, FDateTime::MinValue());
|
|
|
|
if (bDeleteFile == false)
|
|
{
|
|
// Check the time stamps...
|
|
// get local time
|
|
FDateTime LocalTime = InnerPlatformFile->GetTimeStamp(*ServerFile);
|
|
// If local time == MinValue than the file does not exist in the cache.
|
|
if (LocalTime != FDateTime::MinValue())
|
|
{
|
|
FDateTime ServerTime = It.Value();
|
|
// delete if out of date
|
|
// We will use 1.0 second as the tolerance to cover any platform differences in resolution
|
|
FTimespan TimeDiff = LocalTime - ServerTime;
|
|
double TimeDiffInSeconds = TimeDiff.GetTotalSeconds();
|
|
bDeleteFile = (TimeDiffInSeconds > 1.0) || (TimeDiffInSeconds < -1.0);
|
|
if (bDeleteFile == true)
|
|
{
|
|
if (InnerPlatformFile->FileExists(*ServerFile) == true)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: TimeDiff %5.3f, %s"), TimeDiffInSeconds, *It.Key());
|
|
}
|
|
else
|
|
{
|
|
// It's a directory
|
|
bDeleteFile = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Keeping cached file: %s, TimeDiff worked out ok"), *ServerFile);
|
|
}
|
|
}
|
|
}
|
|
if (bDeleteFile == true)
|
|
{
|
|
InnerPlatformFile->DeleteFile(*ServerFile);
|
|
}
|
|
}
|
|
|
|
// Any content files we have locally that were not cached, delete them
|
|
for (TMap<FString, FDateTime>::TIterator It(Visitor.FileTimes); It; ++It)
|
|
{
|
|
if (It.Value() != FDateTime::MinValue())
|
|
{
|
|
// This was *not* found in the server file list... delete it
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: %s"), *It.Key());
|
|
InnerPlatformFile->DeleteFile(*It.Key());
|
|
}
|
|
}
|
|
|
|
// make sure we can sync a file
|
|
FString TestSyncFile = FPaths::Combine(*(FPaths::EngineDir()), TEXT("Config/BaseEngine.ini"));
|
|
|
|
InnerPlatformFile->SetReadOnly(*TestSyncFile, false);
|
|
InnerPlatformFile->DeleteFile(*TestSyncFile);
|
|
if (InnerPlatformFile->FileExists(*TestSyncFile))
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not delete file sync test file %s."), *TestSyncFile);
|
|
}
|
|
|
|
EnsureFileIsLocal(TestSyncFile);
|
|
|
|
if (!InnerPlatformFile->FileExists(*TestSyncFile) || InnerPlatformFile->FileSize(*TestSyncFile) < 1)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not sync test file %s."), *TestSyncFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Network file startup time: %5.3f seconds\n"), NetworkFileStartupTime);
|
|
|
|
}
|
|
|
|
FNetworkPlatformFile::~FNetworkPlatformFile()
|
|
{
|
|
if (!GIsRequestingExit) // the socket subsystem is probably already gone, so it will crash if we clean up
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
if ( FinishedAsyncNetworkReadUnsolicitedFiles )
|
|
{
|
|
delete FinishedAsyncNetworkReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
|
|
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
|
|
}
|
|
if ( FinishedAsyncWriteUnsolicitedFiles )
|
|
{
|
|
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing
|
|
FinishedAsyncWriteUnsolicitedFiles = NULL;
|
|
}
|
|
|
|
delete Transport; // close our sockets.
|
|
}
|
|
}
|
|
|
|
bool FNetworkPlatformFile::DeleteFile(const TCHAR* Filename)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// make and send payload (this is how we would do for sending all commands over the network)
|
|
// FNetworkFileArchive Payload(NFS_Messages::DeleteFile);
|
|
// Payload << Filename;
|
|
// return FNFSMessageHeader::WrapAndSendPayload(Payload, FileSocket);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->DeleteFile(Filename);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::MoveFile(const TCHAR* To, const TCHAR* From)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// make and send payload (this is how we would do for sending all commands over the network)
|
|
// FNetworkFileArchive Payload(NFS_Messages::MoveFile);
|
|
// Payload << To << From;
|
|
// return FNFSMessageHeader::WrapAndSendPayload(Payload, FileSocket);
|
|
|
|
FString RelativeFrom = From;
|
|
MakeStandardNetworkFilename(RelativeFrom);
|
|
|
|
// don't copy files in local directories
|
|
if (!IsInLocalDirectory(RelativeFrom))
|
|
{
|
|
// make sure the source file exists here
|
|
EnsureFileIsLocal(RelativeFrom);
|
|
}
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->MoveFile(To, From);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SetReadOnly(const TCHAR* Filename, bool bNewReadOnlyValue)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->SetReadOnly(Filename, bNewReadOnlyValue);
|
|
}
|
|
|
|
void FNetworkPlatformFile::SetTimeStamp(const TCHAR* Filename, FDateTime DateTime)
|
|
{
|
|
// perform a local operation
|
|
InnerPlatformFile->SetTimeStamp(Filename, DateTime);
|
|
}
|
|
|
|
IFileHandle* FNetworkPlatformFile::OpenRead(const TCHAR* Filename, bool bAllowWrite)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
FString RelativeFilename = Filename;
|
|
MakeStandardNetworkFilename(RelativeFilename);
|
|
// don't copy files in local directories
|
|
if (!IsInLocalDirectory(RelativeFilename))
|
|
{
|
|
EnsureFileIsLocal(RelativeFilename);
|
|
}
|
|
|
|
double StartTime;
|
|
float ThisTime;
|
|
|
|
StartTime = FPlatformTime::Seconds();
|
|
IFileHandle* Result = InnerPlatformFile->OpenRead(Filename, bAllowWrite);
|
|
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Open local file %6.2fms"), ThisTime);
|
|
return Result;
|
|
}
|
|
|
|
IFileHandle* FNetworkPlatformFile::OpenWrite(const TCHAR* Filename, bool bAppend, bool bAllowRead)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// just let the physical file interface write the file (we don't write over the network)
|
|
return InnerPlatformFile->OpenWrite(Filename, bAppend, bAllowRead);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::CreateDirectoryTree(const TCHAR* Directory)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->CreateDirectoryTree(Directory);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::CreateDirectory(const TCHAR* Directory)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->CreateDirectory(Directory);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::DeleteDirectory(const TCHAR* Directory)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->DeleteDirectory(Directory);
|
|
}
|
|
|
|
FFileStatData FNetworkPlatformFile::GetStatData(const TCHAR* FilenameOrDirectory)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->GetStatData(FilenameOrDirectory);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IterateDirectory(const TCHAR* InDirectory, IPlatformFile::FDirectoryVisitor& Visitor)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// for .dll, etc searches that don't specify a path, we need to strip off the path
|
|
// before we send it to the visitor
|
|
bool bHadNoPath = InDirectory[0] == 0;
|
|
|
|
// local files go right to the source
|
|
FString RelativeDirectory = InDirectory;
|
|
MakeStandardNetworkFilename(RelativeDirectory);
|
|
if (IsInLocalDirectory(RelativeDirectory))
|
|
{
|
|
return InnerPlatformFile->IterateDirectory(InDirectory, Visitor);
|
|
}
|
|
|
|
// we loop until this is false
|
|
bool RetVal = true;
|
|
|
|
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
|
|
if (ServerDirectory != NULL)
|
|
{
|
|
// loop over the server files and look if they are in this exact directory
|
|
for (FServerTOC::FDirectory::TIterator It(*ServerDirectory); It && RetVal == true; ++It)
|
|
{
|
|
if (FPaths::GetPath(It.Key()) == RelativeDirectory)
|
|
{
|
|
// timestamps of 0 mean directories
|
|
bool bIsDirectory = It.Value() == 0;
|
|
|
|
// visit (stripping off the path if needed)
|
|
RetVal = Visitor.Visit(bHadNoPath ? *FPaths::GetCleanFilename(It.Key()) : *It.Key(), bIsDirectory);
|
|
}
|
|
}
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IterateDirectoryRecursively(const TCHAR* InDirectory, IPlatformFile::FDirectoryVisitor& Visitor)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// local files go right to the source
|
|
FString RelativeDirectory = InDirectory;
|
|
MakeStandardNetworkFilename(RelativeDirectory);
|
|
|
|
if (IsInLocalDirectory(RelativeDirectory))
|
|
{
|
|
return InnerPlatformFile->IterateDirectoryRecursively(InDirectory, Visitor);
|
|
}
|
|
|
|
// we loop until this is false
|
|
bool RetVal = true;
|
|
|
|
for (TMap<FString, FServerTOC::FDirectory*>::TIterator DirIt(ServerFiles.Directories); DirIt && RetVal == true; ++DirIt)
|
|
{
|
|
if (DirIt.Key().StartsWith(RelativeDirectory))
|
|
{
|
|
FServerTOC::FDirectory& ServerDirectory = *DirIt.Value();
|
|
|
|
// loop over the server files and look if they are in this exact directory
|
|
for (FServerTOC::FDirectory::TIterator It(ServerDirectory); It && RetVal == true; ++It)
|
|
{
|
|
// timestamps of 0 mean directories
|
|
bool bIsDirectory = It.Value() == 0;
|
|
|
|
// visit!
|
|
RetVal = Visitor.Visit(*It.Key(), bIsDirectory);
|
|
}
|
|
}
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IterateDirectoryStat(const TCHAR* InDirectory, IPlatformFile::FDirectoryStatVisitor& Visitor)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// for .dll, etc searches that don't specify a path, we need to strip off the path
|
|
// before we send it to the visitor
|
|
bool bHadNoPath = InDirectory[0] == 0;
|
|
|
|
// local files go right to the source
|
|
FString RelativeDirectory = InDirectory;
|
|
MakeStandardNetworkFilename(RelativeDirectory);
|
|
if (IsInLocalDirectory(RelativeDirectory))
|
|
{
|
|
return InnerPlatformFile->IterateDirectoryStat(InDirectory, Visitor);
|
|
}
|
|
|
|
// we loop until this is false
|
|
bool RetVal = true;
|
|
|
|
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
|
|
if (ServerDirectory != NULL)
|
|
{
|
|
// loop over the server files and look if they are in this exact directory
|
|
for (FServerTOC::FDirectory::TIterator It(*ServerDirectory); It && RetVal == true; ++It)
|
|
{
|
|
if (FPaths::GetPath(It.Key()) == RelativeDirectory)
|
|
{
|
|
// timestamps of 0 mean directories
|
|
bool bIsDirectory = It.Value() == 0;
|
|
|
|
// todo: this data is just wrong for most things, but can we afford to get the files from the server to get the correct info? Could the server provide this instead?
|
|
const FFileStatData StatData(
|
|
FDateTime::MinValue(),
|
|
FDateTime::MinValue(),
|
|
(bIsDirectory) ? FDateTime::MinValue() : It.Value(),
|
|
-1, // FileSize
|
|
bIsDirectory,
|
|
true // IsReadOnly
|
|
);
|
|
|
|
// visit (stripping off the path if needed)
|
|
RetVal = Visitor.Visit(bHadNoPath ? *FPaths::GetCleanFilename(It.Key()) : *It.Key(), StatData);
|
|
}
|
|
}
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IterateDirectoryStatRecursively(const TCHAR* InDirectory, IPlatformFile::FDirectoryStatVisitor& Visitor)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// local files go right to the source
|
|
FString RelativeDirectory = InDirectory;
|
|
MakeStandardNetworkFilename(RelativeDirectory);
|
|
|
|
if (IsInLocalDirectory(RelativeDirectory))
|
|
{
|
|
return InnerPlatformFile->IterateDirectoryStatRecursively(InDirectory, Visitor);
|
|
}
|
|
|
|
// we loop until this is false
|
|
bool RetVal = true;
|
|
|
|
for (TMap<FString, FServerTOC::FDirectory*>::TIterator DirIt(ServerFiles.Directories); DirIt && RetVal == true; ++DirIt)
|
|
{
|
|
if (DirIt.Key().StartsWith(RelativeDirectory))
|
|
{
|
|
FServerTOC::FDirectory& ServerDirectory = *DirIt.Value();
|
|
|
|
// loop over the server files and look if they are in this exact directory
|
|
for (FServerTOC::FDirectory::TIterator It(ServerDirectory); It && RetVal == true; ++It)
|
|
{
|
|
// timestamps of 0 mean directories
|
|
bool bIsDirectory = It.Value() == 0;
|
|
|
|
// todo: this data is just wrong for most things, but can we afford to get the files from the server to get the correct info? Could the server provide this instead?
|
|
const FFileStatData StatData(
|
|
FDateTime::MinValue(),
|
|
FDateTime::MinValue(),
|
|
(bIsDirectory) ? FDateTime::MinValue() : It.Value(),
|
|
0, // FileSize
|
|
bIsDirectory,
|
|
true // IsReadOnly
|
|
);
|
|
|
|
// visit!
|
|
RetVal = Visitor.Visit(*It.Key(), StatData);
|
|
}
|
|
}
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::DeleteDirectoryRecursively(const TCHAR* Directory)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->DeleteDirectory(Directory);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::CopyFile(const TCHAR* To, const TCHAR* From, EPlatformFileRead ReadFlags, EPlatformFileWrite WriteFlags)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
FString RelativeFrom = From;
|
|
MakeStandardNetworkFilename(RelativeFrom);
|
|
|
|
// don't copy files in local directories
|
|
if (!IsInLocalDirectory(RelativeFrom))
|
|
{
|
|
// make sure the source file exists here
|
|
EnsureFileIsLocal(RelativeFrom);
|
|
}
|
|
|
|
// perform a local operation
|
|
return InnerPlatformFile->CopyFile(To, From, ReadFlags, WriteFlags);
|
|
}
|
|
|
|
FString FNetworkPlatformFile::ConvertToAbsolutePathForExternalAppForRead( const TCHAR* Filename )
|
|
{
|
|
FString RelativeFrom = Filename;
|
|
MakeStandardNetworkFilename(RelativeFrom);
|
|
|
|
if (!IsInLocalDirectory(RelativeFrom))
|
|
{
|
|
EnsureFileIsLocal(RelativeFrom);
|
|
}
|
|
return InnerPlatformFile->ConvertToAbsolutePathForExternalAppForRead(Filename);
|
|
}
|
|
|
|
FString FNetworkPlatformFile::ConvertToAbsolutePathForExternalAppForWrite( const TCHAR* Filename )
|
|
{
|
|
FString RelativeFrom = Filename;
|
|
MakeStandardNetworkFilename(RelativeFrom);
|
|
|
|
if (!IsInLocalDirectory(RelativeFrom))
|
|
{
|
|
EnsureFileIsLocal(RelativeFrom);
|
|
}
|
|
return InnerPlatformFile->ConvertToAbsolutePathForExternalAppForWrite(Filename);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::DirectoryExists(const TCHAR* Directory)
|
|
{
|
|
if (InnerPlatformFile->DirectoryExists(Directory))
|
|
{
|
|
return true;
|
|
}
|
|
// If there are any syncable files in this directory, consider it existing
|
|
FString RelativeDirectory = Directory;
|
|
MakeStandardNetworkFilename(RelativeDirectory);
|
|
|
|
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
|
|
return ServerDirectory != NULL;
|
|
}
|
|
|
|
void FNetworkPlatformFile::GetFileInfo(const TCHAR* Filename, FFileInfo& Info)
|
|
{
|
|
FString RelativeFilename = Filename;
|
|
MakeStandardNetworkFilename(RelativeFilename);
|
|
|
|
// don't copy files in local directories
|
|
if (!IsInLocalDirectory(RelativeFilename))
|
|
{
|
|
EnsureFileIsLocal(RelativeFilename);
|
|
}
|
|
|
|
const FFileStatData StatData = InnerPlatformFile->GetStatData(Filename);
|
|
Info.FileExists = StatData.bIsValid && !StatData.bIsDirectory;
|
|
Info.ReadOnly = StatData.bIsReadOnly;
|
|
Info.Size = StatData.FileSize;
|
|
Info.TimeStamp = StatData.ModificationTime;
|
|
Info.AccessTimeStamp = StatData.AccessTime;
|
|
}
|
|
|
|
void FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FString& FilenameToConvert)
|
|
{
|
|
FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FilenameToConvert, ServerEngineDir, ServerGameDir);
|
|
}
|
|
|
|
void FNetworkPlatformFile::FillGetFileList(FNetworkFileArchive& Payload, bool bInStreamingFileRequest)
|
|
{
|
|
TArray<FString> TargetPlatformNames;
|
|
FPlatformMisc::GetValidTargetPlatforms(TargetPlatformNames);
|
|
FString GameName = FApp::GetGameName();
|
|
if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
GameName = FPaths::GetProjectFilePath();
|
|
}
|
|
|
|
FString EngineRelPath = FPaths::EngineDir();
|
|
FString EngineRelPluginPath = FPaths::EnginePluginsDir();
|
|
FString GameRelPath = FPaths::GameDir();
|
|
FString GameRelPluginPath = FPaths::GamePluginsDir();
|
|
|
|
TArray<FString> Directories;
|
|
Directories.Add(EngineRelPath);
|
|
Directories.Add(EngineRelPluginPath);
|
|
Directories.Add(GameRelPath);
|
|
Directories.Add(GameRelPluginPath);
|
|
|
|
Payload << TargetPlatformNames;
|
|
Payload << GameName;
|
|
Payload << EngineRelPath;
|
|
Payload << GameRelPath;
|
|
Payload << Directories;
|
|
Payload << bInStreamingFileRequest;
|
|
}
|
|
|
|
void FNetworkPlatformFile::ProcessServerInitialResponse(FArrayReader& InResponse, int32 OutServerPackageVersion, int32 OutServerPackageLicenseeVersion)
|
|
{
|
|
// Receive the cooked version information.
|
|
InResponse << OutServerPackageVersion;
|
|
InResponse << OutServerPackageLicenseeVersion;
|
|
|
|
// receive the server engine and game dir
|
|
InResponse << ServerEngineDir;
|
|
InResponse << ServerGameDir;
|
|
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Server EngineDir = %s"), *ServerEngineDir);
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Local EngineDir = %s"), *FPaths::EngineDir());
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Server GameDir = %s"), *ServerGameDir);
|
|
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Local GameDir = %s"), *FPaths::GameDir());
|
|
|
|
// Receive a list of files and their timestamps.
|
|
TMap<FString, FDateTime> ServerFileMap;
|
|
InResponse << ServerFileMap;
|
|
for (TMap<FString, FDateTime>::TIterator It(ServerFileMap); It; ++It)
|
|
{
|
|
FString ServerFile = It.Key();
|
|
ConvertServerFilenameToClientFilename(ServerFile);
|
|
ServerFiles.AddFileOrDirectory(ServerFile, It.Value());
|
|
}
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SendReadMessage(uint8* Destination, int64 BytesToRead)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SendWriteMessage(const uint8* Source, int64 BytesToWrite)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SendMessageToServer(const TCHAR* Message, IPlatformFile::IFileServerMessageHandler* Handler)
|
|
{
|
|
// handle the recompile shaders message
|
|
// @todo: Maybe we should just send the string message to the server, but then we'd have to
|
|
// handle the return from the server in a generic way
|
|
if (FCString::Stricmp(Message, TEXT("RecompileShaders")) == 0)
|
|
{
|
|
FNetworkFileArchive Payload(NFS_Messages::RecompileShaders);
|
|
|
|
// let the handler fill out the object
|
|
Handler->FillPayload(Payload);
|
|
|
|
FArrayReader Response;
|
|
if (!SendPayloadAndReceiveResponse(Payload, Response))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// locally delete any files that were modified on the server, so that any read will recache the file
|
|
// this has to be done in this class, not in the Handler (which can't access these members)
|
|
TArray<FString> ModifiedFiles;
|
|
Response << ModifiedFiles;
|
|
|
|
if( InnerPlatformFile != NULL )
|
|
{
|
|
for (int32 Index = 0; Index < ModifiedFiles.Num(); Index++)
|
|
{
|
|
InnerPlatformFile->DeleteFile(*ModifiedFiles[Index]);
|
|
CachedLocalFiles.Remove(ModifiedFiles[Index]);
|
|
ServerFiles.AddFileOrDirectory(ModifiedFiles[Index], FDateTime::UtcNow());
|
|
}
|
|
}
|
|
|
|
|
|
// let the handler process the response directly
|
|
Handler->ProcessResponse(Response);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static FThreadSafeCounter OutstandingAsyncWrites;
|
|
|
|
class FAsyncNetworkWriteWorker : public FNonAbandonableTask
|
|
{
|
|
public:
|
|
/** Filename To write to**/
|
|
FString Filename;
|
|
/** An archive to read the file contents from */
|
|
FArchive* FileArchive;
|
|
/** timestamp for the file **/
|
|
FDateTime ServerTimeStamp;
|
|
IPlatformFile& InnerPlatformFile;
|
|
FScopedEvent* Event;
|
|
|
|
uint8 Buffer[128 * 1024];
|
|
|
|
/** Constructor
|
|
*/
|
|
FAsyncNetworkWriteWorker(const TCHAR* InFilename, FArchive* InArchive, FDateTime InServerTimeStamp, IPlatformFile* InInnerPlatformFile, FScopedEvent* InEvent)
|
|
: Filename(InFilename)
|
|
, FileArchive(InArchive)
|
|
, ServerTimeStamp(InServerTimeStamp)
|
|
, InnerPlatformFile(*InInnerPlatformFile)
|
|
, Event(InEvent)
|
|
{
|
|
}
|
|
|
|
/** Write the file */
|
|
void DoWork()
|
|
{
|
|
if (InnerPlatformFile.FileExists(*Filename))
|
|
{
|
|
InnerPlatformFile.SetReadOnly(*Filename, false);
|
|
InnerPlatformFile.DeleteFile(*Filename);
|
|
}
|
|
// Read FileSize first so that the correct amount of data is read from the archive
|
|
// before exiting this worker.
|
|
uint64 FileSize;
|
|
*FileArchive << FileSize;
|
|
if (ServerTimeStamp != FDateTime::MinValue()) // if the file didn't actually exist on the server, don't create a zero byte file
|
|
{
|
|
FString TempFilename = Filename + TEXT(".tmp");
|
|
InnerPlatformFile.CreateDirectoryTree(*FPaths::GetPath(Filename));
|
|
{
|
|
TUniquePtr<IFileHandle> FileHandle;
|
|
FileHandle.Reset(InnerPlatformFile.OpenWrite(*TempFilename));
|
|
|
|
if (!FileHandle)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not open file for writing '%s'."), *TempFilename);
|
|
}
|
|
|
|
// now write the file from bytes pulled from the archive
|
|
// read/write a chunk at a time
|
|
uint64 RemainingData = FileSize;
|
|
while (RemainingData)
|
|
{
|
|
// read next chunk from archive
|
|
uint32 LocalSize = FPlatformMath::Min<uint32>(ARRAY_COUNT(Buffer), RemainingData);
|
|
FileArchive->Serialize(Buffer, LocalSize);
|
|
// write it out
|
|
if (!FileHandle->Write(Buffer, LocalSize))
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not write '%s'."), *TempFilename);
|
|
}
|
|
|
|
// decrement how much is left
|
|
RemainingData -= LocalSize;
|
|
}
|
|
|
|
// delete async write archives
|
|
if (Event)
|
|
{
|
|
delete FileArchive;
|
|
}
|
|
|
|
if (InnerPlatformFile.FileSize(*TempFilename) != FileSize)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Did not write '%s'."), *TempFilename);
|
|
}
|
|
}
|
|
|
|
// rename from temp filename to real filename
|
|
InnerPlatformFile.MoveFile(*Filename, *TempFilename);
|
|
|
|
// now set the server's timestamp on the local file (so we can make valid comparisons)
|
|
InnerPlatformFile.SetTimeStamp(*Filename, ServerTimeStamp);
|
|
|
|
FDateTime CheckTime = InnerPlatformFile.GetTimeStamp(*Filename);
|
|
if (CheckTime < ServerTimeStamp)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could Not Set Timestamp '%s' %s < %s."), *Filename, *CheckTime.ToString(), *ServerTimeStamp.ToString());
|
|
}
|
|
}
|
|
if (Event)
|
|
{
|
|
if (OutstandingAsyncWrites.Decrement() == 0)
|
|
{
|
|
Event->Trigger(); // last file, fire trigger
|
|
}
|
|
}
|
|
}
|
|
|
|
FORCEINLINE TStatId GetStatId() const
|
|
{
|
|
return TStatId();
|
|
//RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncNetworkWriteWorker, STATGROUP_ThreadPoolAsyncTasks);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Write a file async or sync, with the data coming from a TArray or an FArchive/Filesize
|
|
*/
|
|
void SyncWriteFile(FArchive* Archive, const FString& Filename, FDateTime ServerTimeStamp, IPlatformFile& InnerPlatformFile)
|
|
{
|
|
FScopedEvent* NullEvent = NULL;
|
|
(new FAutoDeleteAsyncTask<FAsyncNetworkWriteWorker>(*Filename, Archive, ServerTimeStamp, &InnerPlatformFile, NullEvent))->StartSynchronousTask();
|
|
}
|
|
|
|
void AsyncWriteFile(FArchive* Archive, const FString& Filename, FDateTime ServerTimeStamp, IPlatformFile& InnerPlatformFile, FScopedEvent* Event = NULL)
|
|
{
|
|
(new FAutoDeleteAsyncTask<FAsyncNetworkWriteWorker>(*Filename, Archive, ServerTimeStamp, &InnerPlatformFile, Event))->StartBackgroundTask();
|
|
}
|
|
|
|
void AsyncReadUnsolicitedFiles(int32 InNumUnsolictedFiles, FNetworkPlatformFile& InNetworkFile, IPlatformFile& InInnerPlatformFile, FString& InServerEngineDir, FString& InServerGameDir, FScopedEvent *InNetworkDoneEvent, FScopedEvent *InWritingDoneEvent)
|
|
{
|
|
class FAsyncReadUnsolicitedFile : public FNonAbandonableTask
|
|
{
|
|
public:
|
|
int32 NumUnsolictedFiles;
|
|
FNetworkPlatformFile& NetworkFile;
|
|
IPlatformFile& InnerPlatformFile;
|
|
FString ServerEngineDir;
|
|
FString ServerGameDir;
|
|
FScopedEvent* NetworkDoneEvent; // finished using the network
|
|
FScopedEvent* WritingDoneEvent; // finished writing the files to disk
|
|
|
|
FAsyncReadUnsolicitedFile(int32 In_NumUnsolictedFiles, FNetworkPlatformFile* In_NetworkFile, IPlatformFile* In_InnerPlatformFile, FString& In_ServerEngineDir, FString& In_ServerGameDir, FScopedEvent *In_NetworkDoneEvent, FScopedEvent *In_WritingDoneEvent )
|
|
: NumUnsolictedFiles(In_NumUnsolictedFiles)
|
|
, NetworkFile(*In_NetworkFile)
|
|
, InnerPlatformFile(*In_InnerPlatformFile)
|
|
, ServerEngineDir(In_ServerEngineDir)
|
|
, ServerGameDir(In_ServerGameDir)
|
|
, NetworkDoneEvent(In_NetworkDoneEvent)
|
|
, WritingDoneEvent(In_WritingDoneEvent)
|
|
{
|
|
}
|
|
|
|
/** Write the file */
|
|
void DoWork()
|
|
{
|
|
OutstandingAsyncWrites.Add( NumUnsolictedFiles );
|
|
for (int32 Index = 0; Index < NumUnsolictedFiles; Index++)
|
|
{
|
|
FArrayReader* UnsolictedResponse = new FArrayReader;
|
|
if (!NetworkFile.ReceiveResponse(*UnsolictedResponse))
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Receive failure!"));
|
|
return;
|
|
}
|
|
FString UnsolictedReplyFile;
|
|
*UnsolictedResponse << UnsolictedReplyFile;
|
|
|
|
if (!UnsolictedReplyFile.IsEmpty())
|
|
{
|
|
FNetworkPlatformFile::ConvertServerFilenameToClientFilename(UnsolictedReplyFile, ServerEngineDir, ServerGameDir);
|
|
// get the server file timestamp
|
|
FDateTime UnsolictedServerTimeStamp;
|
|
*UnsolictedResponse << UnsolictedServerTimeStamp;
|
|
|
|
// write the file by pulling out of the FArrayReader
|
|
AsyncWriteFile(UnsolictedResponse, UnsolictedReplyFile, UnsolictedServerTimeStamp, InnerPlatformFile, WritingDoneEvent);
|
|
}
|
|
}
|
|
NetworkDoneEvent->Trigger();
|
|
}
|
|
|
|
FORCEINLINE TStatId GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncReadUnsolicitedFile, STATGROUP_ThreadPoolAsyncTasks);
|
|
}
|
|
};
|
|
|
|
(new FAutoDeleteAsyncTask<FAsyncReadUnsolicitedFile>(InNumUnsolictedFiles, &InNetworkFile, &InInnerPlatformFile, InServerEngineDir, InServerGameDir, InNetworkDoneEvent, InWritingDoneEvent))->StartSynchronousTask();
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IsMediaExtension(const TCHAR* Ext)
|
|
{
|
|
if (*Ext != TEXT('.'))
|
|
{
|
|
return MP4Extension.EndsWith(Ext);
|
|
}
|
|
else
|
|
{
|
|
return MP4Extension == Ext;
|
|
}
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IsAdditionalCookedFileExtension(const TCHAR* Ext)
|
|
{
|
|
if (*Ext != TEXT('.'))
|
|
{
|
|
return BulkFileExtension.EndsWith(Ext) || FontFileExtension.EndsWith(Ext) || ExpFileExtension.EndsWith(Ext);
|
|
}
|
|
else
|
|
{
|
|
return BulkFileExtension == Ext || FontFileExtension == Ext || ExpFileExtension == Ext;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a filename, make sure the file exists on the local filesystem
|
|
*/
|
|
|
|
void FNetworkPlatformFile::EnsureFileIsLocal(const FString& Filename)
|
|
{
|
|
double StartTime;
|
|
float ThisTime;
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
// have we already cached this file?
|
|
if (CachedLocalFiles.Find(Filename) != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (FinishedAsyncNetworkReadUnsolicitedFiles)
|
|
{
|
|
delete FinishedAsyncNetworkReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
|
|
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
|
|
}
|
|
if (FinishedAsyncWriteUnsolicitedFiles)
|
|
{
|
|
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing to disk
|
|
FinishedAsyncWriteUnsolicitedFiles = NULL;
|
|
}
|
|
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Lock and wait for old async writes %6.2fms"), ThisTime);
|
|
|
|
// even if an error occurs later, we still want to remember not to try again
|
|
CachedLocalFiles.Add(Filename);
|
|
UE_LOG(LogNetworkPlatformFile, Warning, TEXT("Cached file %s"), *Filename)
|
|
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
// no need to read it if it already exists
|
|
// @todo: Handshake with server to delete files that are out of date
|
|
if (InnerPlatformFile->FileExists(*Filename))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Check for local file %6.2fms - %s"), ThisTime, *Filename);
|
|
|
|
// this is a bit of a waste if we aren't doing cook on the fly, but we assume missing asset files are relatively rare
|
|
FString Extension = FPaths::GetExtension(Filename, true);
|
|
bool bIsCookable = GConfig && GConfig->IsReadyForUse() && (FPackageName::IsPackageExtension(*Extension) || IsMediaExtension(*Extension) || IsAdditionalCookedFileExtension(*Extension));
|
|
|
|
// we only copy files that actually exist on the server, can greatly reduce network traffic for, say,
|
|
// the INT file each package tries to load
|
|
if (!bIsCookable && (ServerFiles.FindFile(Filename) == NULL))
|
|
{
|
|
// Uncomment this to have the server file list dumped
|
|
// the first time a file requested is not found.
|
|
#if 0
|
|
static bool sb_DumpedServer = false;
|
|
if (sb_DumpedServer == false)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Dumping server files... %s not found\n"), *Filename);
|
|
for (TMap<FString, FServerTOC::FDirectory*>::TIterator ServerDumpIt(ServerFiles.Directories); ServerDumpIt; ++ServerDumpIt)
|
|
{
|
|
FServerTOC::FDirectory& Directory = *ServerDumpIt.Value();
|
|
for (FServerTOC::FDirectory::TIterator DirDumpIt(Directory); DirDumpIt; ++DirDumpIt)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%10s - %s\n"), *(DirDumpIt.Value().ToString()), *(DirDumpIt.Key()));
|
|
}
|
|
}
|
|
sb_DumpedServer = true;
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// send the filename over (cast away const here because we know this << will not modify the string)
|
|
FNetworkFileArchive Payload(NFS_Messages::SyncFile);
|
|
Payload << (FString&)Filename;
|
|
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
// allocate array reader on the heap, because the SyncWriteFile function will delete it
|
|
FArrayReader Response;
|
|
if (!SendPayloadAndReceiveResponse(Payload, Response))
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Receive failure!"));
|
|
return;
|
|
}
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Send and receive %6.2fms"), ThisTime);
|
|
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
FString ReplyFile;
|
|
Response << ReplyFile;
|
|
ConvertServerFilenameToClientFilename(ReplyFile);
|
|
check(ReplyFile == Filename);
|
|
|
|
// get the server file timestamp
|
|
FDateTime ServerTimeStamp;
|
|
Response << ServerTimeStamp;
|
|
|
|
// write the file in chunks, synchronously
|
|
SyncWriteFile(&Response, ReplyFile, ServerTimeStamp, *InnerPlatformFile);
|
|
|
|
int32 NumUnsolictedFiles;
|
|
Response << NumUnsolictedFiles;
|
|
|
|
if (NumUnsolictedFiles)
|
|
{
|
|
check( FinishedAsyncNetworkReadUnsolicitedFiles == NULL );
|
|
check( FinishedAsyncWriteUnsolicitedFiles == NULL );
|
|
FinishedAsyncNetworkReadUnsolicitedFiles = new FScopedEvent;
|
|
FinishedAsyncWriteUnsolicitedFiles = new FScopedEvent;
|
|
AsyncReadUnsolicitedFiles(NumUnsolictedFiles, *this, *InnerPlatformFile, ServerEngineDir, ServerGameDir, FinishedAsyncNetworkReadUnsolicitedFiles, FinishedAsyncWriteUnsolicitedFiles);
|
|
}
|
|
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Write file to local %6.2fms"), ThisTime);
|
|
}
|
|
|
|
static FString NetworkPlatformFileEndChop(TEXT("/"));
|
|
void FNetworkPlatformFile::MakeStandardNetworkFilename(FString& Filename)
|
|
{
|
|
FPaths::MakeStandardFilename(Filename);
|
|
Filename.RemoveFromEnd(NetworkPlatformFileEndChop, ESearchCase::CaseSensitive);
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IsInLocalDirectoryUnGuarded(const FString& Filename)
|
|
{
|
|
// cache the directory of the input file
|
|
FString Directory = FPaths::GetPath(Filename);
|
|
|
|
// look if the file is in a local directory
|
|
for (int32 DirIndex = 0; DirIndex < LocalDirectories.Num(); DirIndex++)
|
|
{
|
|
if (Directory.StartsWith(LocalDirectories[DirIndex]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if not local, talk to the server
|
|
return false;
|
|
}
|
|
|
|
bool FNetworkPlatformFile::IsInLocalDirectory(const FString& Filename)
|
|
{
|
|
if (!bHasLoadedDDCDirectories)
|
|
{
|
|
// need to be careful here to avoid initializing the DDC from the wrong thread or using LocalDirectories while it is being initialized
|
|
FScopeLock ScopeLock(&LocalDirectoriesCriticalSection);
|
|
|
|
if (IsInGameThread() && GConfig && GConfig->IsReadyForUse())
|
|
{
|
|
// one time DDC directory initialization
|
|
// add any DDC directories to our list of local directories (local = inner platform file, it may
|
|
// actually live on a server, but it will use the platform's file system)
|
|
if (GetDerivedDataCache())
|
|
{
|
|
TArray<FString> DdcDirectories;
|
|
|
|
GetDerivedDataCacheRef().GetDirectories(DdcDirectories);
|
|
|
|
LocalDirectories.Append(DdcDirectories);
|
|
}
|
|
|
|
FPlatformMisc::MemoryBarrier();
|
|
bHasLoadedDDCDirectories = true;
|
|
}
|
|
|
|
return IsInLocalDirectoryUnGuarded(Filename);
|
|
}
|
|
|
|
// once the DDC is initialized, we don't need to lock a critical section anymore
|
|
return IsInLocalDirectoryUnGuarded(Filename);
|
|
}
|
|
|
|
void FNetworkPlatformFile::PerformHeartbeat()
|
|
{
|
|
// send the filename over (cast away const here because we know this << will not modify the string)
|
|
FNetworkFileArchive Payload(NFS_Messages::Heartbeat);
|
|
|
|
|
|
|
|
// send the filename over
|
|
FArrayReader Response;
|
|
if (!SendPayloadAndReceiveResponse(Payload, Response))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// get any files that have been modified on the server -
|
|
TArray<FString> UpdatedFiles;
|
|
Response << UpdatedFiles;
|
|
|
|
// delete any outdated files from the client
|
|
// @todo: This may need a critical section around all calls to LowLevel in the other functions
|
|
// because we don't want to delete files while other threads are using them!
|
|
|
|
TArray<FString> PackageNames;
|
|
for (int32 FileIndex = 0; FileIndex < UpdatedFiles.Num(); FileIndex++)
|
|
{
|
|
// clean up the linkers for this package
|
|
FString LocalFileName = UpdatedFiles[FileIndex];
|
|
ConvertServerFilenameToClientFilename( LocalFileName );
|
|
|
|
UE_LOG(LogNetworkPlatformFile, Log, TEXT("Server updated file '%s', deleting local copy %s"), *UpdatedFiles[FileIndex], *LocalFileName);
|
|
|
|
FString PackageName;
|
|
if ( FPackageName::TryConvertFilenameToLongPackageName(LocalFileName, PackageName) )
|
|
{
|
|
PackageNames.Add(PackageName );
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Log, TEXT("Unable to convert filename to package name %s"), *LocalFileName);
|
|
}
|
|
|
|
if (InnerPlatformFile->FileExists( *LocalFileName ) && InnerPlatformFile->DeleteFile(*LocalFileName) == false)
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Error, TEXT("Failed to delete %s, someone is probably accessing without FNetworkPlatformFile, or we need better thread protection"), *UpdatedFiles[FileIndex]);
|
|
}
|
|
CachedLocalFiles.Remove(LocalFileName);
|
|
ServerFiles.AddFileOrDirectory(LocalFileName, FDateTime::UtcNow());
|
|
}
|
|
|
|
if ( PackageNames.Num() > 0 )
|
|
{
|
|
FCoreUObjectDelegates::NetworkFileRequestPackageReload.ExecuteIfBound(PackageNames);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FString& FilenameToConvert, const FString& InServerEngineDir, const FString& InServerGameDir)
|
|
{
|
|
if (FilenameToConvert.StartsWith(InServerEngineDir))
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*InServerEngineDir, *(FPaths::EngineDir()));
|
|
}
|
|
else if (FilenameToConvert.StartsWith(InServerGameDir))
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*InServerGameDir, *(FPaths::GameDir()));
|
|
}
|
|
}
|
|
|
|
void FNetworkPlatformFile::Tick()
|
|
{
|
|
// try send a heart beat every 5 seconds as long as we are not async loading
|
|
static double StartTime = FPlatformTime::Seconds();
|
|
|
|
bool bShouldPerformHeartbeat = true;
|
|
if ((FPlatformTime::Seconds() - StartTime) > 5.0f )
|
|
{
|
|
|
|
|
|
if (IsAsyncLoading() && bShouldPerformHeartbeat)
|
|
{
|
|
bShouldPerformHeartbeat = false;
|
|
}
|
|
|
|
{
|
|
FScopeLock S(&SynchronizationObject);
|
|
if (FinishedAsyncNetworkReadUnsolicitedFiles && bShouldPerformHeartbeat)
|
|
{
|
|
if ( FinishedAsyncNetworkReadUnsolicitedFiles->IsReady() )
|
|
{
|
|
delete FinishedAsyncNetworkReadUnsolicitedFiles;
|
|
FinishedAsyncNetworkReadUnsolicitedFiles = nullptr;
|
|
}
|
|
else
|
|
{
|
|
bShouldPerformHeartbeat = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bShouldPerformHeartbeat )
|
|
{
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
//DeleteLoaders();
|
|
PerformHeartbeat();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Module for the network file
|
|
*/
|
|
class FNetworkFileModule : public IPlatformFileModule
|
|
{
|
|
public:
|
|
virtual IPlatformFile* GetPlatformFile() override
|
|
{
|
|
static TUniquePtr<IPlatformFile> AutoDestroySingleton = MakeUnique<FNetworkPlatformFile>();
|
|
return AutoDestroySingleton.Get();
|
|
}
|
|
};
|
|
IMPLEMENT_MODULE(FNetworkFileModule, NetworkFile);
|