// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "MapPakDownloader.h" #include "MapPakDownloaderLog.h" #include "Engine/GameViewportClient.h" #include "Engine/Engine.h" #include "EngineGlobals.h" #include #include "Misc/Guid.h" #include "Misc/App.h" #include "Misc/CoreDelegates.h" #include "Misc/PackageName.h" #include "Misc/ConfigCacheIni.h" #include "UObject/UObjectIterator.h" #include "UObject/UObjectHash.h" #include "HAL/PlatformFilemanager.h" #include "HAL/PlatformFile.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SProgressBar.h" DECLARE_DELEGATE_OneParam(FDelegateFString, FString) DECLARE_DELEGATE_OneParam(FDelegateInt32, int32) // avoid IHttpModule class FEmscriptenHttpFileRequest { public: typedef int32 Handle; private: FString FileName; FString Url; FDelegateFString OnLoadCallBack; FDelegateInt32 OnErrorCallBack; FDelegateInt32 OnProgressCallBack; public: FEmscriptenHttpFileRequest() {} static void OnLoad(uint32 Var, void* Arg, const ANSICHAR* FileName) { FEmscriptenHttpFileRequest* Request = reinterpret_cast(Arg); Request->OnLoadCallBack.ExecuteIfBound(FString(ANSI_TO_TCHAR(FileName))); } static void OnError(uint32 Var,void* Arg, int32 ErrorCode) { FEmscriptenHttpFileRequest* Request = reinterpret_cast(Arg); Request->OnErrorCallBack.ExecuteIfBound(ErrorCode); } static void OnProgress(uint32 Var,void* Arg, int32 Progress) { FEmscriptenHttpFileRequest* Request = reinterpret_cast(Arg); Request->OnProgressCallBack.ExecuteIfBound(Progress); } void Process() { UE_LOG(LogMapPakDownloader, Warning, TEXT("Starting Download for %s"), *FileName); emscripten_async_wget2( TCHAR_TO_ANSI(*(Url + FString(TEXT("?rand=")) + FGuid::NewGuid().ToString())), TCHAR_TO_ANSI(*FileName), TCHAR_TO_ANSI(TEXT("GET")), TCHAR_TO_ANSI(TEXT("")), this, &FEmscriptenHttpFileRequest::OnLoad, &FEmscriptenHttpFileRequest::OnError, &FEmscriptenHttpFileRequest::OnProgress ); } void SetFileName(const FString& InFileName) { FileName = InFileName; } FString GetFileName() { return FileName; } void SetUrl(const FString& InUrl) { Url = InUrl; } void SetOnLoadCallBack(const FDelegateFString& CallBack) { OnLoadCallBack = CallBack; } void SetOnErrorCallBack(const FDelegateInt32& CallBack) { OnErrorCallBack = CallBack; } void SetOnProgressCallBack(const FDelegateInt32& CallBack) { OnProgressCallBack = CallBack; } }; class FloatOption : public TSharedFromThis { public: FloatOption() : Value(0.0f) {} void SetFloat(float InFloat) { Value = InFloat; } TOptional GetFloat() const { return Value; } private: float Value; }; FMapPakDownloader::FMapPakDownloader() :IsTransitionLevel(false) {} bool FMapPakDownloader::Init() { // figure out where we are hosted ANSICHAR *LocationString = (ANSICHAR*)EM_ASM_INT_V({ var hoststring = location.href.substring(0, location.href.lastIndexOf('/')); var buffer = Module._malloc(hoststring.length); Module.writeAsciiToMemory(hoststring, buffer); return buffer; }); HostName = FString(ANSI_TO_TCHAR(LocationString)); PakLocation = FString(FApp::GetProjectName()) / FString(TEXT("Content")) / FString(TEXT("Paks")); // Create directory. IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); PlatformFile.CreateDirectory(*PakLocation); ProgressContainter = MakeShareable(new FloatOption()); // Thin progress bar. Change this widget if you want a custom loading screen. LoadingWidget = SNew(SBox) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(SBox) .VAlign(VAlign_Fill) .HAlign(HAlign_Fill) .HeightOverride(FOptionalSize(4)) .WidthOverride(FOptionalSize(300)) [ SNew(SProgressBar) .Percent(ProgressContainter->AsShared(), &FloatOption::GetFloat) .BorderPadding(FVector2D(0, 0)) .BarFillType(EProgressBarFillType::LeftToRight) ] ]; // Hook up PostLoad FCoreUObjectDelegates::PostLoadMapWithWorld.AddLambda([=](UWorld*) { { if (IsTransitionLevel) { GEngine->GameViewport->AddViewportWidgetContent(LoadingWidget->AsShared()); } } }); return true; } void FMapPakDownloader::CachePak() { UE_LOG(LogMapPakDownloader, Warning, TEXT("Caching Dependencies for %s"), *MapToCache); FString PakName = FPackageName::GetShortName(FName(*MapToCache)) + ".pak"; FString DeltaPakName = FPackageName::GetShortName(FName(*LastMap)) + TEXT("_") + FPackageName::GetShortName(FName(*MapToCache)) + ".pak"; FEmscriptenHttpFileRequest* PakRequest = new FEmscriptenHttpFileRequest; // can't use shared ptrs. FDelegateFString OnFileDownloaded = FDelegateFString::CreateLambda([=](FString Name){ UE_LOG(LogMapPakDownloader, Warning, TEXT("%s download complete!"), *PakRequest->GetFileName()); UE_LOG(LogMapPakDownloader, Warning, TEXT("Mounting..."), *Name); FCoreDelegates::OnMountPak.Execute(Name, 0, nullptr); UE_LOG(LogMapPakDownloader, Warning, TEXT("%s Mounted!"), *Name); // Get hold of the world. TObjectIterator It; UWorld* ItWorld = *It; if (IsTransitionLevel) GEngine->GameViewport->RemoveViewportWidgetContent(LoadingWidget->AsShared()); UE_LOG(LogMapPakDownloader, Warning, TEXT("Travel to %s"), *MapToCache); // Make engine Travel to the cached Map. GEngine->SetClientTravel(ItWorld, *MapToCache, TRAVEL_Absolute); IsTransitionLevel = false; ProgressContainter->SetFloat(0.0f); // delete the HTTP request. delete PakRequest; }); FDelegateInt32 OnFileDownloadProgress = FDelegateInt32::CreateLambda([=](int32 Progress){ ProgressContainter->SetFloat((float)Progress/100.0f); UE_LOG(LogMapPakDownloader, Warning, TEXT(" %s %d%% downloaded"), *PakRequest->GetFileName(), Progress); }); PakRequest->SetFileName(PakLocation / DeltaPakName); PakRequest->SetUrl(HostName / PakLocation / DeltaPakName); PakRequest->SetOnLoadCallBack(OnFileDownloaded); PakRequest->SetOnProgressCallBack(OnFileDownloadProgress); PakRequest->SetOnErrorCallBack(FDelegateInt32::CreateLambda([=](int32){ UE_LOG(LogMapPakDownloader, Warning, TEXT("Could not download %s"), *PakRequest->GetFileName()); // lets try again with regular map pak PakRequest->SetFileName(PakLocation/PakName); PakRequest->SetUrl(HostName/PakLocation/PakName); PakRequest->SetOnErrorCallBack( FDelegateInt32::CreateLambda([=](int32){ // we can't even find regular maps. fatal. UE_LOG(LogMapPakDownloader, Fatal, TEXT("Could not find any Map Paks, exiting"), *PakRequest->GetFileName()); } )); PakRequest->Process(); })); PakRequest->Process(); } void FMapPakDownloader::Cache(FString& Map, FString& InLastMap, void* InDynData) { #if 0 // TODO: currently disabled -- this will be converted to use CHUNK settings -- in part 2 of level streaming support for HTML5 bool UseMapDownloader = false; if (GConfig) { GConfig->GetBool(TEXT("/Script/HTML5PlatformEditor.HTML5TargetSettings"), TEXT("UseAsyncLevelLoading"), UseMapDownloader, GEngineIni); } if (UseMapDownloader) { MapToCache = Map; LastMap = InLastMap; FString OutLongPackageName; FString OutFileName; if (!FPackageName::SearchForPackageOnDisk(Map, &OutLongPackageName, &OutFileName)) { UE_LOG(LogMapPakDownloader, Warning, TEXT("Caching.... %s"), *Map); CachePak(); Map = TEXT("/Engine/Maps/Entry"); IsTransitionLevel = true; } } #endif }