6803 lines
219 KiB
C++
6803 lines
219 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LaunchEngineLoop.h"
|
|
|
|
#include "AutoRTFM/AutoRTFMUE.h"
|
|
#include "HAL/PlatformStackWalk.h"
|
|
#include "HAL/PlatformOutputDevices.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "HAL/MallocFrameProfiler.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/QueuedThreadPool.h"
|
|
#include "Misc/QueuedThreadPoolWrapper.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformAffinity.h"
|
|
#include "HAL/DiskUtilizationTracker.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Internationalization/TextLocalizationManagerGlobals.h"
|
|
#include "Logging/LogSuppressionInterface.h"
|
|
#include "Logging/StructuredLog.h"
|
|
#include "Async/TaskGraphInterfaces.h"
|
|
#include "Async/Fundamental/Scheduler.h"
|
|
#include "MemPro/MemProProfiler.h"
|
|
#include "Misc/AsciiSet.h"
|
|
#include "Misc/TimeGuard.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PathViews.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Misc/ConfigUtilities.h"
|
|
#include "Misc/OutputDeviceHelper.h"
|
|
#include "Misc/OutputDeviceRedirector.h"
|
|
#include "Misc/AutomationTest.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/OutputDeviceConsole.h"
|
|
#include "Misc/OutputDeviceStdOut.h"
|
|
#include "Misc/ScopeExit.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "Misc/TrackedActivity.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "HAL/PlatformMemoryHelpers.h"
|
|
#include "HAL/FileManagerGeneric.h"
|
|
#include "HAL/ExceptionHandling.h"
|
|
#include "HAL/IPlatformFileManagedStorageWrapper.h"
|
|
#include "Serialization/CompactBinary.h"
|
|
#include "Serialization/CompactBinarySerialization.h"
|
|
#include "Serialization/CompactBinaryWriter.h"
|
|
#include "Stats/StatsMallocProfilerProxy.h"
|
|
#include "Stats/StatsSystem.h"
|
|
#include "Stats/ThreadIdleStats.h"
|
|
#include "Trace/Trace.inl"
|
|
#include "ProfilingDebugging/TraceAuxiliary.h"
|
|
#include "ProfilingDebugging/CsvProfiler.h"
|
|
#include "ProfilingDebugging/BootProfiling.h"
|
|
#if WITH_ENGINE
|
|
#include "HAL/PlatformSplash.h"
|
|
#include "SceneInterface.h"
|
|
#include "StereoRenderUtils.h"
|
|
#include "DataDrivenShaderPlatformInfo.h"
|
|
#endif
|
|
#if WITH_APPLICATION_CORE
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#endif
|
|
#include "HAL/ThreadManager.h"
|
|
#include "ProfilingDebugging/ExternalProfiler.h"
|
|
#include "ProfilingDebugging/StallDetector.h"
|
|
#include "Containers/StringView.h"
|
|
#include "Containers/Ticker.h"
|
|
|
|
#include "Interfaces/IPluginManager.h"
|
|
#include "ProjectDescriptor.h"
|
|
#include "Interfaces/IProjectManager.h"
|
|
#include "Misc/UProjectInfo.h"
|
|
#include "Misc/EngineVersion.h"
|
|
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Runtime/Launch/Resources/Version.h"
|
|
#include "Modules/BuildVersion.h"
|
|
#include "UObject/DevObjectVersion.h"
|
|
#include "HAL/ThreadHeartBeat.h"
|
|
|
|
#include "Misc/NetworkVersion.h"
|
|
#include "Templates/UniquePtr.h"
|
|
|
|
#include "Compression/OodleDataCompression.h"
|
|
|
|
#if !(IS_PROGRAM || WITH_EDITOR)
|
|
#include "IPlatformFilePak.h"
|
|
#endif
|
|
|
|
#ifndef USE_IO_DISPATCHER
|
|
#define USE_IO_DISPATCHER (WITH_ENGINE || WITH_IOSTORE_IN_EDITOR || !(IS_PROGRAM || WITH_EDITOR))
|
|
#endif
|
|
#if USE_IO_DISPATCHER
|
|
#include "IO/IoDispatcher.h"
|
|
#endif
|
|
|
|
#if !defined(UE_WITH_IOSTOREONDEMAND)
|
|
#define UE_WITH_IOSTOREONDEMAND 0
|
|
#endif
|
|
|
|
#if WITH_COREUOBJECT
|
|
#include "Internationalization/PackageLocalizationManager.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/Linker.h"
|
|
#include "UObject/LinkerLoad.h"
|
|
#include "UObject/PackageResourceManager.h"
|
|
#include "UObject/ReferencerFinder.h"
|
|
|
|
#if WITH_VERSE_VM || defined(__INTELLISENSE__)
|
|
#include "VerseVM/VVMVerse.h"
|
|
#else
|
|
#include "VerseVM/VVMExecutionContext.h"
|
|
#endif
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
#include "Blueprint/BlueprintSupport.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Misc/RemoteConfigIni.h"
|
|
#include "EditorCommandLineUtils.h"
|
|
#include "Input/Reply.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "RenderingThread.h"
|
|
#include "RenderDeferredCleanup.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "UnrealEdMisc.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "UObject/StrongObjectPtr.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Settings/EditorExperimentalSettings.h"
|
|
#include "PIEPreviewDeviceProfileSelectorModule.h"
|
|
#include "Serialization/BulkDataRegistry.h"
|
|
#include "Virtualization/VirtualizationSystem.h"
|
|
#include "AnimationUtils.h"
|
|
#include "UObject/ICookInfo.h"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
#include <objbase.h>
|
|
#include "Windows/HideWindowsPlatformTypes.h"
|
|
#include "Windows/WindowsPlatformPerfCounters.h"
|
|
#endif
|
|
#endif //WITH_EDITOR
|
|
|
|
#if WITH_ENGINE
|
|
#include "AssetCompilingManager.h"
|
|
#include "Engine/GameEngine.h"
|
|
#include "Engine/GameViewportClient.h"
|
|
#include "UnrealClient.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GameFramework/GameUserSettings.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "SystemSettings.h"
|
|
#include "EngineStats.h"
|
|
#include "EngineGlobals.h"
|
|
#include "AudioThread.h"
|
|
#include "AudioDeviceManager.h"
|
|
#if !UE_BUILD_SHIPPING
|
|
#include "IAutomationControllerModule.h"
|
|
#endif // !UE_BUILD_SHIPPING
|
|
#if WITH_EDITORONLY_DATA
|
|
#include "DerivedDataBuild.h"
|
|
#include "DerivedDataCache.h"
|
|
#endif // WITH_EDITORONLY_DATA
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "Serialization/DerivedData.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "DistanceFieldAtlas.h"
|
|
#include "MeshCardBuild.h"
|
|
#include "GlobalShader.h"
|
|
#include "ShaderCodeLibrary.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "TextureResource.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "Internationalization/StringTable.h"
|
|
#include "SceneUtils.h"
|
|
#include "ParticleHelper.h"
|
|
#include "PhysicsPublic.h"
|
|
#include "PlatformFeatures.h"
|
|
#include "DeviceProfiles/DeviceProfileManager.h"
|
|
#include "Commandlets/Commandlet.h"
|
|
#include "EngineService.h"
|
|
#include "TraceService.h"
|
|
#include "ContentStreaming.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "Misc/HotReloadInterface.h"
|
|
#include "ISessionServicesModule.h"
|
|
#include "Net/OnlineEngineInterface.h"
|
|
#include "Internationalization/EnginePackageLocalizationCache.h"
|
|
#include "Rendering/SlateRenderer.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "IMessagingModule.h"
|
|
#include "Engine/DemoNetDriver.h"
|
|
#include "LongGPUTask.h"
|
|
#include "RenderUtils.h"
|
|
#include "DynamicResolutionState.h"
|
|
#include "EngineModule.h"
|
|
#include "DumpGPU.h"
|
|
#include "PSOPrecacheMaterial.h"
|
|
#include "VisualizeTexture.h"
|
|
|
|
#if !UE_SERVER
|
|
#include "AppMediaTimeSource.h"
|
|
#include "IHeadMountedDisplayModule.h"
|
|
#include "IMediaModule.h"
|
|
#include "HeadMountedDisplay.h"
|
|
#include "MRMeshModule.h"
|
|
#include "Interfaces/ISlateRHIRendererModule.h"
|
|
#include "Interfaces/ISlateNullRendererModule.h"
|
|
#include "EngineFontServices.h"
|
|
#endif
|
|
|
|
#include "MoviePlayer.h"
|
|
#include "MoviePlayerProxy.h"
|
|
#include "PreLoadScreenManager.h"
|
|
#include "InstallBundleManagerInterface.h"
|
|
|
|
#include "ShaderCodeLibrary.h"
|
|
#include "ShaderPipelineCache.h"
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
#include "ProfileVisualizerModule.h"
|
|
#if UE_DEPRECATED_PROFILER_ENABLED
|
|
#include "IProfilerServiceModule.h"
|
|
#endif // UE_DEPRECATED_PROFILER_ENABLED
|
|
#endif
|
|
|
|
#if WITH_AUTOMATION_WORKER
|
|
#include "IAutomationWorkerModule.h"
|
|
#endif
|
|
|
|
#if WITH_ODSC
|
|
#include "ODSC/ODSCManager.h"
|
|
#endif
|
|
#endif //WITH_ENGINE
|
|
|
|
#include "Misc/EmbeddedCommunication.h"
|
|
|
|
#if WITH_ENGINE
|
|
#include "Tests/RHIUnitTests.h"
|
|
#endif
|
|
|
|
class FSlateRenderer;
|
|
class SViewport;
|
|
class IPlatformFile;
|
|
class FExternalProfiler;
|
|
class FFeedbackContext;
|
|
|
|
#if WITH_EDITOR
|
|
#include "FeedbackContextEditor.h"
|
|
static FFeedbackContextEditor UnrealEdWarn;
|
|
#include "AudioEditorModule.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
#if UE_EDITOR
|
|
#include "DesktopPlatformModule.h"
|
|
#include "TargetReceipt.h"
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "LaunchEngineLoop"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
#include <ObjBase.h>
|
|
#include "Windows/HideWindowsPlatformTypes.h"
|
|
#include <DbgHelp.h>
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
#include "EngineDefines.h"
|
|
#if ENABLE_VISUAL_LOG
|
|
#include "VisualLogger/VisualLogger.h"
|
|
#endif
|
|
#include "FramePro/FrameProProfiler.h"
|
|
#include "ProfilingDebugging/CsvProfiler.h"
|
|
#endif
|
|
|
|
#if defined(WITH_LAUNCHERCHECK) && WITH_LAUNCHERCHECK
|
|
#include "ILauncherCheckModule.h"
|
|
#endif
|
|
#include "String/ParseTokens.h"
|
|
|
|
#if WITH_COREUOBJECT
|
|
#ifndef USE_LOCALIZED_PACKAGE_CACHE
|
|
#define USE_LOCALIZED_PACKAGE_CACHE 1
|
|
#endif
|
|
#else
|
|
#define USE_LOCALIZED_PACKAGE_CACHE 0
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(CORE_API, Basic);
|
|
#endif
|
|
|
|
#ifndef WITH_CONFIG_PATCHING
|
|
#define WITH_CONFIG_PATCHING 0
|
|
#endif
|
|
|
|
#if PLATFORM_IOS || PLATFORM_TVOS
|
|
#include "IOS/IOSAppDelegate.h"
|
|
#endif
|
|
|
|
#ifndef UE_MERGED_MODULES
|
|
#define UE_MERGED_MODULES 0
|
|
#endif
|
|
|
|
#if CPUPROFILERTRACE_ENABLED
|
|
UE_TRACE_EVENT_BEGIN(Cpu, Frame, NoSync)
|
|
UE_TRACE_EVENT_FIELD(UE::Trace::WideString, Name)
|
|
UE_TRACE_EVENT_END()
|
|
#endif // CPUPROFILERTRACE_ENABLED
|
|
|
|
bool GIsConsoleExecutable = false;
|
|
|
|
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksRandomize(
|
|
TEXT("tick.DoAsyncEndOfFrameTasks.Randomize"),
|
|
0,
|
|
TEXT("Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread.")
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties(
|
|
TEXT("tick.DoAsyncEndOfFrameTasks.ValidateReplicatedProperties"),
|
|
0,
|
|
TEXT("If true, validates that replicated properties haven't changed during the Slate tick. Results will not be valid if demo.ClientRecordAsyncEndOfFrame is also enabled.")
|
|
);
|
|
|
|
static FAutoConsoleTaskPriority CPrio_AsyncEndOfFrameGameTasks(
|
|
TEXT("TaskGraph.TaskPriorities.AsyncEndOfFrameGameTasks"),
|
|
TEXT("Task and thread priority for the experiemntal async end of frame tasks."),
|
|
ENamedThreads::HighThreadPriority,
|
|
ENamedThreads::NormalTaskPriority,
|
|
ENamedThreads::HighTaskPriority
|
|
);
|
|
|
|
static TAutoConsoleVariable<float> CVarSecondsBeforeEmbeddedAppSleeps(
|
|
TEXT("tick.SecondsBeforeEmbeddedAppSleeps"),
|
|
1,
|
|
TEXT("When built as embedded, how many ticks to perform before sleeping")
|
|
);
|
|
|
|
/** Task that executes concurrently with Slate when tick.DoAsyncEndOfFrameTasks is true. */
|
|
class FExecuteConcurrentWithSlateTickTask
|
|
{
|
|
TFunction<void()> TickWithSlate;
|
|
|
|
public:
|
|
|
|
FExecuteConcurrentWithSlateTickTask(TFunction<void()> InTickWithSlate, FEvent* InCompleteEvent)
|
|
: TickWithSlate(InTickWithSlate)
|
|
, CompleteEvent(InCompleteEvent)
|
|
{
|
|
check(CompleteEvent);
|
|
}
|
|
|
|
static FORCEINLINE TStatId GetStatId()
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FExecuteConcurrentWithSlateTickTask, STATGROUP_TaskGraphTasks);
|
|
}
|
|
|
|
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
|
|
{
|
|
return CPrio_AsyncEndOfFrameGameTasks.Get();
|
|
}
|
|
|
|
static FORCEINLINE ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
|
|
|
|
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
|
|
{
|
|
TickWithSlate();
|
|
CompleteEvent->Trigger();
|
|
}
|
|
|
|
private:
|
|
FEvent* CompleteEvent;
|
|
};
|
|
|
|
// Exits the game/editor if any of the specified phrases appears in the log output
|
|
class FOutputDeviceTestExit : public FOutputDevice
|
|
{
|
|
TArray<FString> ExitPhrases;
|
|
FString* FoundExitPhrase = nullptr;
|
|
public:
|
|
FOutputDeviceTestExit(TArray<FString>&& InExitPhrases)
|
|
: ExitPhrases(InExitPhrases)
|
|
{
|
|
}
|
|
virtual ~FOutputDeviceTestExit()
|
|
{
|
|
}
|
|
bool RequestExit() { return !IsEngineExitRequested() && FoundExitPhrase; }
|
|
FString RequestExitPhrase() { return FoundExitPhrase ? *FoundExitPhrase : FString(); }
|
|
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
|
|
{
|
|
if (FoundExitPhrase || IsEngineExitRequested())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto& Phrase : ExitPhrases)
|
|
{
|
|
// look for the exit phrase, but ignore the output of the actual commandline string
|
|
if (FCString::Stristr(V, *Phrase) && !FCString::Stristr(V, TEXT("-testexit=")))
|
|
{
|
|
FoundExitPhrase = &Phrase;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
#if WITH_ENGINE && WITH_EDITOR
|
|
static FAutoConsoleCommand CmdRunCommandlet(
|
|
TEXT("RunCommandlet"),
|
|
TEXT("<Commandlet> <Args...>. NOTE: This is for debugging/iteration purposes only! Running commandlets multiple times in an editor session may result in issues!"),
|
|
FConsoleCommandWithArgsDelegate::CreateLambda( [](const TArray<FString>& Args)
|
|
{
|
|
if ( Args.Num() == 0 )
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Need to specify a commandlet name to run!"));
|
|
return;
|
|
}
|
|
|
|
FString CommandletName = Args[0];
|
|
UClass* CommandletClass = nullptr;
|
|
|
|
CommandletClass = Cast<UClass>(StaticFindFirstObject(UClass::StaticClass(), *CommandletName, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("looking for commandlet")));
|
|
int32 PeriodIdx;
|
|
if (!CommandletClass && CommandletName.FindChar('.', PeriodIdx))
|
|
{
|
|
// try to load module for commandlet specified before a period.
|
|
FModuleManager::Get().LoadModule(*CommandletName.Left(PeriodIdx));
|
|
CommandletClass = FindFirstObject<UClass>(*CommandletName, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("Looking for commandlet class"));
|
|
}
|
|
if (!CommandletClass)
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Commandlet class '%s' not found!"), *CommandletName);
|
|
return;
|
|
}
|
|
TStrongObjectPtr<UCommandlet> Commandlet( NewObject<UCommandlet>(GetTransientPackage(), CommandletClass) );
|
|
|
|
// Execute the commandlet.
|
|
double CommandletExecutionStartTime = FPlatformTime::Seconds();
|
|
|
|
// Commandlets don't always handle -run= properly in the commandline so we'll provide them
|
|
// with a custom version that doesn't have it.
|
|
TArray<FString> CmdlineArgs = Args;
|
|
CmdlineArgs.RemoveAt(0);
|
|
FString CommandletCommandLine = FString::Join(CmdlineArgs, TEXT(" "));
|
|
Commandlet->ParseParms(*CommandletCommandLine);
|
|
|
|
PRIVATE_GRunningCommandletClass = CommandletClass;
|
|
|
|
FCoreDelegates::OnCommandletPreMain.Broadcast();
|
|
int32 ErrorLevel;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*WriteToString<512>(TEXT("Commandlet Main "), Commandlet->GetFName()));
|
|
FTrackedActivityScope CommandletActivity(FTrackedActivity::GetEngineActivity(), *FString::Printf(TEXT("Running %s"), *Commandlet->GetName()), false, FTrackedActivity::ELight::Green);
|
|
ErrorLevel = Commandlet->Main(CommandletCommandLine);
|
|
}
|
|
FCoreDelegates::OnCommandletPostMain.Broadcast();
|
|
|
|
PRIVATE_GRunningCommandletClass = nullptr;
|
|
|
|
double CommandletExecutionTime = FPlatformTime::Seconds() - CommandletExecutionStartTime;
|
|
UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT("Commandlet returned %d. Execution time: %.2f seconds"), ErrorLevel, CommandletExecutionTime);
|
|
})
|
|
);
|
|
#endif
|
|
|
|
#if WITH_APPLICATION_CORE
|
|
static TUniquePtr<FOutputDeviceConsole> GScopedLogConsole;
|
|
#endif
|
|
static TUniquePtr<FOutputDeviceStdOutput> GScopedStdOut;
|
|
static TUniquePtr<FOutputDeviceTestExit> GScopedTestExit;
|
|
|
|
/**
|
|
* Initializes std out device and adds it to GLog
|
|
**/
|
|
void InitializeStdOutDevice()
|
|
{
|
|
// Check if something is trying to initialize std out device twice.
|
|
check(!GScopedStdOut);
|
|
|
|
GScopedStdOut = MakeUnique<FOutputDeviceStdOutput>();
|
|
GLog->AddOutputDevice(GScopedStdOut.Get());
|
|
}
|
|
|
|
|
|
bool ParseGameProjectFromCommandLine(const TCHAR* InCmdLine, FString& OutProjectFilePath, FString& OutGameName)
|
|
{
|
|
FString ProjectFilePathOption;
|
|
|
|
const TCHAR *CmdLine = InCmdLine;
|
|
bool ParseProjectOptionResult = FParse::Value(CmdLine, TEXT("project="), ProjectFilePathOption);
|
|
FString FirstCommandLineToken = FParse::Token(CmdLine, 0);
|
|
|
|
// trim any whitespace at edges of string - this can happen if the token was quoted with leading or trailing whitespace
|
|
// VC++ tends to do this in its "external tools" config
|
|
FirstCommandLineToken.TrimStartInline();
|
|
|
|
// Output parameters
|
|
OutProjectFilePath = TEXT("");
|
|
OutGameName = TEXT("");
|
|
|
|
if ( ( FirstCommandLineToken.Len() && !FirstCommandLineToken.StartsWith(TEXT("-"))) || ParseProjectOptionResult)
|
|
{
|
|
// The first command line argument could be the project file if it exists or the game name if not launching with a project file
|
|
FString ProjectFilePath = ProjectFilePathOption.IsEmpty() ? FString(FirstCommandLineToken) : ProjectFilePathOption;
|
|
if ( FPaths::GetExtension(ProjectFilePath) == FProjectDescriptor::GetExtension() )
|
|
{
|
|
OutProjectFilePath = ProjectFilePath;
|
|
// Here we derive the game name from the project file
|
|
OutGameName = FPaths::GetBaseFilename(OutProjectFilePath);
|
|
return true;
|
|
}
|
|
else if (FPaths::IsRelative(ProjectFilePath) && FPlatformProperties::IsMonolithicBuild() == false)
|
|
{
|
|
// Full game name is assumed to be the first token
|
|
OutGameName = MoveTemp(ProjectFilePath);
|
|
// Derive the project path from the game name. All games must have a uproject file, even if they are in the root folder.
|
|
OutProjectFilePath = FPaths::Combine(*FPaths::RootDir(), *OutGameName, *FString(OutGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (FEditorCommandLineUtils::ParseGameProjectPath(InCmdLine, OutProjectFilePath, OutGameName))
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool ReadInstalledProjectPath(FString& OutProjFilePath)
|
|
{
|
|
if(FApp::IsInstalled())
|
|
{
|
|
FString ProjFilePath;
|
|
if (FFileHelper::LoadFileToString(ProjFilePath, *(FPaths::RootDir() / TEXT("Engine/Build/InstalledProjectBuild.txt"))))
|
|
{
|
|
ProjFilePath.TrimStartAndEndInline();
|
|
if(ProjFilePath.Len() > 0)
|
|
{
|
|
OutProjFilePath = FPaths::RootDir() / ProjFilePath;
|
|
FPaths::NormalizeFilename(OutProjFilePath);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool LaunchSetGameName(const TCHAR *InCmdLine, FString& OutGameProjectFilePathUnnormalized)
|
|
{
|
|
if (GIsGameAgnosticExe)
|
|
{
|
|
// Initialize GameName to an empty string. Populate it below.
|
|
FApp::SetProjectName(TEXT(""));
|
|
|
|
FString ProjFilePath;
|
|
FString LocalGameName;
|
|
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
|
|
{
|
|
// Only set the game name if this is NOT a program...
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
FApp::SetProjectName(*LocalGameName);
|
|
}
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
#if WITH_EDITOR
|
|
else if(ReadInstalledProjectPath(ProjFilePath))
|
|
{
|
|
// Only set the game name if this is NOT a program...
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
FApp::SetProjectName(*FPaths::GetBaseFilename(ProjFilePath));
|
|
}
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
#endif
|
|
#if UE_GAME
|
|
else
|
|
{
|
|
// Try to use the executable name as the game name.
|
|
LocalGameName = FPlatformProcess::ExecutableName();
|
|
int32 FirstCharToRemove = INDEX_NONE;
|
|
if (LocalGameName.FindChar(TCHAR('-'), FirstCharToRemove))
|
|
{
|
|
LocalGameName.LeftInline(FirstCharToRemove, EAllowShrinking::No);
|
|
}
|
|
FApp::SetProjectName(*LocalGameName);
|
|
|
|
// Check it's not UnrealGame, otherwise assume a uproject file relative to the game project directory
|
|
if (LocalGameName != TEXT("UnrealGame"))
|
|
{
|
|
ProjFilePath = FPaths::Combine(TEXT(".."), TEXT(".."), TEXT(".."), *LocalGameName, *FString(LocalGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static bool bPrinted = false;
|
|
if (!bPrinted)
|
|
{
|
|
bPrinted = true;
|
|
if (FApp::HasProjectName())
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("Running engine for game: %s"), FApp::GetProjectName());
|
|
}
|
|
else
|
|
{
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games on cooked platforms require a uproject file be specified."));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("Running engine without a game"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FString ProjFilePath;
|
|
FString LocalGameName;
|
|
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
|
|
{
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
// Non-agnostic exes that require cooked data cannot load projects, so make sure that the LocalGameName is the GameName
|
|
if (LocalGameName != FApp::GetProjectName())
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games cannot load projects on cooked platforms - expected [%s], found [%s]"), FApp::GetProjectName(), *LocalGameName);
|
|
}
|
|
}
|
|
// Only set the game name if this is NOT a program...
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
FApp::SetProjectName(*LocalGameName);
|
|
}
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
|
|
// In a non-game agnostic exe, the game name should already be assigned by now.
|
|
if (!FApp::HasProjectName())
|
|
{
|
|
UE_LOG(LogInit, Fatal,TEXT("Could not set game name!"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LaunchFixProjectPathCase()
|
|
{
|
|
if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
FString ProjectFilePath = FPaths::GetProjectFilePath();
|
|
FString ProjectFilePathCorrectCase = FPaths::FindCorrectCase(ProjectFilePath);
|
|
FPaths::SetProjectFilePath(ProjectFilePathCorrectCase);
|
|
}
|
|
}
|
|
|
|
void LaunchFixGameNameCase()
|
|
{
|
|
#if PLATFORM_DESKTOP && !IS_PROGRAM
|
|
// This is to make sure this function is not misused and is only called when the game name is set
|
|
check(FApp::HasProjectName());
|
|
|
|
// correct the case of the game name, if possible (unless we're running a program and the game name is already set)
|
|
if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
const FString GameName(FPaths::GetBaseFilename(FPaths::GetProjectFilePath()));
|
|
|
|
const bool bGameNameMatchesProjectCaseSensitive = (FCString::Strcmp(*GameName, FApp::GetProjectName()) == 0);
|
|
if (!bGameNameMatchesProjectCaseSensitive && (FApp::IsProjectNameEmpty() || GIsGameAgnosticExe || (GameName.Len() > 0 && GIsGameAgnosticExe)))
|
|
{
|
|
if (GameName == FApp::GetProjectName()) // case insensitive compare
|
|
{
|
|
FApp::SetProjectName(*GameName);
|
|
}
|
|
else
|
|
{
|
|
const FText Message = FText::Format(
|
|
NSLOCTEXT("Core", "MismatchedGameNames", "The name of the .uproject file ('{0}') must match the name of the project passed in the command line ('{1}')."),
|
|
FText::FromString(*GameName),
|
|
FText::FromString(FApp::GetProjectName()));
|
|
if (!GIsBuildMachine)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("%s"), *Message.ToString());
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
}
|
|
FApp::SetProjectName(TEXT("")); // this disables part of the crash reporter to avoid writing log files to a bogus directory
|
|
if (!GIsBuildMachine)
|
|
{
|
|
exit(1);
|
|
}
|
|
UE_LOG(LogInit, Fatal, TEXT("%s"), *Message.ToString());
|
|
}
|
|
}
|
|
}
|
|
#endif //PLATFORM_DESKTOP
|
|
}
|
|
|
|
|
|
static IPlatformFile* ConditionallyCreateFileWrapper(const TCHAR* Name, IPlatformFile* CurrentPlatformFile, const TCHAR* CommandLine, bool* OutFailedToInitialize = nullptr, bool* bOutShouldBeUsed = nullptr )
|
|
{
|
|
if (OutFailedToInitialize)
|
|
{
|
|
*OutFailedToInitialize = false;
|
|
}
|
|
if ( bOutShouldBeUsed )
|
|
{
|
|
*bOutShouldBeUsed = false;
|
|
}
|
|
IPlatformFile* WrapperFile = FPlatformFileManager::Get().GetPlatformFile(Name);
|
|
if (WrapperFile != nullptr && WrapperFile->ShouldBeUsed(CurrentPlatformFile, CommandLine))
|
|
{
|
|
if ( bOutShouldBeUsed )
|
|
{
|
|
*bOutShouldBeUsed = true;
|
|
}
|
|
if (WrapperFile->Initialize(CurrentPlatformFile, CommandLine) == false)
|
|
{
|
|
if (OutFailedToInitialize)
|
|
{
|
|
*OutFailedToInitialize = true;
|
|
}
|
|
// Don't delete the platform file. It will be automatically deleted by its module.
|
|
WrapperFile = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Make sure it won't be used.
|
|
WrapperFile = nullptr;
|
|
}
|
|
return WrapperFile;
|
|
}
|
|
|
|
|
|
/**
|
|
* Look for any file overrides on the command line (i.e. network connection file handler)
|
|
*/
|
|
bool LaunchCheckForFileOverride(const TCHAR* CmdLine, bool& OutFileOverrideFound)
|
|
{
|
|
OutFileOverrideFound = false;
|
|
|
|
// Get the physical platform file.
|
|
IPlatformFile* CurrentPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
// NetworkPlatformFile can be only one of StorageServerClient, StreamingFile or NetworkFile
|
|
// Having a NetworkPlatformFile present prevents creation of Pakfile, CachedReadFile and SandboxFile
|
|
IPlatformFile* NetworkPlatformFile = nullptr;
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (!NetworkPlatformFile)
|
|
{
|
|
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("StorageServerClient"), CurrentPlatformFile, CmdLine);
|
|
if (NetworkPlatformFile)
|
|
{
|
|
CurrentPlatformFile = NetworkPlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
FPlatformFileManager::Get().SetPlatformPhysical(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
// Streaming network wrapper (it has a priority over normal network wrapper)
|
|
bool bShouldInitializeNetwork = !NetworkPlatformFile;
|
|
while (bShouldInitializeNetwork)
|
|
{
|
|
bool bShouldUseStreamingFile = false;
|
|
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("StreamingFile"), CurrentPlatformFile, CmdLine, &bShouldInitializeNetwork, &bShouldUseStreamingFile);
|
|
if (NetworkPlatformFile)
|
|
{
|
|
CurrentPlatformFile = NetworkPlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
|
|
// if streaming network platform file was tried this loop don't try this one
|
|
// Network file wrapper (only create if the streaming wrapper hasn't been created)
|
|
if (!bShouldUseStreamingFile && !NetworkPlatformFile)
|
|
{
|
|
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("NetworkFile"), CurrentPlatformFile, CmdLine, &bShouldInitializeNetwork);
|
|
if (NetworkPlatformFile)
|
|
{
|
|
CurrentPlatformFile = NetworkPlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
if (bShouldInitializeNetwork)
|
|
{
|
|
FString HostIpString;
|
|
FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIpString);
|
|
#if PLATFORM_REQUIRES_FILESERVER
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Failed to connect to file server at %s. RETRYING in 5s.\n"), *HostIpString);
|
|
FPlatformProcess::Sleep(5.0f);
|
|
uint32 Result = 2;
|
|
#else //PLATFORM_REQUIRES_FILESERVER
|
|
// note that this can't be localized because it happens before we connect to a filserver - localizing would cause ICU to try to load.... from over the file server connection!
|
|
FString Error = FString::Printf(TEXT("Failed to connect to any of the following file servers:\n\n %s\n\nWould you like to try again? No will fallback to local disk files, Cancel will quit."), *HostIpString.Replace( TEXT("+"), TEXT("\n ")));
|
|
uint32 Result = FMessageDialog::Open( EAppMsgType::YesNoCancel, FText::FromString( Error ) );
|
|
#endif //PLATFORM_REQUIRES_FILESERVER
|
|
|
|
if (Result == EAppReturnType::No)
|
|
{
|
|
break;
|
|
}
|
|
else if (Result == EAppReturnType::Cancel)
|
|
{
|
|
// Cancel - return a failure, and quit
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (UE::IsUsingZenPakFileStreaming() || !NetworkPlatformFile)
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
if (!NetworkPlatformFile)
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("CachedReadFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
|
|
PlatformFile = ConditionallyCreateFileWrapper(TEXT("SandboxFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Try to create file profiling wrapper
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("ProfileFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SimpleProfileFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
// Try and create file timings stats wrapper
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileReadStats"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
// Try and create file open log wrapper (lists the order files are first opened)
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileOpenLog"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
#endif //#if !UE_BUILD_SHIPPING
|
|
|
|
// Wrap the above in a file logging singleton if requested
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("LogFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
// If our platform file is different than it was when we started, then an override was used
|
|
OutFileOverrideFound = (CurrentPlatformFile != &FPlatformFileManager::Get().GetPlatformFile());
|
|
|
|
return true;
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
/**
|
|
* Process command line aliases
|
|
*
|
|
*/
|
|
void LaunchCheckForCommandLineAliases(const FConfigFile& Config, TArray<FString>& PrevExpansions, bool& bChanged)
|
|
{
|
|
bChanged = false;
|
|
|
|
if (const FConfigSection* Section = Config.FindSection(TEXT("CommandLineAliases")))
|
|
{
|
|
TArray<FString> Tokens;
|
|
{
|
|
const TCHAR* Stream = FCommandLine::Get();
|
|
FString NextToken;
|
|
while (FParse::Token(Stream, NextToken, false))
|
|
{
|
|
Tokens.Add(NextToken);
|
|
}
|
|
}
|
|
|
|
for (FConfigSection::TConstIterator ConfigIt(*Section); ConfigIt; ++ConfigIt)
|
|
{
|
|
FString Key = FString(TEXT("-")) + ConfigIt.Key().ToString();
|
|
TArray<FString>::TIterator TokenIt(Tokens);
|
|
while (TokenIt)
|
|
{
|
|
if (PrevExpansions.Contains(*TokenIt))
|
|
{
|
|
TokenIt.RemoveCurrent();
|
|
bChanged = true;
|
|
continue;
|
|
}
|
|
|
|
if (*TokenIt == Key)
|
|
{
|
|
PrevExpansions.Add(MoveTemp(*TokenIt));
|
|
*TokenIt = ConfigIt.Value().GetValue();
|
|
bChanged = true;
|
|
}
|
|
|
|
++TokenIt;
|
|
}
|
|
}
|
|
|
|
if (bChanged)
|
|
{
|
|
FString NewCommandLine = FString::Join(Tokens, TEXT(" "));
|
|
FCommandLine::Set(*NewCommandLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Look for command line file
|
|
*
|
|
*/
|
|
void LaunchCheckForCmdLineFile(TArray<FString>& PrevExpansions, bool& bChanged)
|
|
{
|
|
bChanged = false;
|
|
|
|
auto RemoveExpansion = [&bChanged]()
|
|
{
|
|
FString NewCommandLine = FCommandLine::Get();
|
|
FString Left;
|
|
FString Right;
|
|
if (NewCommandLine.Split(TEXT("-CmdLineFile="), &Left, &Right))
|
|
{
|
|
FString NextToken;
|
|
const TCHAR* Stream = *Right;
|
|
if (FParse::Token(Stream, NextToken, /*bUseEscape=*/ false))
|
|
{
|
|
Right = FString(Stream);
|
|
}
|
|
|
|
NewCommandLine = Left + TEXT(" ") + Right;
|
|
NewCommandLine.TrimStartAndEndInline();
|
|
FCommandLine::Set(*NewCommandLine);
|
|
bChanged = true;
|
|
}
|
|
};
|
|
|
|
auto TryProcessFile = [&RemoveExpansion, &bChanged](const FString& InFilePath)
|
|
{
|
|
FString FileCmds;
|
|
if (FFileHelper::LoadFileToString(FileCmds, *InFilePath))
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT("Inserting commandline from file: %s, %s"), *InFilePath, *FileCmds);
|
|
|
|
FileCmds = FileCmds.TrimStartAndEnd();
|
|
if (FileCmds.Len() == 0)
|
|
{
|
|
RemoveExpansion();
|
|
return true;
|
|
}
|
|
|
|
FString NewCommandLine = FCommandLine::Get();
|
|
FString Left;
|
|
FString Right;
|
|
if (NewCommandLine.Split(TEXT("-CmdLineFile="), &Left, &Right))
|
|
{
|
|
FString NextToken;
|
|
const TCHAR* Stream = *Right;
|
|
if (FParse::Token(Stream, NextToken, /*bUseEscape=*/ false))
|
|
{
|
|
Right = FString(Stream);
|
|
}
|
|
|
|
NewCommandLine = Left + TEXT(" ") + FileCmds + TEXT(" ") + Right;
|
|
NewCommandLine.TrimStartAndEndInline();
|
|
FCommandLine::Set(*NewCommandLine);
|
|
bChanged = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
FString CmdLineFile;
|
|
while (FParse::Value(FCommandLine::Get(), TEXT("-CmdLineFile="), CmdLineFile))
|
|
{
|
|
if (!CmdLineFile.EndsWith(TEXT(".txt")))
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("Can only load commandline files ending with .txt, can't load: %s"), *CmdLineFile);
|
|
RemoveExpansion();
|
|
continue;
|
|
}
|
|
|
|
if (PrevExpansions.Contains(CmdLineFile))
|
|
{
|
|
// If already expanded, just remove it
|
|
RemoveExpansion();
|
|
continue;
|
|
}
|
|
|
|
bool bFoundFile = TryProcessFile(CmdLineFile);
|
|
if (!bFoundFile && FPaths::ProjectDir().Len() > 0)
|
|
{
|
|
const FString ProjectDir = FPaths::ProjectDir();
|
|
bFoundFile = TryProcessFile(ProjectDir + CmdLineFile);
|
|
if (!bFoundFile)
|
|
{
|
|
const FString ProjectPluginsDir = ProjectDir + TEXT("Plugins/");
|
|
TArray<FString> DirectoryNames;
|
|
IFileManager::Get().IterateDirectory(*ProjectPluginsDir, [&](const TCHAR* FilenameOrDirectory, bool bIsDirectory) -> bool
|
|
{
|
|
if (bIsDirectory && TryProcessFile(FString(FilenameOrDirectory) + TEXT("/") + CmdLineFile))
|
|
{
|
|
bFoundFile = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!bFoundFile)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("Failed to load commandline file '%s'."), *CmdLineFile);
|
|
RemoveExpansion();
|
|
continue;
|
|
}
|
|
|
|
PrevExpansions.Add(MoveTemp(CmdLineFile));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool LaunchHasIncompleteGameName()
|
|
{
|
|
if ( FApp::HasProjectName() && !FPaths::IsProjectFilePathSet() )
|
|
{
|
|
// Verify this is a legitimate game name
|
|
// Launched with a game name. See if the <GameName> folder exists. If it doesn't, it could instead be <GameName>Game
|
|
const FString NonSuffixedGameFolder = FPaths::RootDir() / FApp::GetProjectName();
|
|
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*NonSuffixedGameFolder) == false)
|
|
{
|
|
const FString SuffixedGameFolder = NonSuffixedGameFolder + TEXT("Game");
|
|
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*SuffixedGameFolder))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void LaunchUpdateMostRecentProjectFile()
|
|
{
|
|
// If we are launching without a game name or project file, we should use the last used project file, if it exists
|
|
const FString& AutoLoadProjectFileName = IProjectManager::Get().GetAutoLoadProjectFileName();
|
|
FString RecentProjectFileContents;
|
|
if ( FFileHelper::LoadFileToString(RecentProjectFileContents, *AutoLoadProjectFileName) )
|
|
{
|
|
if ( RecentProjectFileContents.Len() )
|
|
{
|
|
const FString AutoLoadInProgressFilename = AutoLoadProjectFileName + TEXT(".InProgress");
|
|
if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*AutoLoadInProgressFilename) )
|
|
{
|
|
// We attempted to auto-load a project but the last run did not make it to UEditorEngine::InitEditor.
|
|
// This indicates that there was a problem loading the project.
|
|
// Do not auto-load the project, instead load normally until the next time the editor starts successfully.
|
|
UE_LOG(LogInit, Display, TEXT("There was a problem auto-loading %s. Auto-load will be disabled until the editor successfully starts up with a project."), *RecentProjectFileContents);
|
|
}
|
|
else if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*RecentProjectFileContents) )
|
|
{
|
|
// The previously loaded project file was found. Change the game name here and update the project file path
|
|
FApp::SetProjectName(*FPaths::GetBaseFilename(RecentProjectFileContents));
|
|
FPaths::SetProjectFilePath(RecentProjectFileContents);
|
|
UE_LOG(LogInit, Display, TEXT("Loading recent project file: %s"), *RecentProjectFileContents);
|
|
|
|
// Write a file indicating that we are trying to auto-load a project.
|
|
// This file prevents auto-loading of projects for as long as it exists. It is a detection system for failed auto-loads.
|
|
// The file is deleted in UEditorEngine::InitEditor, thus if the load does not make it that far then the project will not be loaded again.
|
|
FFileHelper::SaveStringToFile(TEXT(""), *AutoLoadInProgressFilename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
void OnStartupContentMounted(FInstallBundleRequestResultInfo Result, bool bDumpEarlyConfigReads, bool bDumpEarlyPakFileReads, bool bReloadConfig, bool bForceQuitAfterEarlyReads);
|
|
#endif
|
|
void DumpEarlyReads(bool bDumpEarlyConfigReads, bool bDumpEarlyPakFileReads, bool bForceQuitAfterEarlyReads);
|
|
void HandleConfigReload(bool bReloadConfig);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
class FFileInPakFileHistoryHelper
|
|
{
|
|
private:
|
|
struct FFileInPakFileHistory
|
|
{
|
|
FString PakFileName;
|
|
FString FileName;
|
|
};
|
|
friend uint32 GetTypeHash(const FFileInPakFileHistory& H)
|
|
{
|
|
uint32 Hash = GetTypeHash(H.PakFileName);
|
|
Hash = HashCombine(Hash, GetTypeHash(H.FileName));
|
|
return Hash;
|
|
}
|
|
friend bool operator==(const FFileInPakFileHistory& A, const FFileInPakFileHistory& B)
|
|
{
|
|
return A.PakFileName == B.PakFileName && A.FileName == B.FileName;
|
|
}
|
|
|
|
TSet<FFileInPakFileHistory> History;
|
|
FCriticalSection HistoryLock;
|
|
|
|
void OnFileOpenedForRead(const TCHAR* PakFileName, const TCHAR* FileName)
|
|
{
|
|
//UE_LOG(LogInit, Warning, TEXT("OnFileOpenedForRead %u: %s - %s"), FPlatformTLS::GetCurrentThreadId(), PakFileName, FileName);
|
|
|
|
FScopeLock ScopeLock(&HistoryLock);
|
|
History.Emplace(FFileInPakFileHistory{ PakFileName, FileName });
|
|
}
|
|
|
|
public:
|
|
FFileInPakFileHistoryHelper()
|
|
{
|
|
FCoreDelegates::GetOnFileOpenedForReadFromPakFile().AddRaw(this, &FFileInPakFileHistoryHelper::OnFileOpenedForRead);
|
|
}
|
|
|
|
~FFileInPakFileHistoryHelper()
|
|
{
|
|
FCoreDelegates::GetOnFileOpenedForReadFromPakFile().RemoveAll(this);
|
|
}
|
|
|
|
void DumpHistory()
|
|
{
|
|
FScopeLock ScopeLock(&HistoryLock);
|
|
|
|
History.Sort([](const FFileInPakFileHistory& A, const FFileInPakFileHistory& B)
|
|
{
|
|
if (A.PakFileName == B.PakFileName)
|
|
{
|
|
return A.FileName < B.FileName;
|
|
}
|
|
|
|
return A.PakFileName < B.PakFileName;
|
|
});
|
|
|
|
const FString SavePath = FPaths::ProjectLogDir() / TEXT("FilesLoadedFromPakFiles.csv");
|
|
|
|
FArchive* Writer = IFileManager::Get().CreateFileWriter(*SavePath, FILEWRITE_NoFail);
|
|
|
|
auto WriteLine = [Writer](FString&& Line)
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("%s"), *Line);
|
|
FTCHARToUTF8 UTF8String(*(MoveTemp(Line) + LINE_TERMINATOR));
|
|
Writer->Serialize((UTF8CHAR*)UTF8String.Get(), UTF8String.Length());
|
|
};
|
|
|
|
UE_LOG(LogInit, Display, TEXT("Dumping History of files read from Paks to %s"), *SavePath);
|
|
UE_LOG(LogInit, Display, TEXT("Begin History of files read from Paks"));
|
|
UE_LOG(LogInit, Display, TEXT("------------------------------------------------------"));
|
|
WriteLine(FString::Printf(TEXT("PakFile, File")));
|
|
for (const FFileInPakFileHistory& H : History)
|
|
{
|
|
WriteLine(FString::Printf(TEXT("%s, %s"), *H.PakFileName, *H.FileName));
|
|
}
|
|
UE_LOG(LogInit, Display, TEXT("------------------------------------------------------"));
|
|
UE_LOG(LogInit, Display, TEXT("End History of files read from Paks"));
|
|
|
|
delete Writer;
|
|
Writer = nullptr;
|
|
}
|
|
};
|
|
TUniquePtr<FFileInPakFileHistoryHelper> FileInPakFileHistoryHelper;
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
void RecordFileReadsFromPaks()
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
FileInPakFileHistoryHelper = MakeUnique<FFileInPakFileHistoryHelper>();
|
|
#endif
|
|
}
|
|
|
|
void DumpRecordedFileReadsFromPaks()
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FileInPakFileHistoryHelper)
|
|
{
|
|
FileInPakFileHistoryHelper->DumpHistory();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DeleteRecordedFileReadsFromPaks()
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
FileInPakFileHistoryHelper = nullptr;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
FEngineLoop implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
FEngineLoop::FEngineLoop()
|
|
#if WITH_ENGINE
|
|
: EngineService(nullptr)
|
|
#endif
|
|
{ }
|
|
|
|
|
|
static FString OriginalProjectModuleName;
|
|
static FString ReplacementProjectModuleName;
|
|
|
|
void FEngineLoop::OverrideProjectModule(const FString& InOriginalProjectModuleName, const FString& InReplacementProjectModuleName)
|
|
{
|
|
OriginalProjectModuleName = InOriginalProjectModuleName;
|
|
ReplacementProjectModuleName = InReplacementProjectModuleName;
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("OverrideProjectModule : OriginalProjectModuleName=%s, ReplacementProjectModuleName=%s\n"), *OriginalProjectModuleName, *ReplacementProjectModuleName);
|
|
|
|
}
|
|
|
|
int32 FEngineLoop::PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline)
|
|
{
|
|
FString CmdLine = FCommandLine::BuildFromArgV(nullptr, ArgC, ArgV, AdditionalCommandline);
|
|
|
|
// send the command line without the exe name
|
|
return GEngineLoop.PreInit(*CmdLine);
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
bool IsServerDelegateForOSS(FName WorldContextHandle)
|
|
{
|
|
if (IsRunningDedicatedServer())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
UWorld* World = nullptr;
|
|
#if WITH_EDITOR
|
|
if (WorldContextHandle != NAME_None)
|
|
{
|
|
const FWorldContext* WorldContext = GEngine->GetWorldContextFromHandle(WorldContextHandle);
|
|
if (WorldContext)
|
|
{
|
|
check(WorldContext->WorldType == EWorldType::Game || WorldContext->WorldType == EWorldType::PIE);
|
|
World = WorldContext->World();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!World)
|
|
{
|
|
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
|
|
if (GameEngine)
|
|
{
|
|
World = GameEngine->GetGameWorld();
|
|
}
|
|
else
|
|
{
|
|
// The calling code didn't pass in a world context and really should have
|
|
if (GIsPlayInEditorWorld)
|
|
{
|
|
World = GWorld;
|
|
}
|
|
|
|
#if !WITH_DEV_AUTOMATION_TESTS
|
|
// Not having a world to make the right determination is a bad thing
|
|
// In the editor during PIE this will confuse the individual PIE windows and their associated online components
|
|
UE_CLOG(World == nullptr, LogInit, Error, TEXT("Failed to determine if OSS is server in PIE, OSS requests will fail"));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ENetMode NetMode = World ? World->GetNetMode() : NM_Standalone;
|
|
return (NetMode == NM_ListenServer || NetMode == NM_DedicatedServer);
|
|
}
|
|
#endif
|
|
|
|
#if WITH_ENGINE && CSV_PROFILER
|
|
static void UpdateCoreCsvStats_BeginFrame()
|
|
{
|
|
if (FCsvProfiler::Get()->IsCapturing())
|
|
{
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
const uint32 ProcessId = (uint32)FPlatformProcess::GetCurrentProcessId();
|
|
float ProcessUsagePercent = 0.f, IdleUsagePercent = 0.f;
|
|
if (FPlatformProcess::GetPerFrameProcessorUsage(ProcessId, ProcessUsagePercent, IdleUsagePercent))
|
|
{
|
|
// This occasionally happens on Linux and I am not entirely sure why.
|
|
// If we have zero idle time and zero utilization assume we have full idle time
|
|
if (ProcessUsagePercent == 0.0f && IdleUsagePercent == 0.0f)
|
|
{
|
|
IdleUsagePercent = 1.0f;
|
|
}
|
|
CSV_CUSTOM_STAT_GLOBAL(CPUUsage_Process, ProcessUsagePercent, ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT_GLOBAL(CPUUsage_Idle, IdleUsagePercent, ECsvCustomStatOp::Set);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
#if CSV_PROFILER_ALLOW_DEBUG_FEATURES
|
|
// Handle CsvExecCmds for this frame
|
|
if (GEngine && GWorld)
|
|
{
|
|
TArray<FString> FrameCommands;
|
|
FCsvProfiler::Get()->GetFrameExecCommands(FrameCommands);
|
|
for (FString Cmd : FrameCommands)
|
|
{
|
|
CSV_EVENT_GLOBAL(TEXT("CsvExecCommand : %s"), *Cmd);
|
|
|
|
// Try to execute on the local player
|
|
bool bExecuted = false;
|
|
for (FConstPlayerControllerIterator Iterator = GWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
|
|
{
|
|
APlayerController* PlayerController = Iterator->Get();
|
|
if (PlayerController)
|
|
{
|
|
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player))
|
|
{
|
|
LocalPlayer->Exec(GWorld, *Cmd, *GLog);
|
|
bExecuted = true;
|
|
}
|
|
}
|
|
}
|
|
if (!bExecuted)
|
|
{
|
|
// Fallback to GEngine exec
|
|
GEngine->Exec(GWorld, *Cmd);
|
|
}
|
|
}
|
|
}
|
|
#endif // CSV_PROFILER_ALLOW_DEBUG_FEATURES
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
CSV_DEFINE_CATEGORY(GPUUsage, true);
|
|
#endif
|
|
|
|
static void UpdateCoreCsvStats_EndFrame()
|
|
{
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(RenderThreadTime, FPlatformTime::ToMilliseconds(GRenderThreadTime), ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(GameThreadTime, FPlatformTime::ToMilliseconds(GGameThreadTime), ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(GPUTime, FPlatformTime::ToMilliseconds(RHIGetGPUFrameCycles()), ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(RenderThreadTime_CriticalPath, FPlatformTime::ToMilliseconds(GRenderThreadTimeCriticalPath), ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(GameThreadTime_CriticalPath, FPlatformTime::ToMilliseconds(GGameThreadTimeCriticalPath), ECsvCustomStatOp::Set);
|
|
if (IsRunningRHIInSeparateThread())
|
|
{
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(RHIThreadTime, FPlatformTime::ToMilliseconds(GRHIThreadTime), ECsvCustomStatOp::Set);
|
|
}
|
|
if (GInputLatencyTime > 0)
|
|
{
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(InputLatencyTime, FPlatformTime::ToMilliseconds64(GInputLatencyTime), ECsvCustomStatOp::Set);
|
|
}
|
|
FPlatformMemoryStats MemoryStats = PlatformMemoryHelpers::GetFrameMemoryStats();
|
|
float PhysicalMBFree = float(MemoryStats.AvailablePhysical / 1024) / 1024.0f;
|
|
#if !UE_BUILD_SHIPPING
|
|
// Subtract any extra development memory from physical free. This can result in negative values in cases where we would have crashed OOM
|
|
PhysicalMBFree -= float(FPlatformMemory::GetExtraDevelopmentMemorySize() / 1024ull / 1024ull);
|
|
#endif
|
|
CSV_CUSTOM_STAT_MINIMAL_GLOBAL(MemoryFreeMB, PhysicalMBFree, ECsvCustomStatOp::Set);
|
|
|
|
#if !UE_BUILD_SHIPPING && !CSV_PROFILER_MINIMAL
|
|
float TargetFPS = 30.0f;
|
|
static IConsoleVariable* MaxFPSCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("t.MaxFPS"));
|
|
if (MaxFPSCVar && MaxFPSCVar->GetFloat() > 0)
|
|
{
|
|
TargetFPS = MaxFPSCVar->GetFloat();
|
|
}
|
|
CSV_CUSTOM_STAT_GLOBAL(MaxFrameTime, 1000.0f / TargetFPS, ECsvCustomStatOp::Set);
|
|
|
|
// TODO: see what causes this to be true
|
|
if (GRHISupportsGPUUsage && GNumExplicitGPUsForRendering == 1)
|
|
{
|
|
FRHIGPUUsageFractions GPUUsage = RHIGetGPUUsage(/* GPUIndex = */ 0);
|
|
CSV_CUSTOM_STAT(GPUUsage, Clock, GPUUsage.ClockScaling * 100.0f, ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT(GPUUsage, Usage, GPUUsage.CurrentProcess * 100.0f, ECsvCustomStatOp::Set);
|
|
CSV_CUSTOM_STAT(GPUUsage, External, GPUUsage.ExternalProcesses * 100.0f, ECsvCustomStatOp::Set);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#endif // WITH_ENGINE && CSV_PROFILER
|
|
|
|
#if WITH_ENGINE
|
|
namespace AppLifetimeEventCapture
|
|
{
|
|
static void AppWillDeactivate()
|
|
{
|
|
UE_LOG( LogCore, Display, TEXT("AppLifetime: Application will deactivate") );
|
|
CSV_EVENT_GLOBAL(TEXT("App_WillDeactivate"));
|
|
}
|
|
|
|
static void AppHasReactivated()
|
|
{
|
|
UE_LOG( LogCore, Display, TEXT("AppLifetime: Application has reactivated") );
|
|
CSV_EVENT_GLOBAL(TEXT("App_HasReactivated"));
|
|
}
|
|
|
|
static void AppWillEnterBackground()
|
|
{
|
|
UE_LOG( LogCore, Display, TEXT("AppLifetime: Application will enter background") );
|
|
CSV_EVENT_GLOBAL(TEXT("App_WillEnterBackground"));
|
|
}
|
|
|
|
static void AppHasEnteredForeground()
|
|
{
|
|
UE_LOG( LogCore, Display, TEXT("AppLifetime: Application has entered foreground") );
|
|
CSV_EVENT_GLOBAL(TEXT("App_HasEnteredForeground"));
|
|
}
|
|
|
|
static void Init()
|
|
{
|
|
FCoreDelegates::ApplicationWillDeactivateDelegate.AddStatic(AppWillDeactivate);
|
|
FCoreDelegates::ApplicationHasReactivatedDelegate.AddStatic(AppHasReactivated);
|
|
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddStatic(AppWillEnterBackground);
|
|
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddStatic(AppHasEnteredForeground);
|
|
}
|
|
}
|
|
#endif //WITH_ENGINE
|
|
|
|
static void UpdateGInputTime()
|
|
{
|
|
GInputTime = FPlatformTime::Cycles64();
|
|
}
|
|
|
|
static TArray<FString> TokenizeCommandline(const TCHAR* CmdLine, bool bRetainQuotes)
|
|
{
|
|
TArray<FString> TokenArray;
|
|
|
|
const TCHAR* ParsedCmdLine = CmdLine;
|
|
|
|
while (*ParsedCmdLine)
|
|
{
|
|
FString Token;
|
|
|
|
// skip over whitespace to look for a quote
|
|
while (FChar::IsWhitespace(*ParsedCmdLine))
|
|
{
|
|
ParsedCmdLine++;
|
|
}
|
|
|
|
// if we want to keep quotes around the token, and the first character is a quote, then FToken::Parse
|
|
// will remove the quotes, so put them back
|
|
if (bRetainQuotes && (*ParsedCmdLine == TEXT('"')))
|
|
{
|
|
FParse::Token(ParsedCmdLine, Token, 0);
|
|
Token = FString::Printf(TEXT("\"%s\""), *Token);
|
|
}
|
|
else
|
|
{
|
|
FParse::Token(ParsedCmdLine, Token, 0);
|
|
Token.TrimStartAndEndInline();
|
|
}
|
|
|
|
if (!Token.IsEmpty())
|
|
{
|
|
TokenArray.Add(MoveTemp(Token));
|
|
}
|
|
}
|
|
|
|
return MoveTemp(TokenArray);
|
|
}
|
|
|
|
/** Enumeration representing the type of the command-line argument representing the game (typically the first argument). */
|
|
enum class EGameStringType
|
|
{
|
|
GameName,
|
|
ProjectPath,
|
|
ProjectShortName,
|
|
Unknown
|
|
};
|
|
|
|
/**
|
|
* Finds a command-line argument representing the game, removes it from the array and returns.
|
|
*
|
|
* @param TokenArray Array of the tokenized command line
|
|
* @param QuotedTokenArray Parellel array that maintains quotes around params
|
|
* @param OutStringType Type of the game string found.
|
|
* @return String representing the game command-line parameter; empty string if not found (OutStringType == Unknown in such case).
|
|
*/
|
|
static FString ExtractGameStringArgument(TArray<FString>& TokenArray, TArray<FString>& QuotedTokenArray, EGameStringType& OutStringType)
|
|
{
|
|
for (int32 I = 0; I < TokenArray.Num(); ++I)
|
|
{
|
|
FString NormalizedToken = TokenArray[I];
|
|
|
|
// Path returned by FPaths::GetProjectFilePath() is normalized, so may have symlinks and ~ resolved and may differ from the original path to .uproject passed in the command line
|
|
FPaths::NormalizeFilename(NormalizedToken);
|
|
|
|
const bool bTokenIsGameName = (FApp::HasProjectName() && TokenArray[I] == FApp::GetProjectName());
|
|
const bool bTokenIsGameProjectFilePath = (FPaths::IsProjectFilePathSet() && NormalizedToken == FPaths::GetProjectFilePath());
|
|
const bool bTokenIsGameProjectFileShortName = (FPaths::IsProjectFilePathSet() && TokenArray[I] == FPaths::GetCleanFilename(FPaths::GetProjectFilePath()));
|
|
|
|
if (bTokenIsGameName || bTokenIsGameProjectFilePath || bTokenIsGameProjectFileShortName)
|
|
{
|
|
if (bTokenIsGameName)
|
|
{
|
|
OutStringType = EGameStringType::GameName;
|
|
}
|
|
else if (bTokenIsGameProjectFilePath)
|
|
{
|
|
OutStringType = EGameStringType::ProjectPath;
|
|
}
|
|
else if (bTokenIsGameProjectFileShortName)
|
|
{
|
|
OutStringType = EGameStringType::ProjectShortName;
|
|
}
|
|
|
|
FString Result = TokenArray[I];
|
|
|
|
TokenArray.RemoveAt(I);
|
|
QuotedTokenArray.RemoveAt(I);
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
OutStringType = EGameStringType::Unknown;
|
|
|
|
return FString();
|
|
}
|
|
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("FEngineLoop::PreInitPreStartupScreen.AfterStats"), STAT_FEngineLoop_PreInitPreStartupScreen_AfterStats, STATGROUP_LoadTime);
|
|
DECLARE_CYCLE_STAT(TEXT("FEngineLoop::PreInitPostStartupScreen.AfterStats"), STAT_FEngineLoop_PreInitPostStartupScreen_AfterStats, STATGROUP_LoadTime);
|
|
|
|
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
|
|
{
|
|
ON_SCOPE_EXIT { FBootProfiling::OnPreInitPreStartupScreenComplete(); };
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::StartOfEnginePreInit);
|
|
SCOPED_BOOT_TIMING("FEngineLoop::PreInitPreStartupScreen");
|
|
|
|
// GLog is initialized lazily and its default primary thread is the thread that initialized it.
|
|
// This lazy initialization can happen during initialization of a DLL, which Windows does on a
|
|
// worker thread, which makes that worker thread the primary thread. Make this the primary thread
|
|
// until initialization is far enough along to try to start a dedicated primary thread.
|
|
GLog->SetCurrentThreadAsPrimaryThread();
|
|
|
|
// Most targets have the backlog enabled by default. Enable it as early as possible here for
|
|
// cases where it is disabled by default. It will be disabled when module loading is complete.
|
|
GLog->EnableBacklog(true);
|
|
|
|
// Command line option for enabling named events
|
|
if (FParse::Param(CmdLine, TEXT("statnamedevents")))
|
|
{
|
|
++GCycleStatsShouldEmitNamedEvents;
|
|
}
|
|
|
|
if (FParse::Param(CmdLine, TEXT("verbosenamedevents")))
|
|
{
|
|
++GCycleStatsShouldEmitNamedEvents;
|
|
GShouldEmitVerboseNamedEvents = true;
|
|
}
|
|
|
|
|
|
// Set the flag for whether we've build DebugGame instead of Development. The engine does not know this (whereas the launch module does) because it is always built in development.
|
|
#if UE_BUILD_DEVELOPMENT && defined(UE_BUILD_DEVELOPMENT_WITH_DEBUGGAME) && UE_BUILD_DEVELOPMENT_WITH_DEBUGGAME
|
|
FApp::SetDebugGame(true);
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
// Register a handler for Ctrl-C so we've effective signal handling from the outset.
|
|
FWindowsPlatformMisc::SetGracefulTerminationHandler();
|
|
#endif // PLATFORM_WINDOWS
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
#ifdef EMBEDDED_LINKER_GAME_HELPER_FUNCTION
|
|
extern void EMBEDDED_LINKER_GAME_HELPER_FUNCTION();
|
|
EMBEDDED_LINKER_GAME_HELPER_FUNCTION();
|
|
#endif
|
|
FEmbeddedCommunication::Init();
|
|
FEmbeddedCommunication::KeepAwake(TEXT("Startup"), false);
|
|
#endif
|
|
|
|
FMemory::SetupTLSCachesOnCurrentThread();
|
|
|
|
FPlatformMisc::SetUTF8Output();
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FParse::Param(CmdLine, TEXT("IgnoreDebugger")))
|
|
{
|
|
GIgnoreDebugger = true;
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// Switch into executable's directory.
|
|
#if !defined(DISABLE_CWD_CHANGES) || DISABLE_CWD_CHANGES==0
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
#endif
|
|
|
|
// this is set later with shorter command lines, but we want to make sure it is set ASAP as some subsystems will do the tests themselves...
|
|
// also realize that command lines can be pulled from the network at a slightly later time.
|
|
if (!FCommandLine::Set(CmdLine))
|
|
{
|
|
// Fail, shipping builds will crash if setting command line fails
|
|
return -1;
|
|
}
|
|
|
|
// Avoiding potential exploits by not exposing command line overrides in the shipping games.
|
|
#if !UE_BUILD_SHIPPING && WITH_EDITORONLY_DATA
|
|
// Retrieve additional command line arguments from environment variable.
|
|
FString Env = FPlatformMisc::GetEnvironmentVariable(TEXT("UE-CmdLineArgs")).TrimStart();
|
|
if (Env.Len())
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT("Inserting commandline from UE-CmdLineArgs: %s"), *Env);
|
|
|
|
// Append the command line environment after inserting a space as we can't set it in the
|
|
// environment.
|
|
FCommandLine::Append(TEXT(" -EnvAfterHere "));
|
|
FCommandLine::Append(*Env);
|
|
CmdLine = FCommandLine::Get();
|
|
}
|
|
#endif
|
|
|
|
// Name of project file before normalization (as specified in command line).
|
|
// Used to fixup project name if necessary.
|
|
FString GameProjectFilePathUnnormalized;
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LaunchSetGameName");
|
|
|
|
// Set GameName, based on the command line
|
|
if (LaunchSetGameName(CmdLine, GameProjectFilePathUnnormalized) == false)
|
|
{
|
|
// If it failed, do not continue
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Initialize trace
|
|
FTraceAuxiliary::Initialize(CmdLine);
|
|
FTraceAuxiliary::TryAutoConnect();
|
|
|
|
// disable/enable LLM based on commandline
|
|
{
|
|
SCOPED_BOOT_TIMING("LLM Init");
|
|
LLM(FLowLevelMemTracker::Get().ProcessCommandLine(CmdLine));
|
|
#if MEMPRO_ENABLED
|
|
FMemProProfiler::Init(CmdLine);
|
|
#endif
|
|
}
|
|
LLM_SCOPE(ELLMTag::EnginePreInitMemory);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("InitTaggedStorage");
|
|
FPlatformMisc::InitTaggedStorage(1024);
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
FCoreUObjectDelegates::PostGarbageCollectConditionalBeginDestroy.AddStatic(DeferredPhysResourceCleanup);
|
|
#endif
|
|
FCoreDelegates::OnSamplingInput.AddStatic(UpdateGInputTime);
|
|
|
|
#if defined(WITH_LAUNCHERCHECK) && WITH_LAUNCHERCHECK
|
|
if (ILauncherCheckModule::Get().WasRanFromLauncher() == false)
|
|
{
|
|
// Tell Launcher to run us instead
|
|
ILauncherCheckModule::Get().RunLauncher(ELauncherAction::AppLaunch);
|
|
// We wish to exit
|
|
RequestEngineExit(TEXT("Run outside of launcher; restarting via launcher"));
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#if STATS && UE_STATS_MEMORY_PROFILER_ENABLED
|
|
// Create the stats malloc profiler proxy.
|
|
if (FStatsMallocProfilerProxy::HasMemoryProfilerToken())
|
|
{
|
|
if (PLATFORM_USES_FIXED_GMalloc_CLASS)
|
|
{
|
|
UE_LOG(LogMemory, Fatal, TEXT("Cannot do malloc profiling with PLATFORM_USES_FIXED_GMalloc_CLASS."));
|
|
}
|
|
// Assumes no concurrency here.
|
|
UE::Private::GMalloc = FStatsMallocProfilerProxy::Get();
|
|
}
|
|
#endif // STATS && UE_STATS_MEMORY_PROFILER_ENABLED
|
|
|
|
#if WITH_APPLICATION_CORE
|
|
{
|
|
SCOPED_BOOT_TIMING("CreateConsoleOutputDevice");
|
|
// Initialize log console here to avoid statics initialization issues when launched from the command line.
|
|
GScopedLogConsole = TUniquePtr<FOutputDeviceConsole>(FPlatformApplicationMisc::CreateConsoleOutputDevice());
|
|
}
|
|
#endif
|
|
|
|
// Initialize std out device as early as possible if requested in the command line
|
|
#if PLATFORM_DESKTOP
|
|
// consoles don't typically have stdout, and FOutputDeviceDebug is responsible for echoing logs to the
|
|
// terminal
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("stdout")))
|
|
{
|
|
InitializeStdOutDevice();
|
|
}
|
|
#endif
|
|
|
|
if (FParse::Param(CmdLine, TEXT("UTF8Output")))
|
|
{
|
|
// UE_DEPRECATED(5.6, "This comment exists to describe when -UTF8Output was deprecated and allow finding it in a search for UE 5.6 deprecations.")
|
|
UE_LOG(LogInit, Display, TEXT("Output is UTF-8 by default. Use of -UTF8Output on the command is deprecated and can be removed."));
|
|
}
|
|
|
|
// If we are in Debug, Development, or Test/Shipping with ALLOW_PROFILEGPU... enabled, then automatically allow draw events.
|
|
// Command lines allow disabling of draw events if WITH_PROGILEGPU is enabled. Test/Shipping on their own require explicit opt in.
|
|
#if WITH_PROFILEGPU
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("nodrawevents")))
|
|
{
|
|
SetEmitDrawEvents(true);
|
|
}
|
|
// Continue to protect shipping build from emitdrawevents (if needed in shipping, ALLOW_PROFILEGPU_IN_SHIPPING should be used instead to enable WITH_PROFILEGPU)
|
|
#elif !UE_BUILD_SHIPPING
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("emitdrawevents")))
|
|
{
|
|
SetEmitDrawEvents(true);
|
|
}
|
|
#endif
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FPlatformProperties::SupportsQuit())
|
|
{
|
|
FString ExitPhrases;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("testexit="), ExitPhrases))
|
|
{
|
|
TArray<FString> ExitPhrasesList;
|
|
if (ExitPhrases.ParseIntoArray(ExitPhrasesList, TEXT("+"), true) > 0)
|
|
{
|
|
GScopedTestExit = MakeUnique<FOutputDeviceTestExit>(MoveTemp(ExitPhrasesList));
|
|
GLog->AddOutputDevice(GScopedTestExit.Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Activates malloc frame profiler from the command line
|
|
// Recommend enabling bGenerateSymbols to ensure callstacks can resolve and bRetainFramePointers to ensure frame pointers remain valid.
|
|
// Also disabling the hitch detector ALLOW_HITCH_DETECTION=0 helps ensure quicker more accurate runs.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("mallocframeprofiler")))
|
|
{
|
|
GMallocFrameProfilerEnabled = true;
|
|
UE::Private::GMalloc = FMallocFrameProfiler::OverrideIfEnabled(UE::Private::GMalloc);
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// Switch into executable's directory (may be required by some of the platform file overrides)
|
|
#if !defined(DISABLE_CWD_CHANGES) || DISABLE_CWD_CHANGES==0
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
#endif
|
|
|
|
// This fixes up the relative project path, needs to happen before we set platform file paths
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
SCOPED_BOOT_TIMING("Fix up the relative project path");
|
|
|
|
if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
FString ProjPath = FPaths::GetProjectFilePath();
|
|
if (FPaths::FileExists(ProjPath) == false)
|
|
{
|
|
// display it multiple ways, it's very important error message...
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Project file not found: %s\n"), *ProjPath);
|
|
UE_LOG(LogInit, Display, TEXT("Project file not found: %s"), *ProjPath);
|
|
UE_LOG(LogInit, Display, TEXT("\tAttempting to find via project info helper."));
|
|
// Use the uprojectdirs
|
|
FString GameProjectFile = FUProjectDictionary::GetDefault().GetRelativeProjectPathForGame(FApp::GetProjectName(), FPlatformProcess::BaseDir());
|
|
if (GameProjectFile.IsEmpty() == false)
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("\tFound project file %s."), *GameProjectFile);
|
|
FPaths::SetProjectFilePath(GameProjectFile);
|
|
|
|
// Fixup command line if project file wasn't found in specified directory to properly parse next arguments.
|
|
FString OldCommandLine = FString(FCommandLine::Get());
|
|
OldCommandLine.ReplaceInline(*GameProjectFilePathUnnormalized, *GameProjectFile, ESearchCase::CaseSensitive);
|
|
FCommandLine::Set(*OldCommandLine);
|
|
CmdLine = FCommandLine::Get();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !IS_PROGRAM && !IS_MONOLITHIC
|
|
FString EngineBinariesRootDirectory = FPlatformMisc::EngineDir();
|
|
#endif
|
|
FString ProjectBinariesRootDirectory;
|
|
if (FApp::HasProjectName())
|
|
{
|
|
ProjectBinariesRootDirectory = FPlatformMisc::ProjectDir();
|
|
#if !IS_MONOLITHIC
|
|
#if !IS_PROGRAM
|
|
FPlatformMisc::GetEngineAndProjectAbsoluteDirsFromExecutable(ProjectBinariesRootDirectory, EngineBinariesRootDirectory);
|
|
#endif
|
|
|
|
// Loading preinit/common module if it exists.
|
|
// PreInit module can contain things like decryption logic for pak files and things that is project specific but needs to initialize very early
|
|
// Common module is a merged binary containing all the important modules for the project
|
|
TArray<FString> FileNames =
|
|
{
|
|
#if UE_MERGED_MODULES
|
|
FString::Printf(TEXT("%s-Common.%s"), FPlatformProcess::ExecutableName(), FPlatformProcess::GetModuleExtension()),
|
|
FString::Printf(TEXT("%s-Common-%s-%s.%s"), FPlatformProcess::ExecutableName(), FPlatformProcess::GetBinariesSubdirectory(), LexToString(FApp::GetBuildConfiguration()), FPlatformProcess::GetModuleExtension()),
|
|
#else
|
|
FString::Printf(TEXT("%s-%sPreInit.%s"), FPlatformProcess::ExecutableName(), FApp::GetProjectName(), FPlatformProcess::GetModuleExtension()),
|
|
FString::Printf(TEXT("%s-%sPreInit-%s-%s.%s"), *FApp::GetName(), FApp::GetProjectName(), FPlatformProcess::GetBinariesSubdirectory(), LexToString(FApp::GetBuildConfiguration()), FPlatformProcess::GetModuleExtension())
|
|
#endif
|
|
};
|
|
|
|
// UE-230629: On mac UnrealLightmass this would cause assert on main thread
|
|
#if PLATFORM_MAC
|
|
if (![NSThread isMainThread])
|
|
{
|
|
#endif
|
|
FTaskTagScope scope(ETaskTag::EStaticInit);
|
|
#if PLATFORM_MAC
|
|
}
|
|
#endif
|
|
|
|
for (const FString& FileName : FileNames)
|
|
{
|
|
FString ModulePath = FPaths::Combine(ProjectBinariesRootDirectory, "Binaries", FPlatformProcess::GetBinariesSubdirectory(), FileName);
|
|
if (FPaths::FileExists(ModulePath))
|
|
{
|
|
FPlatformProcess::GetDllHandle(*ModulePath);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Output devices.
|
|
{
|
|
SCOPED_BOOT_TIMING("Init Output Devices");
|
|
#if WITH_APPLICATION_CORE
|
|
GError = FPlatformApplicationMisc::GetErrorOutputDevice();
|
|
GWarn = FPlatformApplicationMisc::GetFeedbackContext();
|
|
#else
|
|
GError = FPlatformOutputDevices::GetError();
|
|
GWarn = FPlatformOutputDevices::GetFeedbackContext();
|
|
#endif
|
|
|
|
FCoreDelegates::OnOutputDevicesInit.Broadcast();
|
|
}
|
|
|
|
// Avoiding potential exploits by not exposing command line overrides in the shipping games.
|
|
#if !UE_BUILD_SHIPPING
|
|
{
|
|
SCOPED_BOOT_TIMING("Command Line Adjustments");
|
|
|
|
FConfigFile CommandLineAliasesConfigFile;
|
|
if (FPaths::ProjectDir().Len() > 0)
|
|
{
|
|
// Pass EngineIntermediateDir as GeneratedConfigDir of FPaths::GeneratedConfigDir() so that saved directory is not cached before -saveddir argument can be added
|
|
FConfigCacheIni::LoadExternalIniFile(CommandLineAliasesConfigFile, TEXT("CommandLineAliases"), nullptr, *FPaths::Combine(FPaths::ProjectDir(), TEXT("Config")),
|
|
/*bIsBaseIniName=*/ false,
|
|
/*Platform=*/ nullptr,
|
|
/*bForceReload=*/ false,
|
|
/*bWriteDestIni=*/ false,
|
|
/*bAllowGeneratedIniWhenCooked=*/ true,
|
|
/*GeneratedConfigDir=*/ *FPaths::EngineIntermediateDir());
|
|
}
|
|
|
|
TArray<FString> PrevAliasExpansions;
|
|
TArray<FString> PrevCmdLineFileExpansions;
|
|
|
|
bool bChanged = false;
|
|
for(;;)
|
|
{
|
|
bool bExpandedAliases = false;
|
|
LaunchCheckForCommandLineAliases(CommandLineAliasesConfigFile, PrevAliasExpansions, bExpandedAliases);
|
|
|
|
bool bExpandedCmdLineFile = false;
|
|
LaunchCheckForCmdLineFile(PrevCmdLineFileExpansions, bExpandedCmdLineFile);
|
|
|
|
if(bExpandedAliases || bExpandedCmdLineFile)
|
|
{
|
|
bChanged = true;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bChanged)
|
|
{
|
|
CmdLine = FCommandLine::Get();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if USE_IO_DISPATCHER
|
|
if (FIoStatus Status = FIoDispatcher::Initialize(); !Status.IsOk())
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Failed to initialize I/O dispatcher: '%s'"), *Status.ToString());
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("BeginPreInitTextLocalization");
|
|
BeginPreInitTextLocalization();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
{
|
|
SCOPED_BOOT_TIMING("PreInitShaderLibrary");
|
|
FShaderCodeLibrary::PreInit();
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
// allow the command line to override the platform file singleton
|
|
bool bFileOverrideFound = false;
|
|
{
|
|
SCOPED_BOOT_TIMING("LaunchCheckForFileOverride");
|
|
if (LaunchCheckForFileOverride(CmdLine, bFileOverrideFound) == false)
|
|
{
|
|
// if it failed, we cannot continue
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
#if PLATFORM_DESKTOP && !IS_MONOLITHIC
|
|
{
|
|
SCOPED_BOOT_TIMING("AddExtraBinarySearchPaths");
|
|
FModuleManager::Get().AddExtraBinarySearchPaths();
|
|
}
|
|
#endif
|
|
|
|
// Initialize file manager
|
|
{
|
|
SCOPED_BOOT_TIMING("IFileManager::Get().ProcessCommandLineOptions");
|
|
IFileManager::Get().ProcessCommandLineOptions();
|
|
}
|
|
|
|
#if WITH_COREUOBJECT
|
|
{
|
|
SCOPED_BOOT_TIMING("InitializeNewAsyncIO");
|
|
FPlatformFileManager::Get().InitializeNewAsyncIO();
|
|
}
|
|
#endif
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::FileSystemReady);
|
|
|
|
if (GIsGameAgnosticExe)
|
|
{
|
|
// If we launched without a project file, but with a game name that is incomplete, warn about the improper use of a Game suffix
|
|
if (LaunchHasIncompleteGameName())
|
|
{
|
|
// We did not find a non-suffixed folder and we DID find the suffixed one.
|
|
// The engine MUST be launched with <GameName>Game.
|
|
const FText GameNameText = FText::FromString(FApp::GetProjectName());
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RequiresGamePrefix", "Error: UnrealEditor does not append 'Game' to the passed in game name.\nYou must use the full name.\nYou specified '{0}', use '{0}Game'."), GameNameText));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// remember thread id of the main thread
|
|
GGameThreadId = FPlatformTLS::GetCurrentThreadId();
|
|
GIsGameThreadIdInitialized = true;
|
|
|
|
FPlatformProcess::SetThreadPriority(FPlatformAffinity::GetGameThreadPriority());
|
|
FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetMainGameMask());
|
|
FPlatformProcess::SetupGameThread();
|
|
|
|
// These bools are the mode selector and are mutually exclusive. This function selects the mode by calling one of the
|
|
// SetIsRunningAs* lambdas, which it does based on commandline and configuration considerations in a specific order.
|
|
// TODO: Convert these bools to an enum since they are mutually exclusive.
|
|
bool bHasCommandletToken = false;
|
|
bool bHasEditorToken = false;
|
|
bool bIsRunningAsDedicatedServer = false;
|
|
bool bIsRegularClient = false;
|
|
|
|
FString TokenToForward;
|
|
|
|
// these tokens are use later to restore a single commandline string, so keep a version that has quotes around tokens,
|
|
// in case there are spaces in a token which will break parsing that single string
|
|
TArray<FString> TokenArray = TokenizeCommandline(CmdLine, false);
|
|
TArray<FString> QuotedTokenArray = TokenizeCommandline(CmdLine, true);
|
|
|
|
EGameStringType GameStringType = EGameStringType::Unknown;
|
|
FString GameString = ExtractGameStringArgument(TokenArray, QuotedTokenArray, GameStringType);
|
|
|
|
auto IsModeSelected = [&bHasCommandletToken, &bHasEditorToken, &bIsRegularClient, &bIsRunningAsDedicatedServer]()
|
|
{
|
|
return bHasCommandletToken || bHasEditorToken || bIsRegularClient || bIsRunningAsDedicatedServer;
|
|
};
|
|
#if UE_EDITOR || WITH_ENGINE || WITH_EDITOR
|
|
FString CommandletCommandLine;
|
|
auto SetIsRunningAsCommandlet = [&CommandletCommandLine, &QuotedTokenArray,
|
|
&bHasCommandletToken, &bIsRunningAsDedicatedServer, &bHasEditorToken, &bIsRegularClient, &IsModeSelected, &TokenToForward]
|
|
(FStringView CommandletName)
|
|
{
|
|
checkf(!IsModeSelected(), TEXT("SetIsRunningAsCommandlet should not be called after mode has been selected."));
|
|
bHasCommandletToken = true;
|
|
|
|
TokenToForward = CommandletName;
|
|
|
|
GIsClient = true;
|
|
GIsServer = true;
|
|
#if WITH_EDITORONLY_DATA
|
|
GIsEditor = true;
|
|
#endif
|
|
#if STATS
|
|
// Leave the stats enabled.
|
|
if (!UE::Stats::FStats::EnabledForCommandlet())
|
|
{
|
|
FThreadStats::PrimaryDisableForever();
|
|
}
|
|
#endif
|
|
|
|
CommandletCommandLine = FString::Join(QuotedTokenArray, TEXT(" "));
|
|
#if WITH_EDITOR
|
|
if (CommandletName == TEXTVIEW("cookcommandlet"))
|
|
{
|
|
PRIVATE_GIsRunningCookCommandlet = true;
|
|
UE::Cook::InitializeCookGlobals();
|
|
PRIVATE_GIsRunningDLCCookCommandlet = (FCString::Strfind(FCommandLine::Get(), TEXT("-dlcname=")) != nullptr);
|
|
}
|
|
#endif
|
|
#if WITH_ENGINE
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
#endif
|
|
// Allow commandlet rendering and/or audio based on command line switch (too early to let the commandlet itself override this).
|
|
PRIVATE_GAllowCommandletRendering = FParse::Param(FCommandLine::Get(), TEXT("AllowCommandletRendering"));
|
|
PRIVATE_GAllowCommandletAudio = FParse::Param(FCommandLine::Get(), TEXT("AllowCommandletAudio"));
|
|
};
|
|
#endif // UE_EDITOR || WITH_ENGINE || WITH_EDITOR
|
|
auto SetIsRunningAsRegularClient = [&IsModeSelected, &bIsRegularClient, &TokenArray, &TokenToForward]()
|
|
{
|
|
checkf(!IsModeSelected(), TEXT("SetIsRunningAsRegularClient should not be called after mode has been selected."));
|
|
bIsRegularClient = true;
|
|
|
|
// Take the first non-switch command-line parameter (i.e. one not starting with a dash) and remember
|
|
// it to check later as it could be a mistyped commandlet (short name).
|
|
const FString* NonSwitchParam = TokenArray.FindByPredicate(
|
|
[](const FString& Token)
|
|
{
|
|
return Token[0] != TCHAR('-');
|
|
}
|
|
);
|
|
|
|
if (NonSwitchParam)
|
|
{
|
|
TokenToForward = *NonSwitchParam;
|
|
}
|
|
|
|
GIsClient = true;
|
|
GIsServer = false;
|
|
#if WITH_EDITORONLY_DATA
|
|
GIsEditor = false;
|
|
#endif
|
|
#if WITH_ENGINE
|
|
checkf(!PRIVATE_GIsRunningCommandlet, TEXT("It should not be possible for PRIVATE_GIsRunningCommandlet to have been set when calling SetIsRunningAsRegularClient"));
|
|
#endif
|
|
};
|
|
auto SetIsRunningAsDedicatedServer = [&IsModeSelected, &bIsRunningAsDedicatedServer]()
|
|
{
|
|
checkf(!IsModeSelected(), TEXT("SetIsRunningAsDedicatedServer should not be called after mode has been selected."));
|
|
bIsRunningAsDedicatedServer = true;
|
|
|
|
GIsClient = false;
|
|
GIsServer = true;
|
|
#if WITH_EDITORONLY_DATA
|
|
GIsEditor = false;
|
|
#endif
|
|
#if WITH_ENGINE
|
|
checkf(!PRIVATE_GIsRunningCommandlet, TEXT("It should not be possible for PRIVATE_GIsRunningCommandlet to have been set when calling SetIsRunningAsDedicatedServer"));
|
|
#endif
|
|
};
|
|
#if WITH_EDITORONLY_DATA
|
|
auto SetIsRunningAsEditor = [&IsModeSelected, &bHasEditorToken]()
|
|
{
|
|
checkf(!IsModeSelected(), TEXT("SetIsRunningAsEditor should not be called after mode has been selected."));
|
|
bHasEditorToken = true;
|
|
|
|
GIsClient = true;
|
|
GIsServer = true;
|
|
GIsEditor = true;
|
|
#if WITH_ENGINE
|
|
checkf(!PRIVATE_GIsRunningCommandlet, TEXT("It should not be possible for PRIVATE_GIsRunningCommandlet to have been set when calling SetIsRunningAsEditor"));
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
if (IsRunningDedicatedServer())
|
|
{
|
|
SetIsRunningAsDedicatedServer();
|
|
}
|
|
|
|
#if UE_EDITOR || WITH_ENGINE || WITH_EDITOR
|
|
TArray<FString> NonSwitchTokens;
|
|
TArray<FString> Switches;
|
|
UCommandlet::ParseCommandLine(CmdLine, NonSwitchTokens, Switches);
|
|
if (!IsModeSelected())
|
|
{
|
|
for (const FString& ParsedToken : NonSwitchTokens)
|
|
{
|
|
if (ParsedToken.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
SetIsRunningAsCommandlet(ParsedToken.TrimStartAndEnd());
|
|
break;
|
|
}
|
|
}
|
|
if (!bHasCommandletToken)
|
|
{
|
|
for (const FString& ParsedSwitch : Switches)
|
|
{
|
|
if (ParsedSwitch.StartsWith(TEXT("RUN=")))
|
|
{
|
|
FString LocalToken = ParsedSwitch.RightChop(4);
|
|
LocalToken.TrimStartAndEndInline();
|
|
if (!LocalToken.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
LocalToken += TEXT("Commandlet");
|
|
}
|
|
SetIsRunningAsCommandlet(LocalToken);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // UE_EDITOR || WITH_ENGINE || WITH_EDITOR
|
|
#if WITH_EDITOR
|
|
int32 MultiprocessId;
|
|
if (FParse::Value(CmdLine, TEXT("-MultiprocessId="), MultiprocessId))
|
|
{
|
|
UE::Private::SetMultiprocessId(MultiprocessId);
|
|
}
|
|
#endif
|
|
|
|
|
|
// In the commandlet case, we have set the Token to something other than the first token from commandline.
|
|
// In all other cases we should check for the first token being the Project Specifier
|
|
if (!bHasCommandletToken)
|
|
{
|
|
if (GameStringType != EGameStringType::Unknown)
|
|
{
|
|
// Set a new command-line that doesn't include the game name as the first argument.
|
|
FCommandLine::Set(*FString::Join(QuotedTokenArray, TEXT(" ")));
|
|
|
|
// Remove spurious project file tokens (which can happen on some platforms that combine commandlines).
|
|
// This handles extra .uprojects, but if you run with MyGame MyGame, we can't tell if the second MyGame is a map or not.
|
|
TokenArray.RemoveAll(
|
|
[](const FString& Token)
|
|
{
|
|
return Token[0] != TCHAR('-') && FPaths::GetExtension(Token) == FProjectDescriptor::GetExtension();
|
|
}
|
|
);
|
|
|
|
if (GameStringType == EGameStringType::ProjectPath || GameStringType == EGameStringType::ProjectShortName)
|
|
{
|
|
// Convert it to relative if possible...
|
|
FString RelativeGameProjectFilePath = FFileManagerGeneric::DefaultConvertToRelativePath(*FPaths::GetProjectFilePath());
|
|
if (RelativeGameProjectFilePath != FPaths::GetProjectFilePath())
|
|
{
|
|
FPaths::SetProjectFilePath(RelativeGameProjectFilePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UE_EDITOR
|
|
// Handle the first token being '-game' or '-server'
|
|
if (!TokenArray.IsEmpty())
|
|
{
|
|
if (TokenArray[0] == TEXT("-GAME") || TokenArray[0] == TEXT("-SERVER"))
|
|
{
|
|
// This isn't necessarily pretty, but many requests have been made to allow
|
|
// UnrealEditor.exe <GAMENAME> -game <map>
|
|
// or
|
|
// UnrealEditor.exe <GAMENAME> -game 127.0.0.0
|
|
// We don't want to remove the -game from the commandline just yet in case
|
|
// we need it for something later. So, just move it to the end for now...
|
|
FString LocalToken = TokenArray[0];
|
|
TokenArray.Add(LocalToken);
|
|
TokenArray.RemoveAt(0);
|
|
QuotedTokenArray.Add(LocalToken);
|
|
QuotedTokenArray.RemoveAt(0);
|
|
|
|
FCommandLine::Set(*FString::Join(QuotedTokenArray, TEXT(" ")));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// If we have no Commandlet or -server, test if we are running as the editor, and set the GIsEditor/GIsClient/GIsServer flags if so.
|
|
// Do this early (and certainly before AppInit) so plugin-consumed library code can know (they wouldn't otherwise)
|
|
// Things like the config system behave differently based on these globals, and we aren't the editor by default.
|
|
if (!IsModeSelected())
|
|
{
|
|
#if UE_EDITOR
|
|
if (!Switches.Contains(TEXT("GAME")))
|
|
{
|
|
SetIsRunningAsEditor();
|
|
}
|
|
#elif WITH_ENGINE && WITH_EDITOR && WITH_EDITORONLY_DATA
|
|
// If a non-editor target build w/ WITH_EDITOR and WITH_EDITORONLY_DATA, use the old token check...
|
|
//@todo. Is this something we need to support?
|
|
if (TokenArray.Contains(TEXT("EDITOR")))
|
|
{
|
|
SetIsRunningAsEditor();
|
|
}
|
|
#endif
|
|
// Game, server and non-engine programs never run as the editor
|
|
}
|
|
|
|
#if UE_EDITOR
|
|
// In the UE_EDITOR configuration we now know enough to finish the mode decision and decide between commandlet or client
|
|
// In other configurations we still may need to check the Token for whether it is a commandlet and we make the decision below
|
|
if (!IsModeSelected())
|
|
{
|
|
SetIsRunningAsRegularClient();
|
|
}
|
|
|
|
if (bHasEditorToken && GIsGameAgnosticExe)
|
|
{
|
|
// If we launched the editor IDE (e.g. not commandlet or -game) without a game name or project name, try to load the most recently loaded project file.
|
|
// We can not do this if we are using a FilePlatform override since the game directory may already be established.
|
|
const bool bIsBuildMachine = FParse::Param(FCommandLine::Get(), TEXT("BUILDMACHINE"));
|
|
const bool bLoadMostRecentProjectFileIfItExists = !FApp::HasProjectName() && !bFileOverrideFound && !bIsBuildMachine && !FParse::Param(CmdLine, TEXT("norecentproject"));
|
|
if (bLoadMostRecentProjectFileIfItExists)
|
|
{
|
|
LaunchUpdateMostRecentProjectFile();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Benchmarking.
|
|
FApp::SetBenchmarking(FParse::Param(FCommandLine::Get(), TEXT("BENCHMARK")));
|
|
#else
|
|
FApp::SetBenchmarking(false);
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
#if WITH_FIXED_TIME_STEP_SUPPORT
|
|
// "-Deterministic" is a shortcut for "-UseFixedTimeStep -FixedSeed"
|
|
bool bDeterministic = FParse::Param(FCommandLine::Get(), TEXT("Deterministic"));
|
|
|
|
FApp::SetUseFixedTimeStep(bDeterministic || FParse::Param(FCommandLine::Get(), TEXT("UseFixedTimeStep")));
|
|
|
|
FApp::bUseFixedSeed = bDeterministic || FApp::IsBenchmarking() || FParse::Param(FCommandLine::Get(), TEXT("FixedSeed"));
|
|
#endif
|
|
|
|
// Initialize random number generator.
|
|
{
|
|
uint32 Seed1 = 0;
|
|
uint32 Seed2 = 0;
|
|
|
|
if (!FApp::bUseFixedSeed)
|
|
{
|
|
Seed1 = FPlatformTime::Cycles();
|
|
Seed2 = FPlatformTime::Cycles();
|
|
}
|
|
|
|
FMath::RandInit(Seed1);
|
|
FMath::SRandInit(Seed2);
|
|
|
|
UE_LOG(LogInit, Verbose, TEXT("RandInit(%d) SRandInit(%d)."), Seed1, Seed2);
|
|
}
|
|
|
|
#if !IS_PROGRAM
|
|
if (!GIsGameAgnosticExe && FApp::HasProjectName() && !FPaths::IsProjectFilePathSet())
|
|
{
|
|
// If we are using a non-agnostic exe where a name was specified but we did not specify a project path. Assemble one based on the game name.
|
|
const FString ProjectFilePath = FPaths::Combine(*FPaths::ProjectDir(), *FString::Printf(TEXT("%s.%s"), FApp::GetProjectName(), *FProjectDescriptor::GetExtension()));
|
|
FPaths::SetProjectFilePath(ProjectFilePath);
|
|
}
|
|
#endif
|
|
|
|
// Initialize platform file with knowledge of the project file path before fixing the casing
|
|
IPlatformFile* CurrentPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("PlatformFileChainElement->InitializeAfterProjectFilePath");
|
|
for (IPlatformFile* PlatformFileChainElement = CurrentPlatformFile; PlatformFileChainElement; PlatformFileChainElement = PlatformFileChainElement->GetLowerLevel())
|
|
{
|
|
PlatformFileChainElement->InitializeAfterProjectFilePath();
|
|
}
|
|
}
|
|
|
|
#if !IS_PROGRAM
|
|
// Now let the platform file fix the project file path case before we attempt to fix the game name
|
|
LaunchFixProjectPathCase();
|
|
#endif
|
|
|
|
// Now verify the project file if we have one
|
|
if (FPaths::IsProjectFilePathSet()
|
|
#if IS_PROGRAM
|
|
// Programs don't need uproject files to exist, but some do specify them and if they exist we should load them
|
|
&& FPaths::FileExists(FPaths::GetProjectFilePath())
|
|
#endif
|
|
)
|
|
{
|
|
SCOPED_BOOT_TIMING("IProjectManager::Get().LoadProjectFile");
|
|
|
|
if (!IProjectManager::Get().LoadProjectFile(FPaths::GetProjectFilePath()))
|
|
{
|
|
// The project file was invalid or saved with a newer version of the engine. Exit.
|
|
UE_LOG(LogInit, Warning, TEXT("Could not find a valid project file, the engine will exit now."));
|
|
return 1;
|
|
}
|
|
|
|
if (IProjectManager::Get().IsEnterpriseProject() && FPaths::DirectoryExists(FPaths::EnterpriseDir()))
|
|
{
|
|
// Add the enterprise binaries directory if we're an enterprise project
|
|
FModuleManager::Get().AddBinariesDirectory(*FPaths::Combine(FPaths::EnterpriseDir(), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory()), false);
|
|
}
|
|
|
|
if (!ReplacementProjectModuleName.IsEmpty())
|
|
{
|
|
IProjectManager::Get().SubstituteModule(OriginalProjectModuleName, ReplacementProjectModuleName);
|
|
}
|
|
}
|
|
|
|
#if !IS_PROGRAM
|
|
if (FApp::HasProjectName())
|
|
{
|
|
// Tell the module manager what the game binaries folder is
|
|
FString ProjectBinariesDirectory = FPaths::Combine(ProjectBinariesRootDirectory, TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory());
|
|
FPlatformProcess::AddDllDirectory(*ProjectBinariesDirectory);
|
|
FModuleManager::Get().SetGameBinariesDirectory(*ProjectBinariesDirectory);
|
|
|
|
LaunchFixGameNameCase();
|
|
}
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
// Add the default engine shader dir
|
|
AddShaderSourceDirectoryMapping(TEXT("/Engine"), FPlatformProcess::ShaderDir());
|
|
|
|
// Add <PROJECT>/Shaders/ folder to virtual shader include directories
|
|
const FString ProjectShaderPath = FPaths::Combine(FPaths::ProjectDir(), TEXT("Shaders"));
|
|
if (FPaths::DirectoryExists(ProjectShaderPath))
|
|
{
|
|
AddShaderSourceDirectoryMapping(TEXT("/Project"), ProjectShaderPath);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
{
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
FString ProjectIntermediateDir = FPaths::ProjectIntermediateDir();
|
|
bool bCreateIntermediateSuccess = PlatformFile.CreateDirectoryTree(*ProjectIntermediateDir);
|
|
if (!bCreateIntermediateSuccess)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to create Intermediate directory '%s'."), *ProjectIntermediateDir);
|
|
}
|
|
|
|
FString AutogenAbsolutePath = FPaths::ConvertRelativePathToFull(ProjectIntermediateDir / TEXT("ShaderAutogen"));
|
|
bool bCreateAutogenSuccess = PlatformFile.CreateDirectory(*AutogenAbsolutePath);
|
|
if (!bCreateAutogenSuccess)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to create Intermediate/ShaderAutogen/ directory '%s'. Make sure Intermediate exists."), *AutogenAbsolutePath);
|
|
}
|
|
|
|
AddShaderSourceDirectoryMapping(TEXT("/ShaderAutogen"), AutogenAbsolutePath);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
#endif //WITH_ENGINE
|
|
|
|
// Some programs might not use the taskgraph or thread pool
|
|
bool bCreateTaskGraphAndThreadPools = true;
|
|
// If STATS is defined (via FORCE_USE_STATS or other), we have to call FTaskGraphInterface::Startup()
|
|
#if IS_PROGRAM && !STATS
|
|
bCreateTaskGraphAndThreadPools = !FParse::Param(FCommandLine::Get(), TEXT("ReduceThreadUsage"));
|
|
#endif
|
|
if (bCreateTaskGraphAndThreadPools)
|
|
{
|
|
// initialize task graph sub-system with potential multiple threads
|
|
SCOPED_BOOT_TIMING("FTaskGraphInterface::Startup");
|
|
FTaskGraphInterface::Startup(FPlatformMisc::NumberOfWorkerThreadsToSpawn());
|
|
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread);
|
|
}
|
|
|
|
#if WITH_EDITOR && PLATFORM_WINDOWS
|
|
FWindowsPlatformPerfCounters::Init();
|
|
#endif
|
|
|
|
if (FPlatformProcess::SupportsMultithreading() && bCreateTaskGraphAndThreadPools)
|
|
{
|
|
SCOPED_BOOT_TIMING("Init FQueuedThreadPool's");
|
|
|
|
int32 StackSize = 128 * 1024;
|
|
// GConfig is not initialized yet, the only solution for now is to hardcode desired values
|
|
// GConfig->GetInt(TEXT("Core.System"), TEXT("PoolThreadStackSize"), StackSize, GEngineIni);
|
|
|
|
bool bForceEditorStackSize = false;
|
|
#if WITH_EDITOR
|
|
bForceEditorStackSize = true;
|
|
#endif
|
|
|
|
if (bHasEditorToken || bForceEditorStackSize)
|
|
{
|
|
StackSize = 1024 * 1024;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
{
|
|
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
|
|
// when we are in the editor we like to do things like build lighting and such
|
|
// this thread pool can be used for those purposes
|
|
GLargeThreadPool = new FQueuedLowLevelThreadPool();
|
|
|
|
// we are only going to give dedicated servers one pool thread
|
|
if (FPlatformProperties::IsServerOnly())
|
|
{
|
|
NumThreadsInThreadPool = 1;
|
|
}
|
|
|
|
// GThreadPool will schedule on the LargeThreadPool but limit max concurrency to the given number.
|
|
GThreadPool = new FQueuedThreadPoolWrapper(GLargeThreadPool, NumThreadsInThreadPool);
|
|
}
|
|
#else
|
|
{
|
|
GThreadPool = new FQueuedLowLevelThreadPool();
|
|
}
|
|
#endif
|
|
{
|
|
GBackgroundPriorityThreadPool = FQueuedThreadPool::Allocate();
|
|
int32 NumThreadsInThreadPool = 2;
|
|
if (FPlatformProperties::IsServerOnly())
|
|
{
|
|
NumThreadsInThreadPool = 1;
|
|
}
|
|
|
|
verify(GBackgroundPriorityThreadPool->Create(NumThreadsInThreadPool, StackSize, TPri_Lowest, TEXT("BackgroundThreadPool")));
|
|
}
|
|
}
|
|
|
|
// this can start using TaskGraph and ThreadPool so they must be created before
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::TaskGraphSystemReady);
|
|
|
|
#if !IS_PROGRAM && !IS_MONOLITHIC
|
|
if (FApp::HasProjectName() && FCString::Strcmp(CurrentPlatformFile->GetName(), TEXT("PakFile")) == 0)
|
|
{
|
|
IPluginManager::Get().SetBinariesRootDirectories(EngineBinariesRootDirectory, ProjectBinariesRootDirectory);
|
|
IPluginManager::Get().SetPreloadBinaries();
|
|
}
|
|
#endif
|
|
|
|
UE::Stats::FStats::Init();
|
|
#if STATS
|
|
FThreadStats::StartThread();
|
|
#endif
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::StatSystemReady);
|
|
|
|
FScopeCycleCounter CycleCount_AfterStats(GET_STATID(STAT_FEngineLoop_PreInitPreStartupScreen_AfterStats));
|
|
|
|
// Load Core modules required for everything else to work (needs to be loaded before InitializeRenderingCVarsCaching)
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadCoreModules");
|
|
if (!LoadCoreModules())
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Failed to load Core modules."));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const bool bDumpEarlyConfigReads = FParse::Param(FCommandLine::Get(), TEXT("DumpEarlyConfigReads"));
|
|
const bool bDumpEarlyPakFileReads = FParse::Param(FCommandLine::Get(), TEXT("DumpEarlyPakFileReads"));
|
|
const bool bForceQuitAfterEarlyReads = FParse::Param(FCommandLine::Get(), TEXT("ForceQuitAfterEarlyReads"));
|
|
|
|
// Overly verbose to avoid a dumb static analysis warning
|
|
#if WITH_CONFIG_PATCHING
|
|
constexpr bool bWithConfigPatching = true;
|
|
#else
|
|
constexpr bool bWithConfigPatching = false;
|
|
#endif
|
|
|
|
if (bDumpEarlyConfigReads)
|
|
{
|
|
UE::ConfigUtilities::RecordConfigReadsFromIni();
|
|
}
|
|
|
|
if (bDumpEarlyPakFileReads)
|
|
{
|
|
RecordFileReadsFromPaks();
|
|
}
|
|
|
|
if(bWithConfigPatching)
|
|
{
|
|
UE_LOG(LogInit, Verbose, TEXT("Begin recording CVar changes for config patching."));
|
|
|
|
UE::ConfigUtilities::RecordApplyCVarSettingsFromIni();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
extern ENGINE_API void InitializeRenderingCVarsCaching();
|
|
InitializeRenderingCVarsCaching();
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
// If we're running as a game or server but don't have a project, inform the user and exit.
|
|
if (bHasEditorToken == false && bHasCommandletToken == false)
|
|
{
|
|
if (!FPaths::IsProjectFilePathSet())
|
|
{
|
|
//@todo this is too early to localize
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Engine", "UERequiresProjectFiles", "Unreal Engine games require a project file as the first parameter."));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#endif //WITH_EDITOR
|
|
|
|
#if WITH_APPLICATION_CORE
|
|
// Get a pointer to the log output device
|
|
GLogConsole = GScopedLogConsole.Get();
|
|
#endif
|
|
|
|
// init Oodle here
|
|
FOodleDataCompression::StartupPreInit();
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadPreInitModules");
|
|
LoadPreInitModules();
|
|
}
|
|
|
|
#if WITH_ENGINE && CSV_PROFILER
|
|
FCsvProfiler::Get()->Init();
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
AppLifetimeEventCapture::Init();
|
|
|
|
if (bHasEditorToken)
|
|
{
|
|
#if WITH_EDITOR
|
|
GWarn = &UnrealEdWarn;
|
|
#else //WITH_EDITOR
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Engine", "EditorNotSupported", "Editor not supported in this mode."));
|
|
FPlatformMisc::RequestExit(false, TEXT("FEngineLoop::PreInitPreStartupScreen.bHasEditorToken"));
|
|
return 1;
|
|
#endif //WITH_EDITOR
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
#if WITH_ENGINE && FRAMEPRO_ENABLED
|
|
FFrameProProfiler::Initialize();
|
|
#endif // FRAMEPRO_ENABLED
|
|
|
|
// Start the application
|
|
{
|
|
SCOPED_BOOT_TIMING("AppInit");
|
|
if (!AppInit())
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Try to start the dedicated primary thread now that the command line is available,
|
|
// and the default output devices have been created. Initializing earlier will cause
|
|
// logs to be missed by the default output devices.
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("NoLogThread")))
|
|
{
|
|
GLog->TryStartDedicatedPrimaryThread();
|
|
}
|
|
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("GIOThreadPool->Create");
|
|
GIOThreadPool = FQueuedThreadPool::Allocate();
|
|
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfIOWorkerThreadsToSpawn();
|
|
if (FPlatformProperties::IsServerOnly())
|
|
{
|
|
NumThreadsInThreadPool = 2;
|
|
}
|
|
verify(GIOThreadPool->Create(NumThreadsInThreadPool, 96 * 1024, TPri_AboveNormal, TEXT("IOThreadPool")));
|
|
}
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(1);
|
|
|
|
#if WITH_ENGINE
|
|
{
|
|
SCOPED_BOOT_TIMING("System settings and cvar init");
|
|
// Initialize system settings before anyone tries to use it...
|
|
GSystemSettings.Initialize(bHasEditorToken);
|
|
|
|
// Apply renderer settings from console variables stored in the INI.
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererOverrideSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.StreamingSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.GarbageCollectionSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.NetworkSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
#if WITH_EDITOR
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/UnrealEd.CookerSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
#endif
|
|
|
|
#if !UE_SERVER
|
|
if (!bIsRunningAsDedicatedServer)
|
|
{
|
|
if (!bHasCommandletToken)
|
|
{
|
|
// Note: It is critical that resolution settings are loaded before the movie starts playing so that the window size and fullscreen state is known
|
|
UGameUserSettings::PreloadResolutionSettings();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("InitScalabilitySystem");
|
|
// Init scalability system and defaults
|
|
Scalability::InitScalabilitySystem();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("InitializeCVarsForActiveDeviceProfile");
|
|
// Set all CVars which have been setup in the device profiles.
|
|
// This may include scalability group settings which will override
|
|
// the defaults set above which can then be replaced below when
|
|
// the game user settings are loaded and applied.
|
|
UDeviceProfileManager::InitializeCVarsForActiveDeviceProfile();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("Scalability::LoadState");
|
|
// As early as possible to avoid expensive re-init of subsystems,
|
|
// after SystemSettings.ini file loading so we get the right state,
|
|
// before ConsoleVariables.ini so the local developer can always override.
|
|
// after InitializeCVarsForActiveDeviceProfile() so the user can override platform defaults
|
|
Scalability::LoadState((bHasEditorToken && !GEditorSettingsIni.IsEmpty()) ? GEditorSettingsIni : GGameUserSettingsIni);
|
|
}
|
|
|
|
if (FPlatformMisc::UseRenderThread())
|
|
{
|
|
GUseThreadedRendering = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadConsoleVariablesFromINI");
|
|
FConfigCacheIni::LoadConsoleVariablesFromINI();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("Platform Initialization");
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Platform Initialization"), STAT_PlatformInit, STATGROUP_LoadTime);
|
|
|
|
// platform specific initialization now that the SystemSettings are loaded
|
|
FPlatformMisc::PlatformInit();
|
|
#if WITH_APPLICATION_CORE
|
|
FPlatformApplicationMisc::Init();
|
|
#endif
|
|
FPlatformMemory::Init();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("AutoRTFM");
|
|
AutoRTFM::InitializeForUE();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
{
|
|
SCOPED_BOOT_TIMING("InitDerivedData");
|
|
UE::DerivedData::IoStore::InitializeIoDispatcher();
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
{
|
|
int32 ExtraDevelopmentMemoryMB = (int32)(FPlatformMemory::GetExtraDevelopmentMemorySize() / 1024ull / 1024ull);
|
|
CSV_METADATA(TEXT("ExtraDevelopmentMemoryMB"), *FString::FromInt(ExtraDevelopmentMemoryMB));
|
|
}
|
|
#endif
|
|
|
|
#if USE_IO_DISPATCHER
|
|
{
|
|
SCOPED_BOOT_TIMING("InitIoDispatcher");
|
|
FIoDispatcher::InitializePostSettings();
|
|
}
|
|
#endif
|
|
|
|
// Let LogConsole know what ini file it should use to save its setting on exit.
|
|
// We can't use GGameIni inside log console because it's destroyed in the global
|
|
// scoped pointer and at that moment GGameIni may already be gone.
|
|
if (GLogConsole != nullptr)
|
|
{
|
|
GLogConsole->SetIniFilename(*GGameIni);
|
|
}
|
|
|
|
|
|
#if CHECK_PUREVIRTUALS
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Engine", "Error_PureVirtualsEnabled", "The game cannot run with CHECK_PUREVIRTUALS enabled. Please disable CHECK_PUREVIRTUALS and rebuild the executable."));
|
|
FPlatformMisc::RequestExit(false, TEXT("FEngineLoop::PreInitPreStartupScreen.Check_PureVirtuals"));
|
|
#endif
|
|
|
|
FEmbeddedCommunication::ForceTick(2);
|
|
|
|
PreInitContext.SlowTaskPtr = new FScopedSlowTask(100, NSLOCTEXT("EngineLoop", "EngineLoop_Initializing", "Initializing..."));
|
|
FScopedSlowTask& SlowTask = *PreInitContext.SlowTaskPtr;
|
|
|
|
#if WITH_ENGINE
|
|
// allow for game explorer processing (including parental controls) and firewalls installation
|
|
if (!FPlatformMisc::CommandLineCommands())
|
|
{
|
|
FPlatformMisc::RequestExit(false, TEXT("FEngineLoop::PreInitPreStartupScreen.CommandLineCommands"));
|
|
}
|
|
|
|
#if !UE_EDITOR
|
|
// UE_EDITOR doesn't care the meaning of the token except for a few special cases (FooCommandlet, -run=, -game, -server)
|
|
// But when running without UE_EDITOR, we look up the token as a class to see if it is a commandlet name prefix
|
|
FString LateCommandletName;
|
|
if (!IsModeSelected())
|
|
{
|
|
//@hack: We need to set these before calling StaticLoadClass so all required data gets loaded for the commandlets.
|
|
TGuardValue<bool> ScopedGIsClient(GIsClient, true);
|
|
TGuardValue<bool> ScopedGIsServer(GIsServer, true);
|
|
#if WITH_EDITOR
|
|
TGuardValue<bool> ScopedGIsEditor(GIsEditor, true);
|
|
#endif // WITH_EDITOR
|
|
TGuardValue<bool> ScopedGIsRunningCommandlet(PRIVATE_GIsRunningCommandlet, true);
|
|
|
|
// Take the first non-switch command-line parameter (i.e. one not starting with a dash). We will check if it's possibly a commandlet.
|
|
const FString* NonSwitchParam = TokenArray.FindByPredicate(
|
|
[](const FString& Token)
|
|
{
|
|
return Token[0] != TCHAR('-');
|
|
}
|
|
);
|
|
|
|
bool bIsPossiblyCommandletName = NonSwitchParam != nullptr;
|
|
if (bIsPossiblyCommandletName)
|
|
{
|
|
FString CommandletName = *NonSwitchParam;
|
|
|
|
if (!CommandletName.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
CommandletName += TEXT("Commandlet");
|
|
}
|
|
|
|
UClass* TempCommandletClass = FindFirstObject<UClass>(*CommandletName, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("Looking for commandlet class"));
|
|
if (TempCommandletClass)
|
|
{
|
|
checkf(TempCommandletClass->IsChildOf(UCommandlet::StaticClass()), TEXT("It is not valid to have a class that ends with \"Commandlet\" that is not a UCommandlet subclass."));
|
|
|
|
LateCommandletName = CommandletName;
|
|
}
|
|
}
|
|
}
|
|
if (!LateCommandletName.IsEmpty())
|
|
{
|
|
SetIsRunningAsCommandlet(LateCommandletName);
|
|
}
|
|
|
|
// In non-UE_EDITOR configuration this is the point where know enough to finish the mode decision and decide between commandlet or client
|
|
if (!IsModeSelected())
|
|
{
|
|
SetIsRunningAsRegularClient();
|
|
}
|
|
#endif
|
|
|
|
// If std out device hasn't been initialized yet (there was no -stdout param in the command line) and
|
|
// we meet all the criteria, initialize it now.
|
|
if (!GScopedStdOut && !bHasEditorToken && !bIsRegularClient && !bIsRunningAsDedicatedServer)
|
|
{
|
|
SCOPED_BOOT_TIMING("InitializeStdOutDevice");
|
|
|
|
InitializeStdOutDevice();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (IsRunningCookCommandlet())
|
|
{
|
|
ITargetPlatformManagerModule* TargetPlatformManager = GetTargetPlatformManager(false);
|
|
FString InitErrors;
|
|
if (TargetPlatformManager && TargetPlatformManager->HasInitErrors(&InitErrors))
|
|
{
|
|
RequestEngineExit(InitErrors);
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("IPlatformFeaturesModule::Get()");
|
|
// allow the platform to start up any features it may need
|
|
IPlatformFeaturesModule::Get();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("InitGamePhys");
|
|
// Init physics engine before loading anything, in case we want to do things like cook during post-load.
|
|
if (!InitGamePhys())
|
|
{
|
|
// If we failed to initialize physics we cannot continue.
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
{
|
|
bool bShouldCleanShaderWorkingDirectory = true;
|
|
|
|
// Only clean the shader working directory if we are the first instance, to avoid deleting files in use by other instances
|
|
//@todo - check if any other instances are running right now
|
|
bShouldCleanShaderWorkingDirectory = FPlatformProcess::IsFirstInstance();
|
|
|
|
if (bShouldCleanShaderWorkingDirectory && !FParse::Param(FCommandLine::Get(), TEXT("Multiprocess")))
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformProcess::CleanShaderWorkingDirectory");
|
|
|
|
// get shader path, and convert it to the userdirectory
|
|
for (const auto& SHaderSourceDirectoryEntry : AllShaderSourceDirectoryMappings())
|
|
{
|
|
FString ShaderDir = FString(FPlatformProcess::BaseDir()) / SHaderSourceDirectoryEntry.Value;
|
|
FString UserShaderDir = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ShaderDir);
|
|
FPaths::CollapseRelativeDirectories(ShaderDir);
|
|
|
|
// make sure we don't delete from the source directory
|
|
if (ShaderDir != UserShaderDir)
|
|
{
|
|
IFileManager::Get().DeleteDirectory(*UserShaderDir, false, true);
|
|
}
|
|
}
|
|
|
|
FPlatformProcess::CleanShaderWorkingDir();
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
GIsDemoMode = FParse::Param(FCommandLine::Get(), TEXT("DEMOMODE"));
|
|
#endif
|
|
|
|
// InitEngineTextLocalization loads Paks, needs Oodle to be setup before here
|
|
InitEngineTextLocalization();
|
|
|
|
bool bForceEnableHighDPI = false;
|
|
#if WITH_EDITOR
|
|
bForceEnableHighDPI = FPIEPreviewDeviceModule::IsRequestingPreviewDevice();
|
|
#endif
|
|
|
|
// This must be called before any window (including the splash screen is created
|
|
FSlateApplication::InitHighDPI(bForceEnableHighDPI);
|
|
|
|
UStringTable::InitializeEngineBridge();
|
|
|
|
if (FApp::ShouldUseThreadingForPerformance() && FPlatformMisc::AllowAudioThread())
|
|
{
|
|
bool bUseThreadedAudio = false;
|
|
if (!GIsEditor)
|
|
{
|
|
GConfig->GetBool(TEXT("Audio"), TEXT("UseAudioThread"), bUseThreadedAudio, GEngineIni);
|
|
}
|
|
FAudioThread::SetUseThreadedAudio(bUseThreadedAudio);
|
|
}
|
|
|
|
// Ensure engine localization has loaded before we show the splash
|
|
FTextLocalizationManager::Get().WaitForAsyncTasks();
|
|
|
|
// Are we creating a slate application?
|
|
bool bSlateApplication = !IsRunningDedicatedServer() && (bIsRegularClient || bHasEditorToken);
|
|
if (bSlateApplication)
|
|
{
|
|
if (FPlatformProcess::SupportsMultithreading() && !FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")))
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformSplash::Show()");
|
|
FPlatformSplash::Show();
|
|
}
|
|
|
|
// Init platform application
|
|
SCOPED_BOOT_TIMING("FSlateApplication::Create()");
|
|
FSlateApplication::Create();
|
|
}
|
|
else
|
|
{
|
|
// If we're not creating the slate application there is some basic initialization
|
|
// that it does that still must be done
|
|
EKeys::Initialize();
|
|
FSlateApplication::InitializeCoreStyle();
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
// The editor makes use of all cultures in its UI, so pre-load the resource data now to avoid a hitch later
|
|
FInternationalization::Get().LoadAllCultureData();
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(3);
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
FPackageLocalizationManager::Get().InitializeFromLazyCallback([](FPackageLocalizationManager& InPackageLocalizationManager)
|
|
{
|
|
InPackageLocalizationManager.InitializeFromCache(MakeShareable(new FEnginePackageLocalizationCache()));
|
|
});
|
|
#endif // USE_LOCALIZED_PACKAGE_CACHE
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FShaderParametersMetadataRegistration::CommitAll()");
|
|
FShaderParametersMetadataRegistration::CommitAll();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FShaderTypeRegistration::CommitAll()");
|
|
FShaderTypeRegistration::CommitAll();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FUniformBufferStruct::InitializeStructs()");
|
|
FShaderParametersMetadata::InitializeAllUniformBufferStructs();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("PreInitHMDDevice()");
|
|
PreInitHMDDevice();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("RHIInit");
|
|
// Initialize the RHI.
|
|
RHIInit(bHasEditorToken);
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("PipelineStateCacheInit");
|
|
PipelineStateCache::Init();
|
|
}
|
|
|
|
{
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Initializing Render Settings"));
|
|
SCOPED_BOOT_TIMING("RenderUtilsInit");
|
|
// One-time initialization of global variables based on engine configuration.
|
|
RenderUtilsInit();
|
|
}
|
|
|
|
{
|
|
bool bUseCodeLibrary = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds;
|
|
if (bUseCodeLibrary)
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("FShaderCodeLibrary::InitForRuntime");
|
|
// Will open material shader code storage if project was packaged with it
|
|
// This only opens the Global shader library, which is always in the content dir.
|
|
FShaderCodeLibrary::InitForRuntime(GMaxRHIShaderPlatform);
|
|
}
|
|
|
|
#if !UE_EDITOR
|
|
// Cooked data only - but also requires the code library - game only
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
SCOPED_BOOT_TIMING("FShaderPipelineCache::Initialize");
|
|
// Initialize the pipeline cache system. Opening is deferred until the manual call to
|
|
// OpenPipelineFileCache below, after content pak's ShaderCodeLibraries are loaded.
|
|
FShaderPipelineCache::Initialize(GMaxRHIShaderPlatform);
|
|
}
|
|
#endif //!UE_EDITOR
|
|
}
|
|
}
|
|
|
|
#if WITH_ODSC
|
|
check(!GODSCManager);
|
|
GODSCManager = new FODSCManager();
|
|
#endif
|
|
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
{
|
|
// Ensure that DDC is initialized from the game thread.
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Initializing Derived Data Cache"));
|
|
SCOPED_BOOT_TIMING("InitDerivedData");
|
|
UE::DerivedData::GetCache();
|
|
UE::DerivedData::GetBuild();
|
|
GetDerivedDataCacheRef();
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (UE::Virtualization::ShouldInitializePreSlate())
|
|
{
|
|
// Explicit initialization of the virtualization system, before slate has initialized and we cannot show error dialogs
|
|
UE::Virtualization::Initialize(UE::Virtualization::EInitializationFlags::None);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
check(!GDistanceFieldAsyncQueue);
|
|
GDistanceFieldAsyncQueue = new FDistanceFieldAsyncQueue();
|
|
|
|
check(!GCardRepresentationAsyncQueue);
|
|
GCardRepresentationAsyncQueue = new FCardRepresentationAsyncQueue();
|
|
|
|
if (AllowShaderCompiling())
|
|
{
|
|
check(!GShaderCompilerStats);
|
|
GShaderCompilerStats = new FShaderCompilerStats();
|
|
|
|
check(!GShaderCompilingManager);
|
|
GShaderCompilingManager = new FShaderCompilingManager();
|
|
|
|
// Shader hash cache is required only for shader compilation.
|
|
InitializeShaderHashCache();
|
|
}
|
|
else
|
|
{
|
|
// create a manager, but it won't do anything internally
|
|
GShaderCompilingManager = new FShaderCompilingManager();
|
|
}
|
|
}
|
|
|
|
{
|
|
EShaderPermutationFlags Flags = EShaderPermutationFlags::None;
|
|
|
|
bool bSupportCookedEditor = false;
|
|
GConfig->GetBool(TEXT("CookedEditorSettings"), TEXT("bSupportCookedEditor"), bSupportCookedEditor, GGameIni);
|
|
Flags |= bSupportCookedEditor ? EShaderPermutationFlags::HasEditorOnlyData : EShaderPermutationFlags::None;
|
|
|
|
const bool bShouldCompileODSCOnlyShaders = ShouldCompileODSCOnlyShaders();
|
|
Flags |= bShouldCompileODSCOnlyShaders ? EShaderPermutationFlags::IsODSCOnly : EShaderPermutationFlags::None;
|
|
|
|
SetAdditionalShaderPermutationFlags(Flags);
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("GetRendererModule");
|
|
// Cache the renderer module in the main thread so that we can safely retrieve it later from the rendering thread.
|
|
GetRendererModule();
|
|
}
|
|
|
|
{
|
|
if (AllowShaderCompiling())
|
|
{
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Initializing Shader Types"));
|
|
SCOPED_BOOT_TIMING("InitializeShaderTypes");
|
|
|
|
#if WITH_EDITOR
|
|
// Explicitly generate AutogenShaderHeaders.ush prior to shader type initialization
|
|
// (since that process will load and cache this header as a sideeffect)
|
|
FShaderCompileUtilities::GenerateBrdfHeaders(GMaxRHIShaderPlatform);
|
|
#endif
|
|
|
|
// Initialize shader types before loading any shaders
|
|
InitializeShaderTypes();
|
|
}
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::ShaderTypesReady);
|
|
|
|
SlowTask.EnterProgressFrame(25, LOCTEXT("CompileGlobalShaderMap", "Compiling Global Shaders..."));
|
|
|
|
// Load the global shaders
|
|
if (AllowGlobalShaderLoad())
|
|
{
|
|
LLM_SCOPE(ELLMTag::Shaders);
|
|
SCOPED_BOOT_TIMING("CompileGlobalShaderMap");
|
|
CompileGlobalShaderMap(false);
|
|
if (IsEngineExitRequested())
|
|
{
|
|
// This means we can't continue without the global shader map.
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("CreateMoviePlayer");
|
|
CreateMoviePlayer();
|
|
}
|
|
|
|
if (FPreLoadScreenManager::ArePreLoadScreensEnabled())
|
|
{
|
|
SCOPED_BOOT_TIMING("FPreLoadScreenManager::Create");
|
|
FPreLoadScreenManager::Create();
|
|
ensure(FPreLoadScreenManager::Get());
|
|
}
|
|
|
|
// If platforms support early movie playback we have to start the rendering thread much earlier
|
|
#if PLATFORM_SUPPORTS_EARLY_MOVIE_PLAYBACK
|
|
{
|
|
SCOPED_BOOT_TIMING("PostInitRHI");
|
|
PostInitRHI();
|
|
}
|
|
|
|
InitRenderingThread();
|
|
#endif
|
|
|
|
FEmbeddedCommunication::ForceTick(4);
|
|
|
|
{
|
|
#if !UE_SERVER// && !UE_EDITOR
|
|
if (!IsRunningDedicatedServer() && !IsRunningCommandlet())
|
|
{
|
|
TSharedPtr<FSlateRenderer> SlateRenderer = GUsingNullRHI ?
|
|
FModuleManager::Get().LoadModuleChecked<ISlateNullRendererModule>("SlateNullRenderer").CreateSlateNullRenderer() :
|
|
FModuleManager::Get().GetModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer").CreateSlateRHIRenderer();
|
|
TSharedRef<FSlateRenderer> SlateRendererSharedRef = SlateRenderer.ToSharedRef();
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("CurrentSlateApp.InitializeRenderer");
|
|
// If Slate is being used, initialize the renderer after RHIInit
|
|
FSlateApplication& CurrentSlateApp = FSlateApplication::Get();
|
|
CurrentSlateApp.InitializeRenderer(SlateRendererSharedRef);
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FEngineFontServices::Create");
|
|
// Create the engine font services now that the Slate renderer is ready
|
|
FEngineFontServices::Create();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadModulesForProject(ELoadingPhase::PostSplashScreen)");
|
|
// Load up all modules that need to hook into the custom splash screen
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostSplashScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostSplashScreen))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen");
|
|
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen - FPreLoadScreenManager::Get()->Initialize");
|
|
// initialize and present custom splash screen
|
|
FPreLoadScreenManager::Get()->Initialize(SlateRendererSharedRef.Get());
|
|
}
|
|
|
|
if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::CustomSplashScreen))
|
|
{
|
|
FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen(EPreLoadScreenTypes::CustomSplashScreen);
|
|
}
|
|
}
|
|
}
|
|
|
|
PreInitContext.SlateRenderer = SlateRenderer;
|
|
}
|
|
else
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadModulesForProject(ELoadingPhase::PostSplashScreen)");
|
|
// Load up all modules that need to hook into the custom splash screen
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostSplashScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostSplashScreen))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
#endif // !UE_SERVER
|
|
}
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
// Save PreInitContext
|
|
PreInitContext.bDumpEarlyConfigReads = bDumpEarlyConfigReads;
|
|
PreInitContext.bDumpEarlyPakFileReads = bDumpEarlyPakFileReads;
|
|
PreInitContext.bForceQuitAfterEarlyReads = bForceQuitAfterEarlyReads;
|
|
PreInitContext.bWithConfigPatching = bWithConfigPatching;
|
|
PreInitContext.bHasEditorToken = bHasEditorToken;
|
|
#if WITH_ENGINE
|
|
PreInitContext.bIsRegularClient = bIsRegularClient;
|
|
#endif // WITH_ENGINE
|
|
PreInitContext.bIsPossiblyUnrecognizedCommandlet = bIsRegularClient && TokenToForward.Len() && !TokenToForward.Contains(TEXT("-"));
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
|
|
PreInitContext.bTokenDoesNotHaveDash = PreInitContext.bIsPossiblyUnrecognizedCommandlet;
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
|
|
PreInitContext.Token = TokenToForward;
|
|
#if UE_EDITOR || WITH_ENGINE
|
|
PreInitContext.CommandletCommandLine = CommandletCommandLine;
|
|
#endif // UE_EDITOR || WITH_ENGINE
|
|
|
|
#if WITH_COREUOBJECT
|
|
#if (WITH_VERSE_VM || defined(__INTELLISENSE__))
|
|
Verse::VerseVM::Startup();
|
|
#else
|
|
verse::FExecutionContext::Create();
|
|
#endif
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ConditionallyEnsureOnCommandletErrors(int32 InNumErrors)
|
|
{
|
|
bool bEnsureOnError = false;
|
|
GConfig->GetBool(TEXT("Core.System"), TEXT("EnsureCommandletOnError"), bEnsureOnError, GEngineIni);
|
|
if (bEnsureOnError)
|
|
{
|
|
ensureMsgf(InNumErrors == 0, TEXT("Commandlet generated %d errors!"), InNumErrors);
|
|
}
|
|
}
|
|
|
|
int32 FEngineLoop::PreInitPostStartupScreen(const TCHAR* CmdLine)
|
|
{
|
|
ON_SCOPE_EXIT{ FBootProfiling::OnPreInitPostStartupScreenComplete(); };
|
|
SCOPED_BOOT_TIMING("FEngineLoop::PreInitPostStartupScreen");
|
|
LLM_SCOPE(ELLMTag::EnginePreInitMemory);
|
|
|
|
if (IsEngineExitRequested())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
extern bool GIsConsoleExecutable;
|
|
#if PLATFORM_WINDOWS
|
|
/*
|
|
Note - ImageNtHeader must be called after DbgHelp is initialized.Failure to wait before initialization will cause calls to
|
|
SymFromAddr for monolithic exes to fail. This results in callstacks not being written to the log.
|
|
From discussion with Microsoft it is unclear when this time is and could be related to large pdbs. At this time here is an appropriate spot.
|
|
*/
|
|
if (PIMAGE_NT_HEADERS NtHeaders = ImageNtHeader(GetModuleHandle(nullptr)))
|
|
{
|
|
GIsConsoleExecutable = (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI);
|
|
}
|
|
else
|
|
{
|
|
GIsConsoleExecutable = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_CHAR);
|
|
}
|
|
#endif // PLATFORM_WINDOWS
|
|
|
|
FScopeCycleCounter CycleCount_AfterStats(GET_STATID(STAT_FEngineLoop_PreInitPostStartupScreen_AfterStats));
|
|
|
|
// Restore PreInitContext
|
|
bool bDumpEarlyConfigReads = PreInitContext.bDumpEarlyConfigReads;
|
|
bool bDumpEarlyPakFileReads = PreInitContext.bDumpEarlyPakFileReads;
|
|
bool bForceQuitAfterEarlyReads = PreInitContext.bForceQuitAfterEarlyReads;
|
|
bool bWithConfigPatching = PreInitContext.bWithConfigPatching;
|
|
bool bHasEditorToken = PreInitContext.bHasEditorToken;
|
|
#if WITH_ENGINE
|
|
bool bIsRegularClient = PreInitContext.bIsRegularClient;
|
|
#endif // WITH_ENGINE
|
|
bool bIsPossiblyUnrecognizedCommandlet = PreInitContext.bIsPossiblyUnrecognizedCommandlet;
|
|
FString Token = PreInitContext.Token;
|
|
#if UE_EDITOR || WITH_ENGINE
|
|
const TCHAR* CommandletCommandLine = *PreInitContext.CommandletCommandLine;
|
|
#endif // UE_EDITOR || WITH_ENGINE
|
|
|
|
check(PreInitContext.SlowTaskPtr);
|
|
FScopedSlowTask& SlowTask = *PreInitContext.SlowTaskPtr;
|
|
|
|
#if WITH_ENGINE
|
|
{
|
|
TSharedPtr<IInstallBundleManager> BundleManager = IInstallBundleManager::GetPlatformInstallBundleManager();
|
|
|
|
#if !UE_SERVER// && !UE_EDITOR
|
|
if (!IsRunningDedicatedServer() && !IsRunningCommandlet())
|
|
{
|
|
SCOPED_BOOT_TIMING("PreInitPostStartupScreen_StartupGraphics");
|
|
|
|
TSharedPtr<FSlateRenderer> SlateRenderer = PreInitContext.SlateRenderer;
|
|
TSharedRef<FSlateRenderer> SlateRendererSharedRef = SlateRenderer.ToSharedRef();
|
|
|
|
if (IsMoviePlayerEnabled())
|
|
{
|
|
SCOPED_BOOT_TIMING("GetMoviePlayer()->SetupLoadingScreenFromIni");
|
|
// allow the movie player to load a sequence from the .inis (a PreLoadingScreen module could have already initialized a sequence, in which case
|
|
// it wouldn't have anything in it's .ini file)
|
|
GetMoviePlayer()->SetupLoadingScreenFromIni();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen)");
|
|
// Load up all modules that need to hook into the loading screen
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (BundleManager != nullptr && !BundleManager->IsNullInterface())
|
|
{
|
|
IInstallBundleManager::InstallBundleCompleteDelegate.AddStatic(
|
|
&OnStartupContentMounted, bDumpEarlyConfigReads, bDumpEarlyPakFileReads, bWithConfigPatching, bForceQuitAfterEarlyReads);
|
|
}
|
|
// If not using the bundle manager, config will be reloaded after ESP, see below
|
|
|
|
if (IsMoviePlayerEnabled() && GetMoviePlayer()->HasEarlyStartupMovie())
|
|
{
|
|
SCOPED_BOOT_TIMING("EarlyStartupMovie");
|
|
GetMoviePlayer()->Initialize(SlateRendererSharedRef.Get(), FPreLoadScreenManager::Get() ? FPreLoadScreenManager::Get()->GetRenderWindow() : nullptr);
|
|
|
|
// hide splash screen now before playing any movies
|
|
FPlatformMisc::PlatformHandleSplashScreen(false);
|
|
|
|
// only allowed to play any movies marked as early startup. These movies or widgets can have no interaction whatsoever with uobjects or engine features
|
|
GetMoviePlayer()->PlayEarlyStartupMovies();
|
|
|
|
// display the splash screen again now that early startup movies have played
|
|
FPlatformMisc::PlatformHandleSplashScreen(true);
|
|
|
|
#if 0 && PAK_TRACKER// dump the files which have been accessed inside the pak file
|
|
FPakPlatformFile* PakPlatformFile = (FPakPlatformFile*)(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName()));
|
|
FString FileList = TEXT("All files accessed before init\n");
|
|
for (const auto& PakMapFile : PakPlatformFile->GetPakMap())
|
|
{
|
|
FileList += PakMapFile.Key + TEXT("\n");
|
|
}
|
|
UE_LOG(LogInit, Display, TEXT("\n%s"), *FileList);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Play Preload Screen"));
|
|
SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen");
|
|
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen - FPreLoadScreenManager::Get()->Initialize");
|
|
// initialize and play our first Early PreLoad Screen if one is setup
|
|
FPreLoadScreenManager::Get()->Initialize(SlateRendererSharedRef.Get());
|
|
|
|
if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EarlyStartupScreen))
|
|
{
|
|
// disable the splash before playing the early startup screen
|
|
FPreLoadScreenManager::Get()->IsResponsibleForRenderingDelegate.AddLambda(
|
|
[](bool bIsPreloadScreenManResponsibleForRendering)
|
|
{
|
|
FPlatformMisc::PlatformHandleSplashScreen(!bIsPreloadScreenManResponsibleForRendering);
|
|
}
|
|
);
|
|
FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen(EPreLoadScreenTypes::EarlyStartupScreen);
|
|
}
|
|
else
|
|
{
|
|
// no early startup screen, show the splash screen
|
|
FPlatformMisc::PlatformHandleSplashScreen(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no preload manager, show the splash screen
|
|
FPlatformMisc::PlatformHandleSplashScreen(true);
|
|
}
|
|
}
|
|
}
|
|
else if (IsRunningCommandlet())
|
|
{
|
|
// Create the engine font services now that the Slate renderer is ready
|
|
FEngineFontServices::Create();
|
|
}
|
|
#endif //!UE_SERVER
|
|
|
|
//Now that our EarlyStartupScreen is finished, lets take the necessary steps to mount paks, apply .ini cvars, and open the shader libraries if we installed content we expect to handle
|
|
//If using a bundle manager, assume its handling all this stuff and that we don't have to do it.
|
|
if (BundleManager == nullptr || BundleManager->IsNullInterface() || !BundleManager->SupportsEarlyStartupPatching())
|
|
{
|
|
// Mount Paks that were installed during EarlyStartupScreen
|
|
if (FCoreDelegates::OnMountAllPakFiles.IsBound() && FPaths::HasProjectPersistentDownloadDir() )
|
|
{
|
|
SCOPED_BOOT_TIMING("MountPaksAfterEarlyStartupScreen");
|
|
|
|
FString InstalledGameContentDir = FPaths::Combine(*FPaths::ProjectPersistentDownloadDir(), TEXT("InstalledContent"), FApp::GetProjectName(), TEXT("Content"), TEXT("Paks"));
|
|
FPlatformMisc::AddAdditionalRootDirectory(FPaths::Combine(*FPaths::ProjectPersistentDownloadDir(), TEXT("InstalledContent")));
|
|
|
|
TArray<FString> PakFolders;
|
|
PakFolders.Add(InstalledGameContentDir);
|
|
FCoreDelegates::OnMountAllPakFiles.Execute(PakFolders);
|
|
|
|
// Look for any plugins installed during EarlyStartupScreen
|
|
IPluginManager::Get().RefreshPluginsList();
|
|
IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen);
|
|
}
|
|
|
|
DumpEarlyReads(bDumpEarlyConfigReads, bDumpEarlyPakFileReads, bForceQuitAfterEarlyReads);
|
|
|
|
//Reapply CVars after our EarlyLoadScreen
|
|
if(bWithConfigPatching)
|
|
{
|
|
SCOPED_BOOT_TIMING("ReapplyCVarsFromIniAfterEarlyStartupScreen");
|
|
HandleConfigReload(bWithConfigPatching);
|
|
}
|
|
|
|
//Handle opening shader library after our EarlyLoadScreen
|
|
{
|
|
LLM_SCOPE(ELLMTag::Shaders);
|
|
SCOPED_BOOT_TIMING("FShaderCodeLibrary::OpenLibrary");
|
|
|
|
// Open the game library which contains the material shaders.
|
|
FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir());
|
|
for (const FString& RootDir : FPlatformMisc::GetAdditionalRootDirectories())
|
|
{
|
|
FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::Combine(RootDir, FApp::GetProjectName(), TEXT("Content")));
|
|
}
|
|
|
|
// Now our shader code main library is opened, kick off the precompile, if already initialized
|
|
FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform);
|
|
}
|
|
}
|
|
#if WITH_EDITOR
|
|
else if (GAllowCookedDataInEditorBuilds)
|
|
{
|
|
//Handle opening shader library after our EarlyLoadScreen
|
|
{
|
|
LLM_SCOPE(ELLMTag::Shaders);
|
|
SCOPED_BOOT_TIMING("FShaderCodeLibrary::OpenLibrary");
|
|
|
|
// Open the game library which contains the material shaders.
|
|
FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
InitGameTextLocalization();
|
|
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Initial UObject load"), STAT_InitialUObjectLoad, STATGROUP_LoadTime);
|
|
|
|
// In order to be able to use short script package names get all script
|
|
// package names from ini files and register them with FPackageName system.
|
|
FPackageName::RegisterShortPackageNamesForUObjectModules();
|
|
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadAssetRegistryModule");
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Loading AssetRegistry"));
|
|
// If we don't do this now and the async loading thread is active, then we will attempt to load this module from a thread
|
|
FModuleManager::Get().LoadModule("AssetRegistry");
|
|
}
|
|
#if WITH_COREUOBJECT
|
|
// Initialize the PackageResourceManager, which is needed to load any (non-script) Packages. It is first used in ProcessNewlyLoadedObjects (due to the loading of asset references in Class Default Objects)
|
|
// It has to be intialized after the AssetRegistryModule; the editor implementations of PackageResourceManager relies on it
|
|
IPackageResourceManager::Initialize();
|
|
#endif
|
|
#if WITH_EDITOR
|
|
// Initialize the BulkDataRegistry, which registers BulkData structs loaded from Packages for later building. It uses the same lifetime as IPackageResourceManager
|
|
IBulkDataRegistry::Initialize();
|
|
#endif
|
|
|
|
FEmbeddedCommunication::ForceTick(5);
|
|
|
|
// for any auto-registered functions that want to wait until main(), run them now
|
|
// @todo loadtime: this should have phases, so caller can decide when auto-register runs [use plugin phases probably?]
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::PreObjectSystemReady);
|
|
|
|
// Make sure all UObject classes are registered and default properties have been initialized
|
|
{
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Initializing UObject Classes"));
|
|
ProcessNewlyLoadedUObjects();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (!UE::Virtualization::ShouldInitializePreSlate())
|
|
{
|
|
// Explicit initialization of the virtualization system, after slate has initialized and we can show error dialogs.
|
|
UE::Virtualization::Initialize(UE::Virtualization::EInitializationFlags::None);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
// Ensure game localization has loaded before we continue
|
|
FTextLocalizationManager::Get().WaitForAsyncTasks();
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::ObjectSystemReady);
|
|
|
|
FEmbeddedCommunication::ForceTick(6);
|
|
|
|
#if WITH_EDITOR
|
|
if(FPIEPreviewDeviceModule::IsRequestingPreviewDevice())
|
|
{
|
|
auto PIEPreviewDeviceModule = FModuleManager::LoadModulePtr<IPIEPreviewDeviceModule>("PIEPreviewDeviceProfileSelector");
|
|
if (PIEPreviewDeviceModule)
|
|
{
|
|
PIEPreviewDeviceModule->ApplyPreviewDeviceState();
|
|
}
|
|
}
|
|
#endif
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
{
|
|
SCOPED_BOOT_TIMING("FPackageLocalizationManager::Get().PerformLazyInitialization()");
|
|
// CoreUObject is definitely available now, so make sure the package localization cache is available
|
|
// This may have already been initialized from the CDO creation from ProcessNewlyLoadedUObjects
|
|
FPackageLocalizationManager::Get().PerformLazyInitialization();
|
|
}
|
|
#endif // USE_LOCALIZED_PACKAGE_CACHE
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("InitDefaultMaterials etc");
|
|
// Default materials may have been loaded due to dependencies when loading
|
|
// classes and class default objects. If not, do so now.
|
|
UMaterialInterface::InitDefaultMaterials();
|
|
UMaterialInterface::AssertDefaultMaterialsExist();
|
|
UMaterialInterface::AssertDefaultMaterialsPostLoaded();
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("IStreamingManager::Get()");
|
|
// Initialize the texture streaming system (needs to happen after RHIInit and ProcessNewlyLoadedUObjects).
|
|
IStreamingManager::Get();
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
// Tell the module manager is may now process newly-loaded UObjects when new C++ modules are loaded
|
|
FModuleManager::Get().StartProcessingNewlyLoadedObjects();
|
|
|
|
FEmbeddedCommunication::ForceTick(7);
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadStartupCoreModules");
|
|
if (!LoadStartupCoreModules())
|
|
{
|
|
// At least one startup module failed to load, return 1 to indicate an error
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen)");
|
|
// Load up all modules that need to hook into the loading screen
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreLoadingScreen))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#if !UE_SERVER
|
|
//See if we have an engine loading PreLoadScreen registered, if not try to play an engine loading movie as a backup.
|
|
if (IsMoviePlayerEnabled() && !GetMoviePlayer()->IsMovieCurrentlyPlaying())
|
|
{
|
|
SCOPED_BOOT_TIMING("FPreLoadScreenManager::Get()->Initialize etc");
|
|
if (FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer())
|
|
{
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
FPreLoadScreenManager::Get()->Initialize(*Renderer);
|
|
}
|
|
else
|
|
{
|
|
//If we don't have a PreLoadScreen to show, try and initialize old flow with the movie player.
|
|
GetMoviePlayer()->Initialize(*Renderer, FPreLoadScreenManager::Get()->GetRenderWindow());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetMoviePlayer()->Initialize(*Renderer, nullptr);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformApplicationMisc::PostInit");
|
|
// do any post appInit processing, before the render thread is started.
|
|
FPlatformApplicationMisc::PostInit();
|
|
}
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
#if !PLATFORM_SUPPORTS_EARLY_MOVIE_PLAYBACK
|
|
{
|
|
SCOPED_BOOT_TIMING("PostInitRHI etc");
|
|
PostInitRHI();
|
|
}
|
|
|
|
InitRenderingThread();
|
|
#endif // !PLATFORM_SUPPORTS_EARLY_MOVIE_PLAYBACK
|
|
|
|
// Playing a movie can only happen after the rendering thread is started.
|
|
#if !UE_SERVER// && !UE_EDITOR
|
|
if (IsMoviePlayerEnabled() && !GetMoviePlayer()->IsMovieCurrentlyPlaying())
|
|
{
|
|
SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen etc");
|
|
if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen(EPreLoadScreenTypes::EngineLoadingScreen);
|
|
FPreLoadScreenManager::Get()->SetEngineLoadingComplete(false);
|
|
}
|
|
else
|
|
{
|
|
// Play any non-early startup loading movies.
|
|
GetMoviePlayer()->PlayMovie();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("PlatformHandleSplashScreen etc");
|
|
#if !UE_SERVER
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
// show or hide splash screen based on movie
|
|
FPlatformMisc::PlatformHandleSplashScreen(!(IsMoviePlayerEnabled() && GetMoviePlayer()->IsMovieCurrentlyPlaying()));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// show splash screen
|
|
FPlatformMisc::PlatformHandleSplashScreen(true);
|
|
}
|
|
}
|
|
|
|
{
|
|
FCoreUObjectDelegates::PreGarbageCollectConditionalBeginDestroy.AddStatic(StartRenderCommandFenceBundler);
|
|
FCoreUObjectDelegates::PostGarbageCollectConditionalBeginDestroy.AddStatic(StopRenderCommandFenceBundler);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// We need to mount the shared resources for templates (if there are any) before we try and load and game classes
|
|
FUnrealEdMisc::Get().MountTemplateSharedPaths();
|
|
|
|
// We need to load any animation compression settings before we load game classes
|
|
FAnimationUtils::PreloadCompressionSettings();
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("LoadStartupModules");
|
|
if (!LoadStartupModules())
|
|
{
|
|
// At least one startup module failed to load, return 1 to indicate an error
|
|
return 1;
|
|
}
|
|
}
|
|
#else // !WITH_ENGINE
|
|
#if WITH_COREUOBJECT
|
|
// Initialize the PackageResourceManager, which is needed to load any (non-script) Packages.
|
|
IPackageResourceManager::Initialize();
|
|
#endif
|
|
#endif // WITH_ENGINE
|
|
|
|
#if WITH_COREUOBJECT
|
|
if (GUObjectArray.IsOpenForDisregardForGC())
|
|
{
|
|
SCOPED_BOOT_TIMING("CloseDisregardForGC");
|
|
GUObjectArray.CloseDisregardForGC();
|
|
}
|
|
NotifyRegistrationComplete();
|
|
FReferencerFinder::NotifyRegistrationComplete();
|
|
#endif // WITH_COREUOBJECT
|
|
|
|
#if WITH_ENGINE
|
|
if (UOnlineEngineInterface::Get()->IsLoaded())
|
|
{
|
|
SetIsServerForOnlineSubsystemsDelegate(FQueryIsRunningServer::CreateStatic(&IsServerDelegateForOSS));
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
if (!bHasEditorToken && !IsRunningDedicatedServer())
|
|
{
|
|
UClass* CommandletClass = nullptr;
|
|
|
|
if (!bIsRegularClient)
|
|
{
|
|
checkf(PRIVATE_GIsRunningCommandlet, TEXT("This should have been set in PreInitPreStartupScreen"));
|
|
|
|
CommandletClass = Cast<UClass>(StaticFindFirstObject(UClass::StaticClass(), *Token, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("looking for commandlet")));
|
|
int32 PeriodIdx;
|
|
if (!CommandletClass && Token.FindChar('.', PeriodIdx))
|
|
{
|
|
// try to load module for commandlet specified before a period.
|
|
FModuleManager::Get().LoadModule(*Token.Left(PeriodIdx));
|
|
CommandletClass = FindFirstObject<UClass>(*Token, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("Looking for commandlet class"));
|
|
}
|
|
if (!CommandletClass)
|
|
{
|
|
if (GLogConsole && !GIsSilent)
|
|
{
|
|
GLogConsole->Show(true);
|
|
}
|
|
UE_LOG(LogInit, Error, TEXT("%s looked like a commandlet, but we could not find the class."), *Token);
|
|
RequestEngineExit(FString::Printf(TEXT("Failed to find commandlet class %s"), *Token));
|
|
return 1;
|
|
}
|
|
|
|
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_UNIX
|
|
if (GIsConsoleExecutable)
|
|
{
|
|
if (GLogConsole != nullptr && GLogConsole->IsAttached())
|
|
{
|
|
GLog->RemoveOutputDevice(GLogConsole);
|
|
}
|
|
// Setup Ctrl-C handler for console application
|
|
FPlatformMisc::SetGracefulTerminationHandler();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Bring up console unless we're a silent build.
|
|
if( GLogConsole && !GIsSilent )
|
|
{
|
|
GLogConsole->Show( true );
|
|
}
|
|
}
|
|
|
|
// print output immediately
|
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Executing %s"), *CommandletClass->GetFullName() );
|
|
|
|
// Allow commandlets to individually override those settings.
|
|
UCommandlet* Default = CastChecked<UCommandlet>(CommandletClass->GetDefaultObject());
|
|
|
|
if ( IsEngineExitRequested() )
|
|
{
|
|
// commandlet set IsEngineExitRequested() during construction
|
|
return 1;
|
|
}
|
|
|
|
GIsClient = Default->IsClient;
|
|
GIsServer = Default->IsServer;
|
|
#if WITH_EDITOR
|
|
GIsEditor = Default->IsEditor;
|
|
#else
|
|
if (Default->IsEditor)
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Cannot run editor commandlet %s with game executable."), *CommandletClass->GetFullName());
|
|
RequestEngineExit(TEXT("Tried to run commandlet in non-editor build"));
|
|
return 1;
|
|
}
|
|
#endif
|
|
// Reset aux log if we don't want to log to the console window.
|
|
if( !Default->LogToConsole )
|
|
{
|
|
GLog->RemoveOutputDevice( GLogConsole );
|
|
}
|
|
|
|
// allow the commandlet the opportunity to create a custom engine
|
|
CommandletClass->GetDefaultObject<UCommandlet>()->CreateCustomEngine(CommandletCommandLine);
|
|
if ( GEngine == nullptr )
|
|
{
|
|
#if WITH_EDITOR
|
|
if ( GIsEditor )
|
|
{
|
|
FString EditorEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("EditorEngine"), EditorEngineClassName, GEngineIni);
|
|
UClass* EditorEngineClass = StaticLoadClass( UEditorEngine::StaticClass(), nullptr, *EditorEngineClassName);
|
|
if (EditorEngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load Editor Engine class '%s'."), *EditorEngineClassName);
|
|
}
|
|
|
|
GEngine = GEditor = NewObject<UEditorEngine>(GetTransientPackage(), EditorEngineClass);
|
|
|
|
GEngine->ParseCommandline();
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Editor Engine..."));
|
|
GEditor->InitEditor(this);
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Editor Engine Completed"));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
FString GameEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
|
|
|
|
UClass* EngineClass = StaticLoadClass( UEngine::StaticClass(), nullptr, *GameEngineClassName);
|
|
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load Engine class '%s'."), *GameEngineClassName);
|
|
}
|
|
|
|
// must do this here so that the engine object that we create on the next line receives the correct property values
|
|
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
|
|
check(GEngine);
|
|
|
|
GEngine->ParseCommandline();
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Game Engine..."));
|
|
GEngine->Init(this);
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Game Engine Completed"));
|
|
}
|
|
}
|
|
|
|
// Call init callbacks
|
|
FCoreDelegates::OnPostEngineInit.Broadcast();
|
|
|
|
// Load all the post-engine init modules
|
|
ensure(IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit));
|
|
ensure(IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit));
|
|
|
|
// Call module loading phases completion callbacks
|
|
SetEngineStartupModuleLoadingComplete();
|
|
|
|
// Disable the backlog only after module loading is complete because that gives modules
|
|
// a chance to consume the backlog when constructing output devices.
|
|
GLog->EnableBacklog(false);
|
|
|
|
//run automation smoke tests now that the commandlet has had a chance to override the above flags and GEngine is available
|
|
FAutomationTestFramework::Get().RunSmokeTests();
|
|
|
|
UCommandlet* Commandlet = NewObject<UCommandlet>(GetTransientPackage(), CommandletClass);
|
|
check(Commandlet);
|
|
Commandlet->AddToRoot();
|
|
|
|
// Execute the commandlet.
|
|
double CommandletExecutionStartTime = FPlatformTime::Seconds();
|
|
|
|
// Commandlets don't always handle -run= properly in the commandline so we'll provide them
|
|
// with a custom version that doesn't have it.
|
|
Commandlet->ParseParms( CommandletCommandLine );
|
|
#if STATS
|
|
// We have to close the scope, otherwise we will end with broken stats.
|
|
CycleCount_AfterStats.StopAndResetStatId();
|
|
#endif // STATS
|
|
UE::Stats::FStats::TickCommandletStats();
|
|
|
|
#if WITH_ENGINE
|
|
PRIVATE_GRunningCommandletClass = CommandletClass;
|
|
#endif
|
|
FCoreDelegates::OnCommandletPreMain.Broadcast();
|
|
int32 ErrorLevel;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*WriteToString<512>(TEXT("Commandlet Main "), Commandlet->GetFName()));
|
|
FTrackedActivityScope CommandletActivity(FTrackedActivity::GetEngineActivity(), *FString::Printf(TEXT("Running %s"), *Commandlet->GetName()), false, FTrackedActivity::ELight::Green);
|
|
ErrorLevel = Commandlet->Main(CommandletCommandLine);
|
|
}
|
|
FCoreDelegates::OnCommandletPostMain.Broadcast();
|
|
#if WITH_ENGINE
|
|
PRIVATE_GRunningCommandletClass = nullptr;
|
|
#endif
|
|
|
|
UE::Stats::FStats::TickCommandletStats();
|
|
|
|
RequestEngineExit(FString::Printf(TEXT("Commandlet %s finished execution (result %d)"), *Commandlet->GetName(), ErrorLevel));
|
|
|
|
// Log warning/ error summary.
|
|
if( Commandlet->ShowErrorCount )
|
|
{
|
|
TArray<FString> AllErrors;
|
|
TArray<FString> AllWarnings;
|
|
GWarn->GetErrors(AllErrors);
|
|
GWarn->GetWarnings(AllWarnings);
|
|
|
|
if (AllErrors.Num() || AllWarnings.Num())
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_LOG(LogInit, Display, TEXT(""));
|
|
UE_LOG(LogInit, Display, TEXT("Warning/Error Summary (Unique only)"));
|
|
UE_LOG(LogInit, Display, TEXT("-----------------------------------"));
|
|
|
|
const int32 MaxMessagesToShow = (GIsBuildMachine || FParse::Param(FCommandLine::Get(), TEXT("DUMPALLWARNINGS"))) ?
|
|
(AllErrors.Num() + AllWarnings.Num()) : 50;
|
|
|
|
TSet<FString> ShownMessages;
|
|
ShownMessages.Empty(MaxMessagesToShow);
|
|
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
|
|
for (const FString& ErrorMessage : AllErrors)
|
|
{
|
|
bool bAlreadyShown = false;
|
|
ShownMessages.Add(ErrorMessage, &bAlreadyShown);
|
|
|
|
if (!bAlreadyShown)
|
|
{
|
|
if (ShownMessages.Num() > MaxMessagesToShow)
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_CLOG(MaxMessagesToShow < AllErrors.Num(), LogInit, Display, TEXT("NOTE: Only first %d errors displayed."), MaxMessagesToShow);
|
|
break;
|
|
}
|
|
|
|
UE_LOG(LogInit, Display, TEXT("%s"), *ErrorMessage);
|
|
}
|
|
}
|
|
|
|
SET_WARN_COLOR(COLOR_YELLOW);
|
|
ShownMessages.Empty(MaxMessagesToShow);
|
|
|
|
for (const FString& WarningMessage : AllWarnings)
|
|
{
|
|
bool bAlreadyShown = false;
|
|
ShownMessages.Add(WarningMessage, &bAlreadyShown);
|
|
|
|
if (!bAlreadyShown)
|
|
{
|
|
if (ShownMessages.Num() > MaxMessagesToShow)
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_CLOG(MaxMessagesToShow < AllWarnings.Num(), LogInit, Display, TEXT("NOTE: Only first %d warnings displayed."), MaxMessagesToShow);
|
|
break;
|
|
}
|
|
|
|
UE_LOG(LogInit, Display, TEXT("%s"), *WarningMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allow the caller to request that we issue an ensure when there are any errors from the executed commandlet
|
|
ConditionallyEnsureOnCommandletErrors(AllErrors.Num());
|
|
|
|
UE_LOG(LogInit, Display, TEXT(""));
|
|
|
|
if( ErrorLevel != 0 )
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogInit, Display, TEXT("Commandlet->Main return this error code: %d"), ErrorLevel );
|
|
UE_LOG(LogInit, Display, TEXT("With %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
}
|
|
else if( ( AllErrors.Num() == 0 ) )
|
|
{
|
|
SET_WARN_COLOR(AllWarnings.Num() ? COLOR_YELLOW : COLOR_GREEN);
|
|
UE_LOG(LogInit, Display, TEXT("Success - %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
}
|
|
else
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogInit, Display, TEXT("Failure - %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
}
|
|
CLEAR_WARN_COLOR();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("Finished."));
|
|
}
|
|
|
|
// Return an non-zero code if errors were logged and UseCommandletResultAsExitCode is false
|
|
if (!Commandlet->UseCommandletResultAsExitCode)
|
|
{
|
|
if ((ErrorLevel == 0) && (GWarn->GetNumErrors() > 0))
|
|
{
|
|
ErrorLevel = 1;
|
|
}
|
|
}
|
|
|
|
double CommandletExecutionTime = FPlatformTime::Seconds() - CommandletExecutionStartTime;
|
|
if (CommandletExecutionTime <= 60)
|
|
{
|
|
UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT("Execution of commandlet took: %.2f seconds"), CommandletExecutionTime);
|
|
}
|
|
else
|
|
{
|
|
FTimespan ExecutionTimeSpan = FTimespan::FromSeconds(CommandletExecutionTime);
|
|
int32 Hours = (int32)(ExecutionTimeSpan.GetTotalHours());
|
|
int32 Minutes = ExecutionTimeSpan.GetMinutes();
|
|
int32 Seconds = ExecutionTimeSpan.GetSeconds();
|
|
// Tried FText::AsTimespan here but it actually felt harder to visually parse than just explicit labeling. Leaving the
|
|
// seconds in so that it's easy to subtract between multiple runs.
|
|
if (Hours)
|
|
{
|
|
UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT("Execution of commandlet took: %dh %dm %ds (%.2f seconds)"), Hours, Minutes, Seconds, CommandletExecutionTime);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT("Execution of commandlet took: %dm %ds (%.2f seconds)"), Minutes, Seconds, CommandletExecutionTime);
|
|
}
|
|
}
|
|
|
|
const bool bShouldPerformFastExit = Commandlet->FastExit || FParse::Param(FCommandLine::Get(), TEXT("fastexit"));
|
|
if (bShouldPerformFastExit)
|
|
{
|
|
FPlatformMisc::RequestExitWithStatus(true, ErrorLevel);
|
|
}
|
|
|
|
// We're ready to exit!
|
|
return ErrorLevel;
|
|
}
|
|
else
|
|
{
|
|
// We're a regular client.
|
|
check(bIsRegularClient);
|
|
|
|
if (bIsPossiblyUnrecognizedCommandlet)
|
|
{
|
|
// here we give people a reasonable warning if they tried to use the short name of a commandlet
|
|
UClass* TempCommandletClass = FindFirstObject<UClass>(*(Token + TEXT("Commandlet")), EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("Looking for commandlet class"));
|
|
if (TempCommandletClass)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("You probably meant to call a commandlet. Please use the full name %s."), *(Token+TEXT("Commandlet")));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// exit if wanted.
|
|
if( IsEngineExitRequested() )
|
|
{
|
|
if ( GEngine != nullptr )
|
|
{
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
FEditorDelegates::OnEditorPreExit.Broadcast();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
GEngine->PreExit();
|
|
}
|
|
AppPreExit();
|
|
// appExit is called outside guarded block.
|
|
return 1;
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(8);
|
|
|
|
if(FParse::Param(FCommandLine::Get(),TEXT("DUMPMOVIE")))
|
|
{
|
|
// -1: remain on
|
|
GIsDumpingMovie = -1;
|
|
}
|
|
|
|
// If dumping movie then we do NOT want on-screen messages
|
|
GAreScreenMessagesEnabled = !GIsDumpingMovie && !GIsDemoMode;
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FParse::Param(FCommandLine::Get(),TEXT("NOSCREENMESSAGES")))
|
|
{
|
|
GAreScreenMessagesEnabled = false;
|
|
}
|
|
|
|
if (GEngine && FParse::Param(FCommandLine::Get(), TEXT("statunit")))
|
|
{
|
|
GEngine->Exec(nullptr, TEXT("stat unit"));
|
|
}
|
|
|
|
// Don't update INI files if benchmarking or -noini
|
|
if( FApp::IsBenchmarking() || FParse::Param(FCommandLine::Get(),TEXT("NOINI")))
|
|
{
|
|
GConfig->Detach( GEngineIni );
|
|
GConfig->Detach( GInputIni );
|
|
GConfig->Detach( GGameIni );
|
|
GConfig->Detach( GEditorIni );
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// initialize the pointer, as it is deleted before being assigned in the first frame
|
|
PendingCleanupObjects = nullptr;
|
|
|
|
// Initialize profile visualizers.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
FModuleManager::Get().LoadModule(TEXT("ProfileVisualizer"));
|
|
#if UE_DEPRECATED_PROFILER_ENABLED
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("ProfilerService"));
|
|
FModuleManager::Get().GetModuleChecked<IProfilerServiceModule>("ProfilerService").CreateProfilerServiceManager();
|
|
}
|
|
#endif // UE_DEPRECATED_PROFILER_ENABLED
|
|
#endif
|
|
|
|
// Init HighRes screenshot system, unless running on server
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
GetHighResScreenshotConfig().Init();
|
|
}
|
|
|
|
// precache compute PSOs for global shaders - enough time should have passed since loading global SM to avoid a blocking load
|
|
if (AllowGlobalShaderLoad())
|
|
{
|
|
PrecacheComputePipelineStatesForGlobalShaders(GMaxRHIFeatureLevel, nullptr);
|
|
}
|
|
|
|
UE::RenderCommandPipe::Initialize();
|
|
|
|
#else // WITH_ENGINE
|
|
InitEngineTextLocalization();
|
|
InitGameTextLocalization();
|
|
FTextLocalizationManager::Get().WaitForAsyncTasks();
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
{
|
|
SCOPED_BOOT_TIMING("FPackageLocalizationManager::Get().InitializeFromDefaultCache");
|
|
FPackageLocalizationManager::Get().InitializeFromDefaultCache();
|
|
}
|
|
#endif // USE_LOCALIZED_PACKAGE_CACHE
|
|
#if WITH_APPLICATION_CORE
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformApplicationMisc::PostInit");
|
|
FPlatformApplicationMisc::PostInit();
|
|
}
|
|
#endif
|
|
#endif // WITH_ENGINE
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("RunSmokeTests");
|
|
//run automation smoke tests now that everything is setup to run
|
|
FAutomationTestFramework::Get().RunSmokeTests();
|
|
}
|
|
|
|
#if WITH_ENGINE && (!UE_BUILD_SHIPPING)
|
|
IRHITestModule* RHIUnitTests = static_cast<IRHITestModule*>(FModuleManager::Get().GetModule(TEXT("RHITests")));
|
|
if (RHIUnitTests)
|
|
{
|
|
RHIUnitTests->RunAllTests();
|
|
}
|
|
#endif //(!UE_BUILD_SHIPPING)
|
|
|
|
FEmbeddedCommunication::ForceTick(9);
|
|
|
|
PreInitContext.Cleanup();
|
|
|
|
// Note we still have 20% remaining on the slow task: this will be used by the Editor/Engine initialization next
|
|
return 0;
|
|
}
|
|
|
|
int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
|
|
{
|
|
#if UE_ENABLE_ARRAY_SLACK_TRACKING
|
|
// Any array allocations before this point won't have array slack tracking, although subsequent reallocations of existing arrays
|
|
// will gain tracking if that occurs. The goal is to filter out startup constructors which run before Main, which introduce a
|
|
// ton of noise into slack reports. Especially the roughly 30,000 static FString constructors in the code base, each with a
|
|
// unique call stack, and all having a little bit of slack due to malloc bucket size rounding.
|
|
ArraySlackTrackInit();
|
|
#endif
|
|
|
|
FBootProfiling::InitCounters();
|
|
|
|
const int32 rv1 = PreInitPreStartupScreen(CmdLine);
|
|
if (rv1 != 0)
|
|
{
|
|
PreInitContext.Cleanup();
|
|
return rv1;
|
|
}
|
|
|
|
const int32 rv2 = PreInitPostStartupScreen(CmdLine);
|
|
if (rv2 != 0)
|
|
{
|
|
PreInitContext.Cleanup();
|
|
return rv2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool FEngineLoop::LoadCoreModules()
|
|
{
|
|
// Always attempt to load CoreUObject. It requires additional pre-init which is called from its module's StartupModule method.
|
|
#if WITH_COREUOBJECT
|
|
// Always register the UObjects callbacks from the module manager for dynamic loading/unloading.
|
|
RegisterModularObjectsProcessing();
|
|
return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
void FEngineLoop::CleanupPreInitContext()
|
|
{
|
|
PreInitContext.Cleanup();
|
|
}
|
|
|
|
void FEngineLoop::LoadPreInitModules()
|
|
{
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading PreInit Modules"), STAT_PreInitModules, STATGROUP_LoadTime);
|
|
|
|
// GGetMapNameDelegate is initialized here
|
|
#if WITH_ENGINE
|
|
FModuleManager::Get().LoadModule(TEXT("Engine"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("Renderer"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("AnimGraphRuntime"));
|
|
|
|
FPlatformApplicationMisc::LoadPreInitModules();
|
|
|
|
#if !UE_SERVER
|
|
if (!IsRunningDedicatedServer() )
|
|
{
|
|
if (!GUsingNullRHI)
|
|
{
|
|
// This needs to be loaded before InitializeShaderTypes is called
|
|
FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("Landscape"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("RHICore"));
|
|
|
|
// Initialize ShaderCore before loading or compiling any shaders,
|
|
// But after Renderer and any other modules which implement shader types.
|
|
FModuleManager::Get().LoadModule(TEXT("RenderCore"));
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// Load the texture compressor module before any textures load. They may
|
|
// compress asynchronously and that can lead to a race condition.
|
|
FModuleManager::Get().LoadModule(TEXT("TextureCompressor"));
|
|
#endif
|
|
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("Virtualization"));
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
#if WITH_EDITOR
|
|
// Load audio editor module before engine class CDOs are loaded
|
|
FModuleManager::Get().LoadModule(TEXT("AudioEditor"));
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // todo: revist
|
|
FModuleManager::Get().LoadModule(TEXT("AnimationModifiers"));
|
|
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
|
|
|
|
#if WITH_ENGINE
|
|
|
|
bool FEngineLoop::LoadStartupCoreModules()
|
|
{
|
|
FScopedSlowTask SlowTask(100);
|
|
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Startup Modules"), STAT_StartupModules, STATGROUP_LoadTime);
|
|
|
|
bool bSuccess = true;
|
|
|
|
// Load all Runtime modules
|
|
SlowTask.EnterProgressFrame(10);
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("Core"));
|
|
FModuleManager::Get().LoadModule(TEXT("Networking"));
|
|
}
|
|
|
|
// Enable the live coding module if it's a developer build
|
|
#if WITH_LIVE_CODING
|
|
FModuleManager::Get().LoadModule("LiveCoding");
|
|
#endif
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
FPlatformApplicationMisc::LoadStartupModules();
|
|
|
|
// initialize messaging
|
|
SlowTask.EnterProgressFrame(10);
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
|
|
}
|
|
|
|
// Init Scene Reconstruction support
|
|
#if !UE_SERVER
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh");
|
|
}
|
|
#endif
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
#if WITH_EDITOR
|
|
FModuleManager::Get().LoadModuleChecked("UnrealEd");
|
|
FModuleManager::Get().LoadModuleChecked("LandscapeEditorUtilities");
|
|
FModuleManager::Get().LoadModuleChecked("SubobjectDataInterface");
|
|
#endif //WITH_EDITOR
|
|
|
|
// Load UI modules
|
|
SlowTask.EnterProgressFrame(10);
|
|
if ( !IsRunningDedicatedServer() )
|
|
{
|
|
FModuleManager::Get().LoadModule("SlateCore");
|
|
FModuleManager::Get().LoadModule("Slate");
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Need to load up the SlateReflector module to initialize the WidgetSnapshotService
|
|
FModuleManager::Get().LoadModule("SlateReflector");
|
|
#endif // !UE_BUILD_SHIPPING
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FModuleManager::Get().LoadModule("EditorStyle");
|
|
// In dedicated server builds with the editor, we need to load UMG/UMGEditor for compiling blueprints.
|
|
// UMG must be loaded for runtime and cooking.
|
|
FModuleManager::Get().LoadModule("UMG");
|
|
// ScriptableEditorWidgets was refactored out of UMG, load it now so that we don't break existing
|
|
// projets that are not listing it in their uproject or uplugin:
|
|
FModuleManager::Get().LoadModule("ScriptableEditorWidgets");
|
|
#else
|
|
if ( !IsRunningDedicatedServer() )
|
|
{
|
|
// UMG must be loaded for runtime and cooking.
|
|
FModuleManager::Get().LoadModule("UMG");
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
// Load all Development modules
|
|
SlowTask.EnterProgressFrame(20);
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
FModuleManager::Get().LoadModule("MessageLog");
|
|
#endif // WITH_UNREAL_DEVELOPER_TOOLS
|
|
#if WITH_EDITOR
|
|
FModuleManager::Get().LoadModule("CollisionAnalyzer");
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
FModuleManager::Get().LoadModule("FunctionalTesting");
|
|
#endif //WITH_UNREAL_DEVELOPER_TOOLS
|
|
|
|
SlowTask.EnterProgressFrame(30);
|
|
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST)) // todo: revisit
|
|
// HACK: load BT editor as early as possible for statically initialized assets (non cooked BT assets needs it)
|
|
// cooking needs this module too
|
|
FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));
|
|
|
|
// Ability tasks are based on GameplayTasks, so we need to make sure that module is loaded as well
|
|
FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
|
|
AudioEditorModule->RegisterAssetActions();
|
|
#endif // WITH_EDITOR
|
|
|
|
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST)) // todo: revisit
|
|
// Load the StringTableEditor module to register its asset actions
|
|
FModuleManager::Get().LoadModule("StringTableEditor");
|
|
|
|
if( !IsRunningDedicatedServer() )
|
|
{
|
|
// VREditor needs to be loaded in non-server editor builds early, so engine content Blueprints can be loaded during DDC generation
|
|
FModuleManager::Get().LoadModule(TEXT("VREditor"));
|
|
}
|
|
|
|
if (IsRunningCommandlet())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("Blutility"));
|
|
}
|
|
|
|
#endif //(WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
|
|
|
|
#if WITH_EDITOR
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("MaterialEditor"));
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
#if WITH_ENGINE
|
|
// Load runtime client modules (which are also needed at cook-time)
|
|
if( !IsRunningDedicatedServer() )
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("Overlay"));
|
|
}
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("MediaAssets"));
|
|
#endif
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("ClothingSystemRuntimeNv"));
|
|
#if WITH_EDITOR
|
|
FModuleManager::Get().LoadModule(TEXT("ClothingSystemEditor"));
|
|
FModuleManager::Get().LoadModule(TEXT("AnimationDataController"));
|
|
|
|
// Required during serialization of AnimSequence which could happen from async loading thread.
|
|
// See UAnimSequence::UpdateFrameRate().
|
|
FModuleManager::Get().LoadModule(TEXT("TimeManagement"));
|
|
|
|
// Required during construction of UAnimBlueprint which could happen from async loading thread.
|
|
// See UAnimBlueprint::UAnimBlueprint().
|
|
FModuleManager::Get().LoadModule(TEXT("AnimGraph"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("WorldBookmark"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("WorldPartitionEditor"));
|
|
#endif
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("PacketHandler"));
|
|
FModuleManager::Get().LoadModule(TEXT("NetworkReplayStreaming"));
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("MassEntity"));
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
bool FEngineLoop::LoadStartupModules()
|
|
{
|
|
FScopedSlowTask SlowTask(3);
|
|
LLM_SCOPE_BYNAME(TEXT("Modules"));
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load any modules that want to be loaded before default modules are loaded up.
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load modules that are configured to load in the default phase
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::Default) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load any modules that want to be loaded after default modules are loaded up.
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FEngineLoop::InitTime()
|
|
{
|
|
// Init variables used for benchmarking and ticking.
|
|
FApp::SetCurrentTime(FPlatformTime::Seconds());
|
|
MaxFrameCounter = 0;
|
|
MaxTickTime = 0;
|
|
TotalTickTime = 0;
|
|
LastFrameCycles = FPlatformTime::Cycles();
|
|
|
|
float FloatMaxTickTime = 0;
|
|
#if (!UE_BUILD_SHIPPING || ENABLE_PGO_PROFILE)
|
|
FParse::Value(FCommandLine::Get(),TEXT("SECONDS="),FloatMaxTickTime);
|
|
MaxTickTime = FloatMaxTickTime;
|
|
|
|
// look of a version of seconds that only is applied if FApp::IsBenchmarking() is set. This makes it easier on
|
|
// say, iOS, where we have a toggle setting to enable benchmarking, but don't want to have to make user
|
|
// also disable the seconds setting as well. -seconds= will exit the app after time even if benchmarking
|
|
// is not enabled
|
|
// NOTE: This will override -seconds= if it's specified
|
|
if (FApp::IsBenchmarking())
|
|
{
|
|
if (FParse::Value(FCommandLine::Get(),TEXT("BENCHMARKSECONDS="),FloatMaxTickTime) && FloatMaxTickTime)
|
|
{
|
|
MaxTickTime = FloatMaxTickTime;
|
|
}
|
|
}
|
|
|
|
// Use -FPS=X to override fixed tick rate if e.g. -BENCHMARK is used.
|
|
float FixedFPS = 0;
|
|
FParse::Value(FCommandLine::Get(),TEXT("FPS="),FixedFPS);
|
|
if( FixedFPS > 0 )
|
|
{
|
|
FApp::SetFixedDeltaTime(1 / FixedFPS);
|
|
}
|
|
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// convert FloatMaxTickTime into number of frames (using 1 / FApp::GetFixedDeltaTime() to convert fps to seconds )
|
|
MaxFrameCounter = FMath::TruncToInt(MaxTickTime / FApp::GetFixedDeltaTime());
|
|
}
|
|
|
|
|
|
//called via FCoreDelegates::StarvedGameLoop
|
|
void GameLoopIsStarved()
|
|
{
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
|
|
int32 FEngineLoop::Init()
|
|
{
|
|
ON_SCOPE_EXIT
|
|
{
|
|
#if TRACK_DISK_UTILIZATION
|
|
CSV_METADATA(TEXT("BootMBLoaded"), *FString::FromInt(GDiskUtilizationTracker.GetLongTermStats().TotalBytesRead / (1024 * 1024)));
|
|
#endif
|
|
FBootProfiling::OnInitComplete();
|
|
};
|
|
LLM_SCOPE(ELLMTag::EngineInitMemory);
|
|
SCOPED_BOOT_TIMING("FEngineLoop::Init");
|
|
|
|
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FEngineLoop::Init" ), STAT_FEngineLoop_Init, STATGROUP_LoadTime );
|
|
|
|
FScopedSlowTask SlowTask(100);
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
FEmbeddedCommunication::ForceTick(10);
|
|
|
|
// Figure out which UEngine variant to use.
|
|
UClass* EngineClass = nullptr;
|
|
if( !GIsEditor )
|
|
{
|
|
SCOPED_BOOT_TIMING("Create GEngine");
|
|
// We're the game.
|
|
FString GameEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
|
|
EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName);
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load Game Engine class '%s'."), *GameEngineClassName);
|
|
}
|
|
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
|
|
}
|
|
else
|
|
{
|
|
#if WITH_EDITOR
|
|
// We're UnrealEd.
|
|
FString UnrealEdEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni);
|
|
EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName);
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *UnrealEdEngineClassName);
|
|
}
|
|
GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);
|
|
#else
|
|
check(0);
|
|
#endif
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(11);
|
|
|
|
check( GEngine );
|
|
|
|
if (IsMoviePlayerEnabled())
|
|
{
|
|
GetMoviePlayer()->PassLoadingScreenWindowBackToGame();
|
|
}
|
|
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("GEngine->ParseCommandline()");
|
|
GEngine->ParseCommandline();
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(12);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("InitTime");
|
|
InitTime();
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(60);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("GEngine->Init");
|
|
GEngine->Init(this);
|
|
}
|
|
|
|
// Call init callbacks
|
|
{
|
|
SCOPED_BOOT_TIMING("OnPostEngineInit.Broadcast");
|
|
FCoreDelegates::OnPostEngineInit.Broadcast();
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(30);
|
|
|
|
// initialize engine instance discovery
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
SCOPED_BOOT_TIMING("SessionService etc");
|
|
if (!IsRunningCommandlet())
|
|
{
|
|
SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService();
|
|
|
|
if (SessionService.IsValid())
|
|
{
|
|
SessionService->Start();
|
|
}
|
|
}
|
|
|
|
EngineService = new FEngineService();
|
|
TraceService = new FTraceService();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit)");
|
|
// Load all the post-engine init modules
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
|
|
{
|
|
RequestEngineExit(TEXT("One or more modules failed PostEngineInit"));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Call module loading phases completion callbacks
|
|
SetEngineStartupModuleLoadingComplete();
|
|
|
|
// Disable the backlog only after module loading is complete because that gives modules
|
|
// a chance to consume the backlog when constructing output devices.
|
|
GLog->EnableBacklog(false);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("GEngine->Start()");
|
|
GEngine->Start();
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(13);
|
|
|
|
if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
SCOPED_BOOT_TIMING("WaitForEngineLoadingScreenToFinish");
|
|
FPreLoadScreenManager::Get()->SetEngineLoadingComplete(true);
|
|
FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish();
|
|
}
|
|
else if (IsMoviePlayerEnabled())
|
|
{
|
|
SCOPED_BOOT_TIMING("WaitForMovieToFinish");
|
|
GetMoviePlayer()->WaitForMovieToFinish();
|
|
}
|
|
|
|
FTraceAuxiliary::EnableCommandlineChannels();
|
|
|
|
#if !UE_SERVER
|
|
// initialize media framework
|
|
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");
|
|
|
|
if (MediaModule != nullptr)
|
|
{
|
|
MediaModule->SetTimeSource(MakeShareable(new FAppMediaTimeSource));
|
|
}
|
|
#endif
|
|
|
|
FEmbeddedCommunication::ForceTick(14);
|
|
|
|
// initialize automation worker
|
|
#if WITH_AUTOMATION_WORKER
|
|
FModuleManager::Get().LoadModule("AutomationWorker");
|
|
#endif
|
|
|
|
// Automation tests can be invoked locally in non-editor builds configuration (e.g. performance profiling in Test configuration)
|
|
#if WITH_ENGINE && !UE_BUILD_SHIPPING
|
|
FModuleManager::Get().LoadModule("AutomationController");
|
|
FModuleManager::GetModuleChecked<IAutomationControllerModule>("AutomationController").Init();
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("ProfilerClient"));
|
|
}
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("SequenceRecorder"));
|
|
FModuleManager::Get().LoadModule(TEXT("SequenceRecorderSections"));
|
|
#endif
|
|
|
|
GIsRunning = true;
|
|
|
|
if (!GIsEditor)
|
|
{
|
|
// hide a couple frames worth of rendering
|
|
FViewport::SetGameRenderingEnabled(true, 3);
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(15);
|
|
|
|
FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved);
|
|
|
|
// Ready to measure thread heartbeat
|
|
FThreadHeartBeat::Get().Start();
|
|
|
|
FShaderPipelineCache::PauseBatching();
|
|
{
|
|
#if defined(WITH_CODE_GUARD_HANDLER) && WITH_CODE_GUARD_HANDLER
|
|
void CheckImageIntegrity();
|
|
CheckImageIntegrity();
|
|
#endif
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FCoreDelegates::OnFEngineLoopInitComplete.Broadcast()");
|
|
FCoreDelegates::OnFEngineLoopInitComplete.Broadcast();
|
|
}
|
|
FShaderPipelineCache::ResumeBatching();
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
FEmbeddedCommunication::AllowSleep(TEXT("Startup"));
|
|
FEmbeddedCommunication::KeepAwake(TEXT("FirstTicks"), true);
|
|
#endif
|
|
|
|
#if UE_EXTERNAL_PROFILING_ENABLED
|
|
FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::InitActiveProfiler();
|
|
if (ActiveProfiler)
|
|
{
|
|
ActiveProfiler->Register();
|
|
}
|
|
#endif // UE_EXTERNAL_PROFILING_ENABLED
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::EndOfEngineInit);
|
|
|
|
// Emit logging. Don't edit! Automation looks for this to detect failures during initialization.
|
|
UE_LOG(LogInit, Display, TEXT("Engine is initialized. Leaving FEngineLoop::Init()"));
|
|
return 0;
|
|
}
|
|
|
|
|
|
// 5.4.2 local change to avoid modifying public headers
|
|
namespace PipelineStateCache
|
|
{
|
|
// Waits for any pending tasks to complete.
|
|
extern RHI_API void WaitForAllTasks();
|
|
|
|
}
|
|
|
|
void FEngineLoop::Exit()
|
|
{
|
|
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "EngineLoop.Exit" ) );
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FEngineLoop::Exit);
|
|
TRACE_BOOKMARK(TEXT("EngineLoop.Exit"));
|
|
|
|
ClearPendingCleanupObjects();
|
|
|
|
GIsRunning = 0;
|
|
GLogConsole = nullptr;
|
|
|
|
IInstallBundleManager::InstallBundleCompleteDelegate.RemoveAll(this);
|
|
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
// If we exit before the preload screen is done, clean it up before shutting down
|
|
if (FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
FPreLoadScreenManager::Get()->SetEngineLoadingComplete(true);
|
|
FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish();
|
|
}
|
|
|
|
FPreLoadScreenManager::Destroy();
|
|
}
|
|
|
|
// shutdown visual logger and flush all data
|
|
#if ENABLE_VISUAL_LOG
|
|
FVisualLogger::Get().TearDown();
|
|
#endif
|
|
|
|
FAssetCompilingManager::Get().Shutdown();
|
|
|
|
#if WITH_ENGINE
|
|
// shut down messaging
|
|
delete EngineService;
|
|
EngineService = nullptr;
|
|
|
|
delete TraceService;
|
|
TraceService = nullptr;
|
|
|
|
if (SessionService.IsValid())
|
|
{
|
|
SessionService->Stop();
|
|
SessionService.Reset();
|
|
}
|
|
|
|
if (GDistanceFieldAsyncQueue)
|
|
{
|
|
delete GDistanceFieldAsyncQueue;
|
|
GDistanceFieldAsyncQueue = nullptr;
|
|
}
|
|
|
|
if (GCardRepresentationAsyncQueue)
|
|
{
|
|
delete GCardRepresentationAsyncQueue;
|
|
GCardRepresentationAsyncQueue = nullptr;
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
// Make sure we're not in the middle of loading something after this point as some
|
|
// systems are shut down during PreExit and it can cause issues and crashes if loading continues
|
|
// beyond this point.
|
|
FlushAsyncLoading();
|
|
|
|
if ( GEngine != nullptr )
|
|
{
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
FEditorDelegates::OnEditorPreExit.Broadcast();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
GEngine->PreExit();
|
|
}
|
|
|
|
if (GEngine != nullptr)
|
|
{
|
|
GEngine->ReleaseAudioDeviceManager();
|
|
}
|
|
|
|
|
|
// Make sure we're not in the middle of loading something.
|
|
{
|
|
bool bFlushOnExit = true;
|
|
if (GConfig)
|
|
{
|
|
FBoolConfigValueHelper FlushStreamingOnExitHelper(TEXT("/Script/Engine.StreamingSettings"), TEXT("s.FlushStreamingOnExit"), GEngineIni);
|
|
bFlushOnExit = FlushStreamingOnExitHelper;
|
|
}
|
|
if (bFlushOnExit)
|
|
{
|
|
FlushAsyncLoading();
|
|
}
|
|
else
|
|
{
|
|
CancelAsyncLoading();
|
|
}
|
|
// From now on it's not allowed to request new async loads
|
|
// any new requests done during the scope of the flush should have been flushed as well, now prevent any new requests
|
|
SetAsyncLoadingAllowed(false);
|
|
}
|
|
|
|
// Block till all outstanding resource streaming requests are fulfilled.
|
|
if (!IStreamingManager::HasShutdown())
|
|
{
|
|
UTexture2D::CancelPendingTextureStreaming();
|
|
if (FStreamingManagerCollection* StreamingManager = IStreamingManager::Get_Concurrent())
|
|
{
|
|
StreamingManager->BlockTillAllRequestsFinished();
|
|
}
|
|
}
|
|
FAudioDeviceManager::Shutdown();
|
|
|
|
// close all windows
|
|
FSlateApplication::Shutdown();
|
|
|
|
#if !UE_SERVER
|
|
if ( FEngineFontServices::IsInitialized() )
|
|
{
|
|
FEngineFontServices::Destroy();
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#if WITH_EDITOR
|
|
// These module must be shut down first because other modules may try to access them during shutdown.
|
|
// Accessing these modules at shutdown causes instability since the object system will have been shut down and these modules uses uobjects internally.
|
|
FModuleManager::Get().UnloadModule("AssetTools", true);
|
|
|
|
#endif // WITH_EDITOR
|
|
FModuleManager::Get().UnloadModule("WorldBrowser", true);
|
|
|
|
|
|
#if WITH_ENGINE
|
|
// Reset any in progress PSO compile requests, reduces pipelinestatecache task wait time.
|
|
ClearMaterialPSORequests();
|
|
#endif
|
|
|
|
// Wait for any pending tasks to complete.
|
|
PipelineStateCache::WaitForAllTasks();
|
|
|
|
AppPreExit();
|
|
|
|
TermGamePhys();
|
|
|
|
#if WITH_EDITOR
|
|
IBulkDataRegistry::Shutdown();
|
|
#endif
|
|
|
|
#if WITH_COREUOBJECT
|
|
// PackageResourceManager depends on AssetRegistry, so must be shutdown before we unload the AssetRegistry module
|
|
IPackageResourceManager::Shutdown();
|
|
#endif
|
|
FModuleManager::Get().UnloadModule("AssetRegistry", true);
|
|
|
|
// Stop the rendering thread.
|
|
ShutdownRenderingThread();
|
|
|
|
// Disable the PSO cache
|
|
FShaderPipelineCache::Shutdown();
|
|
|
|
// Free the global shader map, needs to happen before FShaderCodeLibrary::Shutdown to avoid warnings
|
|
// about leaking RHI references.
|
|
ShutdownGlobalShaderMap();
|
|
|
|
// Close shader code map, if any
|
|
FShaderCodeLibrary::Shutdown();
|
|
|
|
// Stop IoDispatcher after FShaderCodeLibrary, as it holds on to file requests
|
|
#if WITH_ENGINE
|
|
UE::DerivedData::IoStore::TearDownIoDispatcher();
|
|
#endif
|
|
#if USE_IO_DISPATCHER
|
|
FIoDispatcher::Shutdown();
|
|
FModuleManager::Get().UnloadModule("IoStoreOnDemandCore", true);
|
|
#if UE_WITH_IOSTOREONDEMAND
|
|
FModuleManager::Get().UnloadModule("IoStoreOnDemand", true);
|
|
#endif
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
// Make sure we shut this down before the modules are torn down, we can clean this up if/when the
|
|
// virtualization module is moved to be a plugin
|
|
UE::Virtualization::Shutdown();
|
|
#endif //WITH_EDITOR
|
|
|
|
#if WITH_ENGINE
|
|
// Save the hot reload state
|
|
IHotReloadInterface* HotReload = IHotReloadInterface::GetPtr();
|
|
if(HotReload != nullptr)
|
|
{
|
|
HotReload->SaveConfig();
|
|
}
|
|
#endif
|
|
|
|
// Unload all modules. Note that this doesn't actually unload the module DLLs (that happens at
|
|
// process exit by the OS), but it does call ShutdownModule() on all loaded modules in the reverse
|
|
// order they were loaded in, so that systems can unregister and perform general clean up.
|
|
{
|
|
SCOPED_BOOT_TIMING("UnloadModulesAtShutdown");
|
|
FModuleManager::Get().UnloadModulesAtShutdown();
|
|
}
|
|
|
|
IStreamingManager::Shutdown();
|
|
|
|
#if HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0)
|
|
FRealtimeGPUProfiler::SafeRelease();
|
|
#endif
|
|
|
|
DestroyMoviePlayer();
|
|
|
|
// Move earlier?
|
|
#if STATS
|
|
FThreadStats::StopThread();
|
|
#endif
|
|
|
|
// Clean up the thread pool
|
|
// GThreadPool might be a wrapper around GLargeThreadPool so we have to destroy it first
|
|
if (GThreadPool != nullptr)
|
|
{
|
|
GThreadPool->Destroy();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GLargeThreadPool != nullptr)
|
|
{
|
|
GLargeThreadPool->Destroy();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
if (GBackgroundPriorityThreadPool != nullptr)
|
|
{
|
|
GBackgroundPriorityThreadPool->Destroy();
|
|
}
|
|
|
|
RHIExit();
|
|
|
|
FTaskGraphInterface::Shutdown();
|
|
|
|
FPlatformMisc::ShutdownTaggedStorage();
|
|
|
|
#if WITH_EDITOR && PLATFORM_WINDOWS
|
|
FWindowsPlatformPerfCounters::Shutdown();
|
|
#endif
|
|
|
|
#if WITH_ENGINE && FRAMEPRO_ENABLED
|
|
FFrameProProfiler::TearDown();
|
|
#endif // FRAMEPRO_ENABLED
|
|
|
|
UE::Trace::Exit();
|
|
}
|
|
|
|
|
|
void FEngineLoop::ProcessLocalPlayerSlateOperations() const
|
|
{
|
|
FSlateApplication& SlateApp = FSlateApplication::Get();
|
|
|
|
// For all the game worlds drill down to the player controller for each game viewport and process it's slate operation
|
|
for ( const FWorldContext& Context : GEngine->GetWorldContexts() )
|
|
{
|
|
UWorld* CurWorld = Context.World();
|
|
if ( CurWorld && CurWorld->IsGameWorld() )
|
|
{
|
|
UGameViewportClient* GameViewportClient = CurWorld->GetGameViewport();
|
|
TSharedPtr< SViewport > ViewportWidget = GameViewportClient ? GameViewportClient->GetGameViewportWidget() : nullptr;
|
|
|
|
if ( ViewportWidget.IsValid() )
|
|
{
|
|
FWidgetPath PathToWidget;
|
|
SlateApp.GeneratePathToWidgetUnchecked(ViewportWidget.ToSharedRef(), PathToWidget);
|
|
|
|
if (PathToWidget.IsValid())
|
|
{
|
|
for (FConstPlayerControllerIterator Iterator = CurWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
|
|
{
|
|
APlayerController* PlayerController = Iterator->Get();
|
|
if (PlayerController)
|
|
{
|
|
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player))
|
|
{
|
|
TOptional<int32> UserIndex = SlateApp.GetUserIndexForController(LocalPlayer->GetControllerId());
|
|
if (UserIndex.IsSet())
|
|
{
|
|
FReply& TheReply = LocalPlayer->GetSlateOperations();
|
|
SlateApp.ProcessExternalReply(PathToWidget, TheReply, UserIndex.GetValue());
|
|
|
|
TheReply = FReply::Unhandled();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
void OnStartupContentMounted(FInstallBundleRequestResultInfo Result, bool bDumpEarlyConfigReads, bool bDumpEarlyPakFileReads, bool bReloadConfig, bool bForceQuitAfterEarlyReads)
|
|
{
|
|
if (Result.bIsStartup && Result.Result == EInstallBundleResult::OK)
|
|
{
|
|
DumpEarlyReads(bDumpEarlyConfigReads, bDumpEarlyPakFileReads, bForceQuitAfterEarlyReads);
|
|
HandleConfigReload(bReloadConfig);
|
|
|
|
IInstallBundleManager::InstallBundleCompleteDelegate.RemoveAll(&GEngineLoop);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void DumpEarlyReads(bool bDumpEarlyConfigReads, bool bDumpEarlyPakFileReads, bool bForceQuitAfterEarlyReads)
|
|
{
|
|
if (bDumpEarlyConfigReads)
|
|
{
|
|
UE::ConfigUtilities::DumpRecordedConfigReadsFromIni();
|
|
UE::ConfigUtilities::DeleteRecordedConfigReadsFromIni();
|
|
}
|
|
|
|
if (bDumpEarlyPakFileReads)
|
|
{
|
|
DumpRecordedFileReadsFromPaks();
|
|
DeleteRecordedFileReadsFromPaks();
|
|
}
|
|
|
|
if (bForceQuitAfterEarlyReads)
|
|
{
|
|
GLog->Flush();
|
|
if (GEngine)
|
|
{
|
|
GEngine->DeferredCommands.Emplace(TEXT("Quit force"));
|
|
}
|
|
else
|
|
{
|
|
FPlatformMisc::RequestExit(true, TEXT("DumpEarlyReads"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void HandleConfigReload(bool bReloadConfig)
|
|
{
|
|
if (bReloadConfig)
|
|
{
|
|
UE::ConfigUtilities::ReapplyRecordedCVarSettingsFromIni();
|
|
UE::ConfigUtilities::DeleteRecordedCVarSettingsFromIni();
|
|
}
|
|
}
|
|
|
|
bool FEngineLoop::ShouldUseIdleMode() const
|
|
{
|
|
static const auto CVarIdleWhenNotForeground = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("t.IdleWhenNotForeground"));
|
|
bool bIdleMode = false;
|
|
|
|
// Yield cpu usage if desired
|
|
if (FApp::IsGame()
|
|
&& FPlatformProperties::SupportsWindowedMode()
|
|
&& CVarIdleWhenNotForeground->GetValueOnGameThread()
|
|
&& !FApp::HasFocus())
|
|
{
|
|
bIdleMode = true;
|
|
}
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
if (FEmbeddedCommunication::IsAwakeForTicking() == false)
|
|
{
|
|
bIdleMode = true;
|
|
}
|
|
#endif
|
|
|
|
if (bIdleMode)
|
|
{
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (!Context.World()->AreAlwaysLoadedLevelsLoaded())
|
|
{
|
|
bIdleMode = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bIdleMode;
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST && MALLOC_GT_HOOKS
|
|
|
|
#include "Containers/StackTracker.h"
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn(
|
|
TEXT("LogGameThreadMallocChurn.Enable"),
|
|
0,
|
|
TEXT("If > 0, then collect sample game thread malloc, realloc and free, periodically print a report of the worst offenders."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_PrintFrequency(
|
|
TEXT("LogGameThreadMallocChurn.PrintFrequency"),
|
|
300,
|
|
TEXT("Number of frames between churn reports."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_Threshhold(
|
|
TEXT("LogGameThreadMallocChurn.Threshhold"),
|
|
10,
|
|
TEXT("Minimum average number of allocs per frame to include in the report."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_SampleFrequency(
|
|
TEXT("LogGameThreadMallocChurn.SampleFrequency"),
|
|
100,
|
|
TEXT("Number of allocs to skip between samples. This is used to prevent churn sampling from slowing the game down too much."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_StackIgnore(
|
|
TEXT("LogGameThreadMallocChurn.StackIgnore"),
|
|
2,
|
|
TEXT("Number of items to discard from the top of a stack frame."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_RemoveAliases(
|
|
TEXT("LogGameThreadMallocChurn.RemoveAliases"),
|
|
1,
|
|
TEXT("If > 0 then remove aliases from the counting process. This essentialy merges addresses that have the same human readable string. It is slower."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_StackLen(
|
|
TEXT("LogGameThreadMallocChurn.StackLen"),
|
|
3,
|
|
TEXT("Maximum number of stack frame items to keep. This improves aggregation because calls that originate from multiple places but end up in the same place will be accounted together."));
|
|
|
|
|
|
extern CORE_API TFunction<void(int32)>* GGameThreadMallocHook;
|
|
|
|
struct FScopedSampleMallocChurn
|
|
{
|
|
static FStackTracker GGameThreadMallocChurnTracker;
|
|
static uint64 DumpFrame;
|
|
|
|
bool bEnabled;
|
|
int32 CountDown;
|
|
TFunction<void(int32)> Hook;
|
|
|
|
FScopedSampleMallocChurn()
|
|
: bEnabled(CVarLogGameThreadMallocChurn.GetValueOnGameThread() > 0)
|
|
, CountDown(CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread())
|
|
, Hook(
|
|
[this](int32 Index)
|
|
{
|
|
if (--CountDown <= 0)
|
|
{
|
|
CountDown = CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread();
|
|
CollectSample();
|
|
}
|
|
}
|
|
)
|
|
{
|
|
if (bEnabled)
|
|
{
|
|
check(IsInGameThread());
|
|
check(!GGameThreadMallocHook);
|
|
if (!DumpFrame)
|
|
{
|
|
DumpFrame = GFrameCounter + CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread();
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
GGameThreadMallocChurnTracker.ToggleTracking(true, true);
|
|
GGameThreadMallocHook = &Hook;
|
|
}
|
|
else
|
|
{
|
|
check(IsInGameThread());
|
|
GGameThreadMallocChurnTracker.ToggleTracking(false, true);
|
|
if (DumpFrame)
|
|
{
|
|
DumpFrame = 0;
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
}
|
|
}
|
|
~FScopedSampleMallocChurn()
|
|
{
|
|
if (bEnabled)
|
|
{
|
|
check(IsInGameThread());
|
|
check(GGameThreadMallocHook == &Hook);
|
|
GGameThreadMallocHook = nullptr;
|
|
GGameThreadMallocChurnTracker.ToggleTracking(false, true);
|
|
check(DumpFrame);
|
|
if (GFrameCounter > DumpFrame)
|
|
{
|
|
PrintResultsAndReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollectSample()
|
|
{
|
|
check(IsInGameThread());
|
|
GGameThreadMallocChurnTracker.CaptureStackTrace(CVarLogGameThreadMallocChurn_StackIgnore.GetValueOnGameThread(), nullptr, CVarLogGameThreadMallocChurn_StackLen.GetValueOnGameThread(), CVarLogGameThreadMallocChurn_RemoveAliases.GetValueOnGameThread() > 0);
|
|
}
|
|
void PrintResultsAndReset()
|
|
{
|
|
DumpFrame = GFrameCounter + CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread();
|
|
FOutputDeviceRedirector* Log = FOutputDeviceRedirector::Get();
|
|
float SampleAndFrameCorrection = float(CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread()) / float(CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread());
|
|
GGameThreadMallocChurnTracker.DumpStackTraces(CVarLogGameThreadMallocChurn_Threshhold.GetValueOnGameThread(), *Log, SampleAndFrameCorrection);
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
};
|
|
FStackTracker FScopedSampleMallocChurn::GGameThreadMallocChurnTracker;
|
|
uint64 FScopedSampleMallocChurn::DumpFrame = 0;
|
|
|
|
#endif
|
|
|
|
#if WITH_RHI_BREADCRUMBS
|
|
TOptional<FRHIBreadcrumbEventManual> GRHIFrameBreadcrumb;
|
|
#endif
|
|
|
|
static inline void BeginFrameRenderThread(FRHICommandListImmediate& RHICmdList, uint64 CurrentFrameCounter)
|
|
{
|
|
if ( !FApp::CanEverRender() )
|
|
{
|
|
GFrameNumberRenderThread++;
|
|
return;
|
|
}
|
|
|
|
TRACE_BEGIN_FRAME(TraceFrameType_Rendering);
|
|
GFrameNumberRenderThread++;
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
// If we are profiling, kick off a long GPU task to make the GPU always behind the CPU so that we
|
|
// won't get GPU idle time measured in profiling results
|
|
#if WITH_PROFILEGPU && (RHI_NEW_GPU_PROFILER == 0)
|
|
if (GTriggerGPUProfile && !GTriggerGPUHitchProfile)
|
|
{
|
|
IssueScalableLongGPUTask(RHICmdList);
|
|
}
|
|
#endif
|
|
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
#if WITH_RHI_BREADCRUMBS
|
|
#if CSV_PROFILER
|
|
if (FCsvProfiler::Get()->IsCapturing_Renderthread())
|
|
{
|
|
GRHIFrameBreadcrumb.Emplace(RHICmdList, RHI_BREADCRUMB_DESC_FORWARD_VALUES(TEXT("Frame"), TEXT("Frame %d (CsvFrame %d)"), RHI_GPU_STAT_ARGS_NONE)(
|
|
RHI_BREADCRUMB_FIELD("Frame Number", CurrentFrameCounter)
|
|
, RHI_BREADCRUMB_FIELD("Csv Frame Number", FCsvProfiler::Get()->GetCaptureFrameNumberRT())
|
|
));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
GRHIFrameBreadcrumb.Emplace(RHICmdList, RHI_BREADCRUMB_DESC_FORWARD_VALUES(TEXT("Frame"), TEXT("Frame %d"), RHI_GPU_STAT_ARGS_NONE)(
|
|
RHI_BREADCRUMB_FIELD("Frame Number", CurrentFrameCounter)
|
|
));
|
|
}
|
|
#endif
|
|
|
|
GPU_STATS_BEGINFRAME(RHICmdList);
|
|
FCoreDelegates::OnBeginFrameRT.Broadcast();
|
|
|
|
RHICmdList.EnqueueLambda([CurrentFrameCounter](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
UEngine::SetRenderSubmitLatencyMarkerStart(CurrentFrameCounter);
|
|
});
|
|
|
|
#if CSV_PROFILER_STATS
|
|
FCsvProfiler::BeginExclusiveStat("RenderThreadOther");
|
|
#endif
|
|
|
|
GVisualizeTexture.BeginFrameRenderThread();
|
|
|
|
// Waits after this point but before BeginRenderingViewFamilies are not on the RT critical path (since we're waiting for the GT)
|
|
UE::Stats::FThreadIdleStats::EndCriticalPath();
|
|
}
|
|
|
|
|
|
static inline void EndFrameRenderThread(FRHICommandListImmediate& RHICmdList, uint64 CurrentFrameCounter)
|
|
{
|
|
if ( !FApp::CanEverRender() )
|
|
{
|
|
RHICmdList.EndFrame();
|
|
return;
|
|
}
|
|
|
|
#if CSV_PROFILER_STATS
|
|
FCsvProfiler::EndExclusiveStat("RenderThreadOther");
|
|
#endif
|
|
|
|
RHICmdList.EnqueueLambda([CurrentFrameCounter](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
UEngine::SetRenderSubmitLatencyMarkerEnd(CurrentFrameCounter);
|
|
});
|
|
|
|
FCoreDelegates::OnEndFrameRT.Broadcast();
|
|
GVisualizeTexture.EndFrameRenderThread();
|
|
|
|
GPU_STATS_ENDFRAME(RHICmdList);
|
|
|
|
#if WITH_RHI_BREADCRUMBS
|
|
GRHIFrameBreadcrumb->End(RHICmdList);
|
|
GRHIFrameBreadcrumb.Reset();
|
|
#endif
|
|
|
|
RHICmdList.EndFrame();
|
|
|
|
TRACE_END_FRAME(TraceFrameType_Rendering);
|
|
}
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
#include "Misc/EmbeddedCommunication.h"
|
|
#endif
|
|
|
|
void FEngineLoop::Tick()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FEngineLoop::Tick);
|
|
SCOPE_STALL_COUNTER(FEngineLoop::Tick, 2.0);
|
|
|
|
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST && MALLOC_GT_HOOKS
|
|
FScopedSampleMallocChurn ChurnTracker;
|
|
#endif
|
|
// let the low level mem tracker pump once a frame to update states
|
|
LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame());
|
|
|
|
LLM_SCOPE(ELLMTag::EngineMisc);
|
|
|
|
BeginExitIfRequested();
|
|
#if !UE_BUILD_SHIPPING
|
|
if (GScopedTestExit.IsValid() && GScopedTestExit->RequestExit())
|
|
{
|
|
UE_LOG(LogExit, Display, TEXT("**** TestExit: %s ****"), *GScopedTestExit->RequestExitPhrase());
|
|
FPlatformMisc::RequestExit(true, TEXT("FEngineLoop::Tick.GScopedTestExit"));
|
|
}
|
|
#endif
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HeartBeat);
|
|
// Send a heartbeat for the diagnostics thread
|
|
FThreadHeartBeat::Get().HeartBeat(true);
|
|
}
|
|
|
|
FGameThreadHitchHeartBeat::Get().FrameStart();
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(TickHotfixables);
|
|
FPlatformMisc::TickHotfixables();
|
|
}
|
|
|
|
// Must be called on the game thread outside of any frame breadcrumbs.
|
|
LatchRenderThreadConfiguration();
|
|
|
|
// Make sure something is ticking the rendering tickables in -onethread mode to avoid leaks/bugs.
|
|
if (!GUseThreadedRendering)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(TickRenderingTickables);
|
|
TickRenderingTickables();
|
|
}
|
|
|
|
// Ensure we aren't starting a frame while loading or playing a loading movie
|
|
FMoviePlayerProxy::BlockingForceFinished();
|
|
ensure(!IsMoviePlayerEnabled() || (GetMoviePlayer()->IsLoadingFinished() && !GetMoviePlayer()->IsMovieCurrentlyPlaying()));
|
|
|
|
// Frame profiling kickoff
|
|
#if UE_EXTERNAL_PROFILING_ENABLED
|
|
FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::GetActiveProfiler();
|
|
if (ActiveProfiler)
|
|
{
|
|
ActiveProfiler->FrameSync();
|
|
}
|
|
#endif // UE_EXTERNAL_PROFILING_ENABLED
|
|
|
|
FPlatformMisc::BeginNamedEventFrame();
|
|
|
|
uint64 CurrentFrameCounter = GFrameCounter;
|
|
|
|
#if ENABLE_NAMED_EVENTS
|
|
bool bTraceCpuChannelEnabled = false;
|
|
#if CPUPROFILERTRACE_ENABLED
|
|
bTraceCpuChannelEnabled = UE_TRACE_CHANNELEXPR_IS_ENABLED(CpuChannel);
|
|
static FAutoNamedEventsToggler TraceNamedEventsToggler;
|
|
TraceNamedEventsToggler.Update(bTraceCpuChannelEnabled && UE::Trace::IsTracing());
|
|
#endif
|
|
|
|
// Generate the Frame event string
|
|
TCHAR IndexedFrameString[32] = { 0 };
|
|
const TCHAR* FrameStringPrefix = bTraceCpuChannelEnabled ? TEXT(" ") : TEXT("Frame ");
|
|
#if CSV_PROFILER
|
|
if (FCsvProfiler::Get()->IsCapturing())
|
|
{
|
|
FCString::Snprintf(IndexedFrameString, 32, TEXT("Csv%s%d"), FrameStringPrefix, FCsvProfiler::Get()->GetCaptureFrameNumber());
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
FCString::Snprintf(IndexedFrameString, 32, TEXT("%s%d"), FrameStringPrefix, CurrentFrameCounter);
|
|
}
|
|
const TCHAR* FrameString = IndexedFrameString;
|
|
#if CPUPROFILERTRACE_ENABLED
|
|
UE_TRACE_LOG_SCOPED_T(Cpu, Frame, CpuChannel)
|
|
<< Frame.Name(FrameString);
|
|
#endif // CPUPROFILERTRACE_ENABLED
|
|
|
|
#if PLATFORM_LIMIT_PROFILER_UNIQUE_NAMED_EVENTS
|
|
FrameString = TEXT("FEngineLoop");
|
|
#endif
|
|
SCOPED_NAMED_EVENT_TCHAR_CONDITIONAL(FrameString, FColor::Red, !bTraceCpuChannelEnabled);
|
|
#endif //ENABLE_NAMED_EVENTS
|
|
|
|
// execute callbacks for cvar changes
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_CallAllConsoleVariableSinks);
|
|
IConsoleManager::Get().CallAllConsoleVariableSinks();
|
|
}
|
|
|
|
TRACE_BEGIN_FRAME(TraceFrameType_Game);
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FrameTime);
|
|
|
|
#if WITH_PROFILEGPU && !UE_BUILD_SHIPPING && (RHI_NEW_GPU_PROFILER == 0)
|
|
// Issue the measurement of the execution time of a basic LongGPUTask unit on the very first frame
|
|
// The results will be retrived on the first call of IssueScalableLongGPUTask
|
|
if (GFrameCounter == 0 && IsFeatureLevelSupported(GMaxRHIShaderPlatform, ERHIFeatureLevel::SM5) && FApp::CanEverRender())
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
ENQUEUE_RENDER_COMMAND(MeasureLongGPUTaskExecutionTimeCmd)(
|
|
[](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
MeasureLongGPUTaskExecutionTime(RHICmdList);
|
|
});
|
|
}
|
|
#endif
|
|
|
|
#if WITH_ENGINE && CSV_PROFILER
|
|
UpdateCoreCsvStats_BeginFrame();
|
|
#endif
|
|
|
|
FCoreDelegates::OnBeginFrame.Broadcast();
|
|
|
|
// flush debug output which has been buffered by other threads
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_FlushThreadedLogs);
|
|
GLog->FlushThreadedLogs(EOutputDeviceRedirectorFlushOptions::Async);
|
|
}
|
|
|
|
// exit if frame limit is reached in benchmark mode, or if time limit is reached
|
|
if ((FApp::IsBenchmarking() && MaxFrameCounter && (GFrameCounter > MaxFrameCounter)) ||
|
|
(MaxTickTime && (TotalTickTime > MaxTickTime)))
|
|
{
|
|
FPlatformMisc::RequestExit(false, TEXT("FEngineLoop::Tick.Benchmarking"));
|
|
}
|
|
|
|
// set FApp::CurrentTime, FApp::DeltaTime and potentially wait to enforce max tick rate
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_UpdateTimeAndHandleMaxTickRate);
|
|
GEngine->UpdateTimeAndHandleMaxTickRate();
|
|
GEngine->SetSimulationLatencyMarkerStart(CurrentFrameCounter);
|
|
}
|
|
|
|
// beginning of RHI frame
|
|
ENQUEUE_RENDER_COMMAND(BeginFrame)([CurrentFrameCounter](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
BeginFrameRenderThread(RHICmdList, CurrentFrameCounter);
|
|
});
|
|
|
|
for (FSceneInterface* Scene : GetRendererModule().GetAllocatedScenes())
|
|
{
|
|
ENQUEUE_RENDER_COMMAND(FScene_StartFrame)([Scene](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
Scene->StartFrame();
|
|
});
|
|
}
|
|
|
|
UE::RenderCommandPipe::StartRecording();
|
|
|
|
#if !UE_SERVER && WITH_ENGINE
|
|
if (!GIsEditor && GEngine->GameViewport && GEngine->GameViewport->GetWorld() && GEngine->GameViewport->GetWorld()->IsCameraMoveable())
|
|
{
|
|
// When not in editor, we emit dynamic resolution's begin frame right after RHI's.
|
|
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::BeginFrame);
|
|
}
|
|
#endif
|
|
|
|
// tick performance monitoring
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_TickFPSChart);
|
|
GEngine->TickPerformanceMonitoring( FApp::GetDeltaTime() );
|
|
|
|
extern COREUOBJECT_API void ResetAsyncLoadingStats();
|
|
ResetAsyncLoadingStats();
|
|
}
|
|
|
|
#if UPDATE_MALLOC_STATS
|
|
// update memory allocator stats
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Malloc_UpdateStats);
|
|
UE::Private::GMalloc->UpdateStats();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
UE::Stats::FStats::AdvanceFrame( false, UE::Stats::FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) );
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_FrameTime );
|
|
|
|
// Calculates average FPS/MS (outside STATS on purpose)
|
|
CalculateFPSTimings();
|
|
|
|
// handle some per-frame tasks on the rendering thread
|
|
ENQUEUE_RENDER_COMMAND(ResetDeferredUpdates)(
|
|
[](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
FDeferredUpdateResource::ResetNeedsUpdate();
|
|
});
|
|
|
|
// Don't pump messages if we're running embedded as the outer application
|
|
// will pass us messages instead.
|
|
if (!GUELibraryOverrideSettings.bIsEmbedded)
|
|
{
|
|
GEngine->SetInputSampleLatencyMarker(CurrentFrameCounter);
|
|
|
|
//QUICK_SCOPE_CYCLE_COUNTER(STAT_PumpMessages);
|
|
FPlatformApplicationMisc::PumpMessages(true);
|
|
}
|
|
|
|
bool bIdleMode;
|
|
{
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Idle);
|
|
|
|
// Idle mode prevents ticking and rendering completely
|
|
bIdleMode = ShouldUseIdleMode();
|
|
if (bIdleMode)
|
|
{
|
|
// Yield CPU time
|
|
FPlatformProcess::Sleep(.1f);
|
|
}
|
|
}
|
|
|
|
// @todo vreditor urgent: Temporary hack to allow world-to-meters to be set before
|
|
// input is polled for motion controller devices each frame.
|
|
extern ENGINE_API float GNewWorldToMetersScale;
|
|
if( GNewWorldToMetersScale != 0.0f )
|
|
{
|
|
#if WITH_ENGINE
|
|
UWorld* WorldToScale = GWorld;
|
|
|
|
#if WITH_EDITOR
|
|
if( GIsEditor && GEditor->PlayWorld != nullptr && GEditor->bIsSimulatingInEditor )
|
|
{
|
|
WorldToScale = GEditor->PlayWorld;
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
if( WorldToScale != nullptr )
|
|
{
|
|
if( GNewWorldToMetersScale != WorldToScale->GetWorldSettings()->WorldToMeters )
|
|
{
|
|
WorldToScale->GetWorldSettings()->WorldToMeters = GNewWorldToMetersScale;
|
|
}
|
|
}
|
|
|
|
GNewWorldToMetersScale = 0.0f;
|
|
}
|
|
#endif //WITH_ENGINE
|
|
|
|
// tick active platform files
|
|
FPlatformFileManager::Get().TickActivePlatformFile();
|
|
|
|
// Roughly track the time when the input was sampled
|
|
FCoreDelegates::OnSamplingInput.Broadcast();
|
|
|
|
// process accumulated Slate input
|
|
if (FSlateApplication::IsInitialized() && !bIdleMode)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Input);
|
|
SCOPE_TIME_GUARD(TEXT("SlateInput"));
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_SlateInput);
|
|
LLM_SCOPE(ELLMTag::UI);
|
|
|
|
FSlateApplication& SlateApp = FSlateApplication::Get();
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_PollGameDeviceState);
|
|
SlateApp.PollGameDeviceState();
|
|
}
|
|
// Gives widgets a chance to process any accumulated input
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_FinishedInputThisFrame);
|
|
SlateApp.FinishedInputThisFrame();
|
|
}
|
|
}
|
|
|
|
// main game engine tick (world, game objects, etc.)
|
|
GEngine->Tick(FApp::GetDeltaTime(), bIdleMode);
|
|
|
|
// If a movie that is blocking the game thread has been playing,
|
|
// wait for it to finish before we continue to tick or tick again
|
|
// We do this right after GEngine->Tick() because that is where user code would initiate a load / movie.
|
|
{
|
|
if (FPreLoadScreenManager::Get())
|
|
{
|
|
if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
//Wait for any Engine Loading Screen to stop
|
|
if (FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
|
{
|
|
FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish();
|
|
}
|
|
|
|
//Switch Game Window Back
|
|
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
|
|
if (GameEngine)
|
|
{
|
|
GameEngine->SwitchGameWindowToUseGameViewport();
|
|
}
|
|
}
|
|
|
|
#if !UE_SERVER
|
|
// Is it ok to start up the movie player?
|
|
if (IsMoviePlayerEnabled() && !GetMoviePlayer()->IsMovieCurrentlyPlaying())
|
|
{
|
|
// Enable the MoviePlayer now that the preload screen manager is done.
|
|
if (FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer())
|
|
{
|
|
GetMoviePlayer()->Initialize(*Renderer, FPreLoadScreenManager::Get()->GetRenderWindow());
|
|
}
|
|
}
|
|
#endif // !UE_SERVER
|
|
|
|
//Destroy / Clean Up PreLoadScreenManager as we are now done
|
|
FPreLoadScreenManager::Destroy();
|
|
}
|
|
else if (IsMoviePlayerEnabled())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_WaitForMovieToFinish);
|
|
GetMoviePlayer()->WaitForMovieToFinish(true);
|
|
}
|
|
}
|
|
|
|
FAssetCompilingManager::Get().ProcessAsyncTasks(true);
|
|
|
|
FMoviePlayerProxy::BlockingForceFinished();
|
|
// Tick the platform and input portion of Slate application, we need to do this before we run things
|
|
// concurrent with networking.
|
|
if (FSlateApplication::IsInitialized() && !bIdleMode)
|
|
{
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_ProcessPlayerControllersSlateOperations);
|
|
check(!IsRunningDedicatedServer());
|
|
|
|
// Process slate operations accumulated in the world ticks.
|
|
ProcessLocalPlayerSlateOperations();
|
|
}
|
|
|
|
FSlateApplication::Get().Tick(ESlateTickType::PlatformAndInput);
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
// process concurrent Slate tasks
|
|
FGraphEventRef ConcurrentTask;
|
|
FEvent* ConcurrentTaskCompleteEvent = nullptr;
|
|
const bool bDoConcurrentSlateTick = GEngine->ShouldDoAsyncEndOfFrameTasks();
|
|
|
|
const UGameViewportClient* const GameViewport = GEngine->GameViewport;
|
|
const UWorld* const GameViewportWorld = GameViewport ? GameViewport->GetWorld() : nullptr;
|
|
UDemoNetDriver* const CurrentDemoNetDriver = GameViewportWorld ? GameViewportWorld->GetDemoNetDriver() : nullptr;
|
|
|
|
const bool bValidateReplicatedProperties = CurrentDemoNetDriver && CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties.GetValueOnGameThread() != 0;
|
|
|
|
if (bDoConcurrentSlateTick)
|
|
{
|
|
const float DeltaSeconds = FApp::GetDeltaTime();
|
|
|
|
if (CurrentDemoNetDriver && CurrentDemoNetDriver->ShouldTickFlushAsyncEndOfFrame())
|
|
{
|
|
ConcurrentTaskCompleteEvent = FPlatformProcess::GetSynchEventFromPool();
|
|
check(ConcurrentTaskCompleteEvent);
|
|
|
|
ConcurrentTask = TGraphTask<FExecuteConcurrentWithSlateTickTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
|
|
[CurrentDemoNetDriver, DeltaSeconds]()
|
|
{
|
|
if (CVarDoAsyncEndOfFrameTasksRandomize.GetValueOnAnyThread(true) > 0)
|
|
{
|
|
FPlatformProcess::Sleep(FMath::RandRange(0.0f, .003f)); // this shakes up the threading to find race conditions
|
|
}
|
|
|
|
if (CurrentDemoNetDriver != nullptr)
|
|
{
|
|
CurrentDemoNetDriver->TickFlushAsyncEndOfFrame(DeltaSeconds);
|
|
}
|
|
},
|
|
ConcurrentTaskCompleteEvent);
|
|
check(ConcurrentTask.IsValid());
|
|
|
|
// If we're validating, we want to only test the slate tick so wait for the task
|
|
if (bValidateReplicatedProperties)
|
|
{
|
|
CSV_SCOPED_SET_WAIT_STAT(ConcurrentWithSlate);
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ConcurrentWithSlateTickTasks_Wait);
|
|
check(ConcurrentTaskCompleteEvent);
|
|
ConcurrentTaskCompleteEvent->Wait();
|
|
FPlatformProcess::ReturnSynchEventToPool(ConcurrentTaskCompleteEvent);
|
|
ConcurrentTaskCompleteEvent = nullptr;
|
|
ConcurrentTask = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optionally validate that Slate has not modified any replicated properties for client replay recording.
|
|
FDemoSavedPropertyState PreSlateObjectStates;
|
|
if (bValidateReplicatedProperties)
|
|
{
|
|
PreSlateObjectStates = CurrentDemoNetDriver->SavePropertyState();
|
|
}
|
|
#endif
|
|
|
|
// Tick(Advance) Time for the application and then tick and paint slate application widgets.
|
|
// We split separate this action from the one above to permit running network replication concurrent with slate widget ticking and painting.
|
|
if (FSlateApplication::IsInitialized() && !bIdleMode)
|
|
{
|
|
const bool bRenderingSuspended = GEngine->IsRenderingSuspended();
|
|
|
|
FMoviePlayerProxy::SetIsSlateThreadAllowed(false);
|
|
FSlateApplication::Get().Tick(bRenderingSuspended ? ESlateTickType::Time : ESlateTickType::TimeAndWidgets);
|
|
FMoviePlayerProxy::SetIsSlateThreadAllowed(true);
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
if (bValidateReplicatedProperties)
|
|
{
|
|
const bool bReplicatedPropertiesDifferent = CurrentDemoNetDriver->ComparePropertyState(PreSlateObjectStates);
|
|
if (bReplicatedPropertiesDifferent)
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT("Replicated properties changed during Slate tick!"));
|
|
}
|
|
}
|
|
|
|
if (ConcurrentTask.GetReference())
|
|
{
|
|
CSV_SCOPED_SET_WAIT_STAT(ConcurrentWithSlate);
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ConcurrentWithSlateTickTasks_Wait);
|
|
check(ConcurrentTaskCompleteEvent);
|
|
ConcurrentTaskCompleteEvent->Wait();
|
|
FPlatformProcess::ReturnSynchEventToPool(ConcurrentTaskCompleteEvent);
|
|
ConcurrentTaskCompleteEvent = nullptr;
|
|
ConcurrentTask = nullptr;
|
|
}
|
|
#endif
|
|
|
|
#if STATS
|
|
// Clear any stat group notifications we have pending just in case they weren't claimed during FSlateApplication::Get().Tick
|
|
extern CORE_API void ClearPendingStatGroups();
|
|
ClearPendingStatGroups();
|
|
#endif
|
|
|
|
#if WITH_EDITOR && !UE_BUILD_SHIPPING
|
|
// tick automation controller (Editor only)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationController);
|
|
static FName AutomationController("AutomationController");
|
|
if (FModuleManager::Get().IsModuleLoaded(AutomationController))
|
|
{
|
|
FModuleManager::GetModuleChecked<IAutomationControllerModule>(AutomationController).Tick();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if WITH_ENGINE && WITH_AUTOMATION_WORKER
|
|
// tick automation worker
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationWorker);
|
|
static const FName AutomationWorkerModuleName = TEXT("AutomationWorker");
|
|
if (FModuleManager::Get().IsModuleLoaded(AutomationWorkerModuleName))
|
|
{
|
|
FModuleManager::GetModuleChecked<IAutomationWorkerModule>(AutomationWorkerModuleName).Tick();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
UE::RenderCommandPipe::StopRecording();
|
|
|
|
for (FSceneInterface* Scene : GetRendererModule().GetAllocatedScenes())
|
|
{
|
|
ENQUEUE_RENDER_COMMAND(FScene_EndFrame)([Scene](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
Scene->EndFrame(RHICmdList);
|
|
});
|
|
}
|
|
|
|
// tick render hardware interface
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RHITickTime);
|
|
RHITick( FApp::GetDeltaTime() ); // Update RHI.
|
|
}
|
|
|
|
// We need to set this marker before EndFrameRenderThread is enqueued.
|
|
// If multithreaded rendering is off, it can cause a bad ordering of game and rendering markers.
|
|
GEngine->SetSimulationLatencyMarkerEnd(CurrentFrameCounter);
|
|
|
|
// Disregard first few ticks for total tick time as it includes loading and such.
|
|
if (GFrameCounter > 5)
|
|
{
|
|
TotalTickTime += FApp::GetDeltaTime();
|
|
}
|
|
|
|
// Find the objects which need to be cleaned up the next frame.
|
|
FPendingCleanupObjects* PreviousPendingCleanupObjects = PendingCleanupObjects;
|
|
PendingCleanupObjects = GetPendingCleanupObjects();
|
|
|
|
// This could be perhaps moved down to get greater parallelism
|
|
// Sync game and render/RHI threads.
|
|
FFrameEndSync::Sync(FFrameEndSync::EFlushMode::EndFrame);
|
|
|
|
// tick core ticker, threads & deferred commands
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_DeferredTickTime);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(DeferredTickTime);
|
|
// Delete the objects which were enqueued for deferred cleanup before the previous frame.
|
|
delete PreviousPendingCleanupObjects;
|
|
|
|
#if WITH_COREUOBJECT
|
|
DeleteLoaders(); // destroy all linkers pending delete
|
|
#endif
|
|
|
|
FTSTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
|
|
FThreadManager::Get().Tick();
|
|
GEngine->TickDeferredCommands();
|
|
}
|
|
FMoviePlayerProxy::BlockingForceFinished();
|
|
|
|
#if !UE_SERVER
|
|
// tick media framework
|
|
static const FName MediaModuleName(TEXT("Media"));
|
|
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>(MediaModuleName);
|
|
if (MediaModule != nullptr)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_MediaTickPostRender);
|
|
MediaModule->TickPostRender();
|
|
}
|
|
#endif
|
|
|
|
FCoreDelegates::OnEndFrame.Broadcast();
|
|
|
|
// Increment global frame counter. Once for each engine tick.
|
|
GFrameCounter++;
|
|
|
|
ENQUEUE_RENDER_COMMAND(FrameCounter)(
|
|
[CurrentFrameCounter = GFrameCounter](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
GFrameCounterRenderThread = CurrentFrameCounter;
|
|
});
|
|
|
|
#if WITH_ENGINE && CSV_PROFILER
|
|
// By design, update this after incrementing GFrameCounter. Calls PlatformMemoryHelpers::GetFrameMemoryStats, which caches
|
|
// memory stats based on the value of GFrameCounter. Placing this after the increment guarantees that cached memory stats are
|
|
// canonically updated at this same point each frame, as opposed to arbitrarily in various other code paths (at least when the
|
|
// CSV profiler is compiled in, which is true in all builds except shipping).
|
|
UpdateCoreCsvStats_EndFrame();
|
|
#endif
|
|
|
|
// Tick DumpGPU to start/stop frame dumps
|
|
#if WITH_ENGINE && WITH_DUMPGPU
|
|
UE::RenderCore::DumpGPU::TickEndFrame();
|
|
#endif
|
|
|
|
#if !UE_SERVER && WITH_ENGINE
|
|
{
|
|
// We emit dynamic resolution's end frame right before RHI's. GEngine is going to ignore it if no BeginFrame was done.
|
|
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::EndFrame);
|
|
}
|
|
#endif
|
|
|
|
// end of RHI frame
|
|
ENQUEUE_RENDER_COMMAND(EndFrame)(
|
|
[CurrentFrameCounter](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
EndFrameRenderThread(RHICmdList, CurrentFrameCounter);
|
|
});
|
|
|
|
// Set CPU utilization stats.
|
|
const FCPUTime CPUTime = FPlatformTime::GetCPUTime();
|
|
SET_FLOAT_STAT( STAT_CPUTimePct, CPUTime.CPUTimePct );
|
|
SET_FLOAT_STAT( STAT_CPUTimePctRelative, CPUTime.CPUTimePctRelative );
|
|
|
|
// Set the UObject count stat
|
|
#if !UE_BUILD_TEST && !UE_BUILD_SHIPPING
|
|
SET_DWORD_STAT(STAT_Hash_NumObjects, GUObjectArray.GetObjectArrayNumMinusAvailable());
|
|
#endif // !UE_BUILD_TEST && !UE_BUILD_SHIPPING
|
|
}
|
|
|
|
TRACE_END_FRAME(TraceFrameType_Game);
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
static double LastSleepTime = FPlatformTime::Seconds();
|
|
double TimeNow = FPlatformTime::Seconds();
|
|
if (LastSleepTime > 0 && TimeNow - LastSleepTime >= CVarSecondsBeforeEmbeddedAppSleeps.GetValueOnAnyThread())
|
|
{
|
|
LastSleepTime = 0;
|
|
FEmbeddedCommunication::AllowSleep(TEXT("FirstTicks"));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void FEngineLoop::ClearPendingCleanupObjects()
|
|
{
|
|
FlushRenderingCommands();
|
|
delete PendingCleanupObjects;
|
|
PendingCleanupObjects = nullptr;
|
|
}
|
|
|
|
#endif // WITH_ENGINE
|
|
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogTimestamp(
|
|
TEXT("log.Timestamp"),
|
|
1,
|
|
TEXT("Defines if time is included in each line in the log file and in what form. Layout: [time][frame mod 1000]\n"
|
|
" 0 = Do not display log timestamps\n"
|
|
" 1 = Log time stamps in UTC and frame time (default) e.g. [2015.11.25-21.28.50:803][376]\n"
|
|
" 2 = Log timestamps in seconds elapsed since GStartTime e.g. [0130.29][420]"
|
|
" 3 = Log timestamps in local time and frame time e.g. [2017.08.04-17.59.50:803][420]"
|
|
" 4 = Log timestamps with the engine's timecode and frame time e.g. [17:59:50:18][420]"),
|
|
ECVF_Default);
|
|
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogCategory(
|
|
TEXT("log.Category"),
|
|
1,
|
|
TEXT("Defines if the categoy is included in each line in the log file and in what form.\n"
|
|
" 0 = Do not log category\n"
|
|
" 2 = Log the category (default)"),
|
|
ECVF_Default);
|
|
|
|
|
|
// Gets called any time cvars change (on the main thread)
|
|
static void CVarLogSinkFunction()
|
|
{
|
|
{
|
|
// for debugging
|
|
ELogTimes::Type OldGPrintLogTimes = GPrintLogTimes;
|
|
|
|
int32 LogTimestampValue = CVarLogTimestamp.GetValueOnGameThread();
|
|
|
|
// Note GPrintLogTimes can be used on multiple threads but it should be no issue to change it on the fly
|
|
switch(LogTimestampValue)
|
|
{
|
|
default:
|
|
case 0: GPrintLogTimes = ELogTimes::None; break;
|
|
case 1: GPrintLogTimes = ELogTimes::UTC; break;
|
|
case 2: GPrintLogTimes = ELogTimes::SinceGStartTime; break;
|
|
case 3: GPrintLogTimes = ELogTimes::Local; break;
|
|
case 4: GPrintLogTimes = ELogTimes::Timecode; break;
|
|
}
|
|
}
|
|
|
|
{
|
|
int32 LogCategoryValue = CVarLogCategory.GetValueOnGameThread();
|
|
|
|
// Note GPrintLogCategory can be used on multiple threads but it should be no issue to change it on the fly
|
|
GPrintLogCategory = LogCategoryValue != 0;
|
|
}
|
|
}
|
|
|
|
|
|
FAutoConsoleVariableSink CVarLogSink(FConsoleCommandDelegate::CreateStatic(&CVarLogSinkFunction));
|
|
|
|
static void CheckForPrintTimesOverride()
|
|
{
|
|
// Determine whether to override the default setting for including timestamps in the log.
|
|
FString LogTimes;
|
|
if (GConfig->GetString( TEXT( "LogFiles" ), TEXT( "LogTimes" ), LogTimes, GEngineIni ))
|
|
{
|
|
if (LogTimes == TEXT( "None" ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::None, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
else if (LogTimes == TEXT( "UTC" ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::UTC, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
else if (LogTimes == TEXT( "SinceStart" ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::SinceGStartTime, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
else if (LogTimes == TEXT( "Local" ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::Local, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
else if (LogTimes == TEXT( "Timecode" ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::Timecode, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
// Assume this is a bool for backward compatibility
|
|
else if (FCString::ToBool( *LogTimes ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::UTC, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
}
|
|
|
|
if (FParse::Param( FCommandLine::Get(), TEXT( "LOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::UTC, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "UTCLOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::UTC, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "NOLOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::None, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "LOGTIMESINCESTART" ) ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::SinceGStartTime, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "LOCALLOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::Local, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param(FCommandLine::Get(), TEXT( "LOGTIMECODE" )))
|
|
{
|
|
CVarLogTimestamp->Set((int)ELogTimes::Timecode, ECVF_SetByCommandline);
|
|
}
|
|
}
|
|
|
|
#if UE_EDITOR
|
|
//Standardize paths when deciding if running the proper editor exe.
|
|
void CleanUpPath(FString& InPath)
|
|
{
|
|
//Converts to full path will also replace '\' with '/' and will collapse relative directories (C:\foo\..\bar to C:\bar)
|
|
InPath = FPaths::ConvertRelativePathToFull(InPath);
|
|
FPaths::RemoveDuplicateSlashes(InPath);
|
|
}
|
|
|
|
bool DoesCurrentExecutableMatch(const FString& EditorTargetFileName)
|
|
{
|
|
FTargetReceipt Receipt;
|
|
if (!FPaths::FileExists(EditorTargetFileName) || !Receipt.Read(EditorTargetFileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Returning false for -Cmd matches legacy behavior; do we want to change this?
|
|
const bool bCheckLaunch_true = true, bCheckCmd_false = false;
|
|
return Receipt.LaunchesCurrentExecutable(bCheckLaunch_true, bCheckCmd_false);
|
|
}
|
|
|
|
bool LaunchCorrectEditorExecutable(const FString& EditorTargetFileName)
|
|
{
|
|
// Don't allow relaunching the executable if we're running some unattended scripted process.
|
|
if(FApp::IsUnattended())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Figure out the executable that we should be running
|
|
FString LaunchExecutableName;
|
|
if(EditorTargetFileName.Len() == 0)
|
|
{
|
|
LaunchExecutableName = FPlatformProcess::GenerateApplicationPath(TEXT("UnrealEditor"), FApp::GetBuildConfiguration());
|
|
}
|
|
else
|
|
{
|
|
FTargetReceipt Receipt;
|
|
if(!FPaths::FileExists(EditorTargetFileName) || !Receipt.Read(EditorTargetFileName))
|
|
{
|
|
return false;
|
|
}
|
|
LaunchExecutableName = Receipt.Launch;
|
|
}
|
|
CleanUpPath(LaunchExecutableName);
|
|
|
|
// Get the current executable name. Don't allow relaunching if we're running the console app.
|
|
FString CurrentExecutableName = FPlatformProcess::ExecutablePath();
|
|
if(FPaths::GetBaseFilename(CurrentExecutableName).EndsWith(TEXT("-Cmd")))
|
|
{
|
|
return false;
|
|
}
|
|
CleanUpPath(CurrentExecutableName);
|
|
|
|
// Nothing to do if they're the same
|
|
if(FPaths::IsSamePath(LaunchExecutableName, CurrentExecutableName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Relaunch the correct executable
|
|
UE_LOG(LogInit, Display, TEXT("Running incorrect executable for target (%s). Launching %s instead..."), *CurrentExecutableName, *LaunchExecutableName);
|
|
FPlatformProcess::CreateProc(*IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*LaunchExecutableName), FCommandLine::GetOriginal(), true, false, false, nullptr, 0, nullptr, nullptr, nullptr);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/* FEngineLoop static interface
|
|
*****************************************************************************/
|
|
|
|
|
|
bool FEngineLoop::AppInit( )
|
|
{
|
|
{
|
|
SCOPED_BOOT_TIMING("BeginInitTextLocalization");
|
|
BeginInitTextLocalization();
|
|
}
|
|
|
|
|
|
|
|
// Error history.
|
|
FCString::Strcpy(GErrorHist, TEXT("Fatal error!" LINE_TERMINATOR_ANSI LINE_TERMINATOR_ANSI));
|
|
|
|
// Platform specific pre-init.
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformMisc::PlatformPreInit");
|
|
FPlatformMisc::PlatformPreInit();
|
|
}
|
|
#if WITH_APPLICATION_CORE
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformApplicationMisc::PreInit");
|
|
FPlatformApplicationMisc::PreInit();
|
|
}
|
|
#endif
|
|
|
|
// Keep track of start time.
|
|
GSystemStartTime = FDateTime::Now().ToString();
|
|
|
|
// Switch into executable's directory.
|
|
#if !defined(DISABLE_CWD_CHANGES) || DISABLE_CWD_CHANGES==0
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("IFileManager::Get().ProcessCommandLineOptions()");
|
|
// Now finish initializing the file manager after the command line is set up
|
|
IFileManager::Get().ProcessCommandLineOptions();
|
|
}
|
|
|
|
FPageAllocator::Get().LatchProtectedMode();
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("purgatorymallocproxy")))
|
|
{
|
|
FMemory::EnablePurgatoryTests();
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("poisonmallocproxy")))
|
|
{
|
|
FMemory::EnablePoisonTests();
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("BUILDMACHINE")))
|
|
{
|
|
GIsBuildMachine = true;
|
|
// propagate to subprocesses, especially because some - like ShaderCompileWorker - use DDC, for which this switch matters
|
|
FCommandLine::AddToSubprocessCommandLine(TEXT(" -buildmachine"), ECommandLineArgumentFlags::AllContexts);
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
#if PLATFORM_WINDOWS
|
|
|
|
// make sure that the log directory tree exists
|
|
IFileManager::Get().MakeDirectory( *FPaths::ProjectLogDir(), true );
|
|
|
|
// update the mini dump filename now that we have enough info to point it to the log folder even in installed builds
|
|
FCString::Strcpy(MiniDumpFilenameW, *IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FString::Printf(TEXT("%sunreal-v%i-%s.dmp"), *FPaths::ProjectLogDir(), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString())));
|
|
#endif
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformOutputDevices::SetupOutputDevices");
|
|
// Init logging to disk
|
|
FPlatformOutputDevices::SetupOutputDevices();
|
|
}
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FConfigCacheIni::InitializeConfigSystem");
|
|
LLM_SCOPE(ELLMTag::ConfigSystem);
|
|
// init config system
|
|
FConfigCacheIni::InitializeConfigSystem();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
int32 NumPreallocateNames = 0;
|
|
GConfig->GetInt(TEXT("NameTable"), TEXT("PreallocateNames"), NumPreallocateNames, GEditorIni);
|
|
int32 PreAllocateNameMB = 0;
|
|
GConfig->GetInt(TEXT("NameTable"), TEXT("PreallocateNameMemoryMB"), PreAllocateNameMB, GEditorIni);
|
|
if (NumPreallocateNames || PreAllocateNameMB)
|
|
{
|
|
FName::Reserve(PreAllocateNameMB * 1024 * 1024, NumPreallocateNames);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("ConfigUtilities::ApplyCVarsFromBootHotfix");
|
|
// Apply boot hotfixes
|
|
UE::ConfigUtilities::ApplyCVarsFromBootHotfix();
|
|
}
|
|
|
|
#if USE_IO_DISPATCHER
|
|
FModuleManager::Get().LoadModule("IoStoreOnDemandCore");
|
|
#if UE_WITH_IOSTOREONDEMAND
|
|
FModuleManager::Get().LoadModule("IoStoreOnDemand");
|
|
#endif
|
|
#endif
|
|
|
|
// Apply config driven presets
|
|
FTraceAuxiliary::InitializePresets(FCommandLine::Get());
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::IniSystemReady);
|
|
|
|
// Load "asap" plugin modules
|
|
IPluginManager& PluginManager = IPluginManager::Get();
|
|
IProjectManager& ProjectManager = IProjectManager::Get();
|
|
if (!ProjectManager.LoadModulesForProject(ELoadingPhase::EarliestPossible) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::EarliestPossible))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::EarliestPossiblePluginsLoaded);
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("FPlatformStackWalk::Init");
|
|
// Now that configs have been initialized, setup stack walking options
|
|
FPlatformStackWalk::Init();
|
|
}
|
|
|
|
CheckForPrintTimesOverride();
|
|
|
|
// Check whether the project or any of its plugins are missing or are out of date
|
|
#if UE_EDITOR && !IS_MONOLITHIC
|
|
if(!GIsBuildMachine && !FApp::IsUnattended() && FPaths::IsProjectFilePathSet() && !PluginManager.GetPreloadBinaries())
|
|
{
|
|
// Check all the plugins are present
|
|
if(!PluginManager.AreRequiredPluginsAvailable())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find the editor target
|
|
FString EditorTargetFileName;
|
|
FString DefaultEditorTarget;
|
|
bool bIsRunningExe = false;
|
|
GConfig->GetString(TEXT("/Script/BuildSettings.BuildSettings"), TEXT("DefaultEditorTarget"), DefaultEditorTarget, GEngineIni);
|
|
|
|
// If we have multiple targets for the same code, then try to find a target that matches the running executable. Otherwise
|
|
// we use the first matching target.
|
|
for (const FTargetInfo& Target : FDesktopPlatformModule::Get()->GetTargetsForProject(FPaths::GetProjectFilePath()))
|
|
{
|
|
if (Target.Type == EBuildTargetType::Editor)
|
|
{
|
|
const TCHAR* ArchitectureSuffix = FPlatformProcess::GetArchitectureSuffix();
|
|
|
|
FString ScratchEditorTargetFileName;
|
|
if (FPaths::IsUnderDirectory(Target.Path, FPlatformMisc::ProjectDir()))
|
|
{
|
|
ScratchEditorTargetFileName = FTargetReceipt::GetDefaultPath(FPlatformMisc::ProjectDir(), *Target.Name, FPlatformProcess::GetBinariesSubdirectory(), FApp::GetBuildConfiguration(), ArchitectureSuffix);
|
|
}
|
|
else if (FPaths::IsUnderDirectory(Target.Path, FPaths::EngineDir()))
|
|
{
|
|
ScratchEditorTargetFileName = FTargetReceipt::GetDefaultPath(*FPaths::EngineDir(), *Target.Name, FPlatformProcess::GetBinariesSubdirectory(), FApp::GetBuildConfiguration(), ArchitectureSuffix);
|
|
}
|
|
|
|
if (DoesCurrentExecutableMatch(ScratchEditorTargetFileName))
|
|
{
|
|
EditorTargetFileName = MoveTemp(ScratchEditorTargetFileName);
|
|
bIsRunningExe = true;
|
|
break;
|
|
}
|
|
else if (DefaultEditorTarget.Len() == 0 || Target.Name == DefaultEditorTarget)
|
|
{
|
|
if (EditorTargetFileName.Len() == 0)
|
|
{
|
|
EditorTargetFileName = MoveTemp(ScratchEditorTargetFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're not running the correct executable for the current target, and the listed executable exists, run that instead
|
|
if (!bIsRunningExe && LaunchCorrectEditorExecutable(EditorTargetFileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if we need to compile
|
|
bool bNeedCompile = false;
|
|
GConfig->GetBool(TEXT("/Script/UnrealEd.EditorLoadingSavingSettings"), TEXT("bForceCompilationAtStartup"), bNeedCompile, GEditorPerProjectIni);
|
|
if(FParse::Param(FCommandLine::Get(), TEXT("SKIPCOMPILE")) || FParse::Param(FCommandLine::Get(), TEXT("MULTIPROCESS")))
|
|
{
|
|
bNeedCompile = false;
|
|
}
|
|
if(!bNeedCompile)
|
|
{
|
|
// Check if any of the project or plugin modules are out of date, and the user wants to compile them.
|
|
TArray<FString> IncompatibleFiles;
|
|
ProjectManager.CheckModuleCompatibility(IncompatibleFiles);
|
|
|
|
TArray<FString> IncompatibleEngineFiles;
|
|
PluginManager.CheckModuleCompatibility(IncompatibleFiles, IncompatibleEngineFiles);
|
|
|
|
if (IncompatibleFiles.Num() > 0)
|
|
{
|
|
// Log the modules which need to be rebuilt
|
|
for (int Idx = 0; Idx < IncompatibleFiles.Num(); Idx++)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("Incompatible or missing module: %s"), *IncompatibleFiles[Idx]);
|
|
}
|
|
|
|
// Build the error message for the dialog box
|
|
FString ModulesList = TEXT("The following modules are missing or built with a different engine version:\n\n");
|
|
|
|
int NumModulesToDisplay = (IncompatibleFiles.Num() <= 20)? IncompatibleFiles.Num() : 15;
|
|
for (int Idx = 0; Idx < NumModulesToDisplay; Idx++)
|
|
{
|
|
ModulesList += FString::Printf(TEXT(" %s\n"), *IncompatibleFiles[Idx]);
|
|
}
|
|
if(IncompatibleFiles.Num() > NumModulesToDisplay)
|
|
{
|
|
ModulesList += FString::Printf(TEXT(" (+%d others, see log for details)\n"), IncompatibleFiles.Num() - NumModulesToDisplay);
|
|
}
|
|
|
|
// If we're running with -stdout, assume that we're a non interactive process and about to fail
|
|
if (FApp::IsUnattended() || FParse::Param(FCommandLine::Get(), TEXT("stdout")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If there are any engine modules that need building, force the user to build through the IDE
|
|
if(IncompatibleEngineFiles.Num() > 0)
|
|
{
|
|
FString CompileForbidden = ModulesList + TEXT("\nEngine modules cannot be compiled at runtime. Please build through your IDE.");
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *CompileForbidden, TEXT("Missing Modules"));
|
|
return false;
|
|
}
|
|
|
|
// Ask whether to compile before continuing
|
|
FString CompilePrompt = ModulesList + TEXT("\nWould you like to rebuild them now?");
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *CompilePrompt, *FString::Printf(TEXT("Missing %s Modules"), FApp::GetProjectName())) == EAppReturnType::Yes)
|
|
{
|
|
bNeedCompile = true;
|
|
}
|
|
else
|
|
{
|
|
FString ProceedWithoutCompilePrompt = ModulesList + TEXT("\nWould you like to proceed anyway? Code and content that depends on these modules may not work correctly. Enter at your own risk.");
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *ProceedWithoutCompilePrompt, *FString::Printf(TEXT("Missing %s Modules"), FApp::GetProjectName())) == EAppReturnType::No)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(EditorTargetFileName.Len() > 0 && !FPaths::FileExists(EditorTargetFileName))
|
|
{
|
|
// Prompt to compile. The target file isn't essential, but we
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *FString::Printf(TEXT("The %s file does not exist. Would you like to build the editor?"), *FPaths::GetCleanFilename(EditorTargetFileName)), TEXT("Missing target file")) == EAppReturnType::Yes)
|
|
{
|
|
bNeedCompile = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(16);
|
|
|
|
if(bNeedCompile)
|
|
{
|
|
// Try to compile it
|
|
FFeedbackContext *Context = (FFeedbackContext*)FDesktopPlatformModule::Get()->GetNativeFeedbackContext();
|
|
Context->BeginSlowTask(FText::FromString(TEXT("Starting build...")), true, true);
|
|
ECompilationResult::Type CompilationResult = ECompilationResult::Unknown;
|
|
bool bCompileResult = FDesktopPlatformModule::Get()->CompileGameProject(FPaths::RootDir(), FPaths::GetProjectFilePath(), Context, &CompilationResult);
|
|
Context->EndSlowTask();
|
|
|
|
// Check if we're running the wrong executable now
|
|
if(bCompileResult && LaunchCorrectEditorExecutable(EditorTargetFileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if we needed to modify engine files
|
|
if (!bCompileResult && CompilationResult == ECompilationResult::FailedDueToEngineChange)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Engine modules are out of date, and cannot be compiled while the engine is running. Please build through your IDE."), TEXT("Missing Modules"));
|
|
return false;
|
|
}
|
|
|
|
// Get a list of modules which are still incompatible
|
|
TArray<FString> StillIncompatibleFiles;
|
|
ProjectManager.CheckModuleCompatibility(StillIncompatibleFiles);
|
|
|
|
TArray<FString> StillIncompatibleEngineFiles;
|
|
PluginManager.CheckModuleCompatibility(StillIncompatibleFiles, StillIncompatibleEngineFiles);
|
|
|
|
if(!bCompileResult || StillIncompatibleFiles.Num() > 0)
|
|
{
|
|
for (int Idx = 0; Idx < StillIncompatibleFiles.Num(); Idx++)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("Still incompatible or missing module: %s"), *StillIncompatibleFiles[Idx]);
|
|
}
|
|
if (!FApp::IsUnattended())
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *FString::Printf(TEXT("%s could not be compiled. Try rebuilding from source manually."), FApp::GetProjectName()), TEXT("Error"));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Put the command line and config info into the suppression system (before plugins start loading)
|
|
FLogSuppressionInterface::Get().ProcessConfigAndCommandLine();
|
|
|
|
#if PLATFORM_IOS || PLATFORM_TVOS
|
|
// Now that the config system is ready, init the audio system.
|
|
[[IOSAppDelegate GetDelegate] InitializeAudioSession];
|
|
#endif
|
|
|
|
// Show log if wanted.
|
|
if (GLogConsole && FParse::Param(FCommandLine::Get(), TEXT("LOG")))
|
|
{
|
|
GLogConsole->Show(true);
|
|
}
|
|
|
|
|
|
// NOTE: This is the earliest place to init the online subsystems (via plugins)
|
|
// Code needs GConfigFile to be valid
|
|
// Must be after FThreadStats::StartThread();
|
|
// Must be before Render/RHI subsystem D3DCreate() for platform services that need D3D hooks like Steam
|
|
|
|
{
|
|
SCOPED_BOOT_TIMING("Load pre-init plugin modules");
|
|
UE_SCOPED_ENGINE_ACTIVITY(TEXT("Loading Plugins (PreInit)"));
|
|
|
|
// Load "pre-init" plugin modules
|
|
if (!ProjectManager.LoadModulesForProject(ELoadingPhase::PostConfigInit) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FEmbeddedCommunication::ForceTick(17);
|
|
|
|
// after the above has run we now have the REQUIRED set of engine .INIs (all of the other .INIs)
|
|
// that are gotten from .h files' config() are not requires and are dynamically loaded when the .u files are loaded
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Prompt the user for remote debugging?
|
|
bool bPromptForRemoteDebug = false;
|
|
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugging"), bPromptForRemoteDebug, GEngineIni);
|
|
bool bPromptForRemoteDebugOnEnsure = false;
|
|
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugOnEnsure"), bPromptForRemoteDebugOnEnsure, GEngineIni);
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUG")))
|
|
{
|
|
bPromptForRemoteDebug = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUGENSURE")))
|
|
{
|
|
bPromptForRemoteDebug = true;
|
|
bPromptForRemoteDebugOnEnsure = true;
|
|
}
|
|
|
|
FPlatformMisc::SetShouldPromptForRemoteDebugging(bPromptForRemoteDebug);
|
|
FPlatformMisc::SetShouldPromptForRemoteDebugOnEnsure(bPromptForRemoteDebugOnEnsure);
|
|
|
|
// Feedback context.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("WARNINGSASERRORS")))
|
|
{
|
|
GWarn->TreatWarningsAsErrors = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("ERRORSASWARNINGS")))
|
|
{
|
|
GWarn->TreatErrorsAsWarnings = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("SILENT")))
|
|
{
|
|
GIsSilent = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("RUNNINGUNATTENDEDSCRIPT")))
|
|
{
|
|
GIsRunningUnattendedScript = true;
|
|
}
|
|
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// Print all initial startup logging
|
|
FApp::PrintStartupLogMessages();
|
|
|
|
// if a logging build, clear out old log files. Avoid races when multiple processes are running at once.
|
|
#if !NO_LOGGING
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("MULTIPROCESS")))
|
|
{
|
|
FMaintenance::DeleteOldLogs();
|
|
}
|
|
#endif
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
{
|
|
SCOPED_BOOT_TIMING("FApp::InitializeSession");
|
|
FApp::InitializeSession();
|
|
}
|
|
#endif
|
|
|
|
#if PLATFORM_USE_PLATFORM_FILE_MANAGED_STORAGE_WRAPPER
|
|
// Delay initialization of FPersistentStorageManager to a point where GConfig is initialized
|
|
FPersistentStorageManager::Get().Initialize();
|
|
#endif
|
|
|
|
// Checks.
|
|
check(sizeof(uint8) == 1);
|
|
check(sizeof(int8) == 1);
|
|
check(sizeof(uint16) == 2);
|
|
check(sizeof(uint32) == 4);
|
|
check(sizeof(uint64) == 8);
|
|
check(sizeof(ANSICHAR) == 1);
|
|
|
|
#if PLATFORM_TCHAR_IS_4_BYTES
|
|
check(sizeof(TCHAR) == 4);
|
|
#else
|
|
check(sizeof(TCHAR) == 2);
|
|
#endif
|
|
|
|
check(sizeof(int16) == 2);
|
|
check(sizeof(int32) == 4);
|
|
check(sizeof(int64) == 8);
|
|
check(sizeof(bool) == 1);
|
|
check(sizeof(float) == 4);
|
|
check(sizeof(double) == 8);
|
|
|
|
// Init list of common colors.
|
|
GColorList.CreateColorMap();
|
|
|
|
bool bForceSmokeTests = false;
|
|
GConfig->GetBool(TEXT("AutomationTesting"), TEXT("bForceSmokeTests"), bForceSmokeTests, GEngineIni);
|
|
bForceSmokeTests |= FParse::Param(FCommandLine::Get(), TEXT("bForceSmokeTests"));
|
|
FAutomationTestFramework::Get().SetForceSmokeTests(bForceSmokeTests);
|
|
|
|
FEmbeddedCommunication::ForceTick(18);
|
|
|
|
// Init other systems.
|
|
{
|
|
SCOPED_BOOT_TIMING("FCoreDelegates::OnInit.Broadcast");
|
|
FCoreDelegates::OnInit.Broadcast();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (FPIEPreviewDeviceModule::IsRequestingPreviewDevice())
|
|
{
|
|
FPIEPreviewDeviceModule* PIEPreviewDeviceProfileSelectorModule = FModuleManager::LoadModulePtr<FPIEPreviewDeviceModule>("PIEPreviewDeviceProfileSelector");
|
|
if (PIEPreviewDeviceProfileSelectorModule)
|
|
{
|
|
Scalability::ChangeScalabilityPreviewPlatform(PIEPreviewDeviceProfileSelectorModule->GetPreviewPlatformName(), GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FEmbeddedCommunication::ForceTick(19);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FEngineLoop::AppPreExit( )
|
|
{
|
|
SCOPED_BOOT_TIMING("AppPreExit");
|
|
|
|
UE_LOG(LogExit, Log, TEXT("Preparing to exit.") );
|
|
|
|
FCoreDelegates::OnPreExit.Broadcast();
|
|
|
|
#if WITH_ENGINE
|
|
if (FString(FCommandLine::Get()).Contains(TEXT("CreatePak")) && GetDerivedDataCache())
|
|
{
|
|
// if we are creating a Pak, we need to make sure everything is done and written before we exit
|
|
UE_LOG(LogInit, Display, TEXT("Closing DDC Pak File."));
|
|
GetDerivedDataCacheRef().WaitForQuiescence(true);
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
FRemoteConfig::Flush();
|
|
#endif
|
|
|
|
FCoreDelegates::OnExit.Broadcast();
|
|
|
|
// FGenericRHIGPUFence uses GFrameNumberRenderThread to tell when a frame is finished. If such an object is added in the last frame before we
|
|
// exit, it will never be "signaled", since nothing ever increments GFrameNumberRenderThread again. This can lead to deadlocks in code
|
|
// which uses async tasks to wait until resources are safe to be deleted (for example, FMediaTextureResource).
|
|
// To avoid this, we set the frame number to the maximum possible value here, before waiting for the thread pool to die. It's safe to do so
|
|
// because FlushRenderingCommands() is called multiple times on exit before reaching this point, so there's no way the render thread has any
|
|
// more frames in flight.
|
|
GFrameNumberRenderThread = MAX_uint32;
|
|
|
|
if (GIOThreadPool != nullptr)
|
|
{
|
|
GIOThreadPool->Destroy();
|
|
}
|
|
|
|
#if (WITH_VERSE_VM || defined(__INTELLISENSE__)) && WITH_COREUOBJECT
|
|
Verse::VerseVM::Shutdown();
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
if ( GShaderCompilingManager )
|
|
{
|
|
delete GShaderCompilingManager;
|
|
GShaderCompilingManager = nullptr;
|
|
}
|
|
if(GShaderCompilerStats)
|
|
{
|
|
delete GShaderCompilerStats;
|
|
GShaderCompilerStats = nullptr;
|
|
}
|
|
|
|
#if WITH_ODSC
|
|
if (GODSCManager)
|
|
{
|
|
delete GODSCManager;
|
|
GODSCManager = nullptr;
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
#if WITH_COREUOBJECT
|
|
// Shutdown the PackageResourceManager in AppPreExit for programs that do not call FEngineLoop::Exit
|
|
IPackageResourceManager::Shutdown();
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void FEngineLoop::AppExit()
|
|
{
|
|
static bool bCalledOnce;
|
|
if (bCalledOnce)
|
|
{
|
|
return;
|
|
}
|
|
bCalledOnce = true;
|
|
|
|
// when compiled WITH_ENGINE, this will happen in FEngineLoop::Exit()
|
|
#if !WITH_ENGINE
|
|
#if STATS
|
|
FThreadStats::StopThread();
|
|
#endif
|
|
FTaskGraphInterface::Shutdown();
|
|
#endif // WITH_ENGINE
|
|
|
|
UE_LOG(LogExit, Log, TEXT("Exiting."));
|
|
|
|
#if WITH_APPLICATION_CORE
|
|
FPlatformApplicationMisc::TearDown();
|
|
#endif
|
|
FPlatformMisc::PlatformTearDown();
|
|
|
|
if (GConfig)
|
|
{
|
|
GConfig->Exit();
|
|
delete GConfig;
|
|
GConfig = nullptr;
|
|
}
|
|
|
|
if( GLog )
|
|
{
|
|
GLog->TearDown();
|
|
}
|
|
|
|
FTextLocalizationManager::TearDown();
|
|
FInternationalization::TearDown();
|
|
|
|
FTraceAuxiliary::Shutdown();
|
|
}
|
|
|
|
void FEngineLoop::PostInitRHI()
|
|
{
|
|
#if WITH_ENGINE
|
|
TArray<uint32> PixelFormatByteWidth;
|
|
PixelFormatByteWidth.AddUninitialized(PF_MAX);
|
|
for (int i = 0; i < PF_MAX; i++)
|
|
{
|
|
PixelFormatByteWidth[i] = GPixelFormats[i].BlockBytes;
|
|
}
|
|
RHIPostInit(PixelFormatByteWidth);
|
|
|
|
if (FApp::CanEverRender())
|
|
{
|
|
// perform an early check of hardware capabilities
|
|
EShaderPlatform ShaderPlatform = GMaxRHIShaderPlatform;
|
|
|
|
const UE::StereoRenderUtils::FStereoShaderAspects Aspects(ShaderPlatform);
|
|
|
|
UE::StereoRenderUtils::LogISRInit(Aspects);
|
|
UE::StereoRenderUtils::VerifyISRConfig(Aspects, ShaderPlatform);
|
|
|
|
// Initialize system textures
|
|
ENQUEUE_RENDER_COMMAND(InitializeSystemTextures)([](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
GetRendererModule().InitializeSystemTextures(RHICmdList);
|
|
});
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void FEngineLoop::PreInitHMDDevice()
|
|
{
|
|
#if WITH_ENGINE && !UE_SERVER
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("nohmd")) && !FParse::Param(FCommandLine::Get(), TEXT("emulatestereo")))
|
|
{
|
|
// Get a list of modules that implement this feature
|
|
FName Type = IHeadMountedDisplayModule::GetModularFeatureName();
|
|
IModularFeatures& ModularFeatures = IModularFeatures::Get();
|
|
TArray<IHeadMountedDisplayModule*> HMDModules = ModularFeatures.GetModularFeatureImplementations<IHeadMountedDisplayModule>(Type);
|
|
|
|
// Check whether the user passed in an explicit HMD module on the command line
|
|
FString ExplicitHMDName;
|
|
bool bUseExplicitHMDName = FParse::Value(FCommandLine::Get(), TEXT("hmd="), ExplicitHMDName);
|
|
|
|
// Iterate over modules, checking ExplicitHMDName and calling PreInit
|
|
for (auto HMDModuleIt = HMDModules.CreateIterator(); HMDModuleIt; ++HMDModuleIt)
|
|
{
|
|
IHeadMountedDisplayModule* HMDModule = *HMDModuleIt;
|
|
|
|
|
|
bool bUnregisterHMDModule = false;
|
|
if (bUseExplicitHMDName)
|
|
{
|
|
TArray<FString> HMDAliases;
|
|
HMDModule->GetModuleAliases(HMDAliases);
|
|
HMDAliases.Add(HMDModule->GetModuleKeyName());
|
|
|
|
bUnregisterHMDModule = true;
|
|
for (const FString& HMDModuleName : HMDAliases)
|
|
{
|
|
if (ExplicitHMDName.Equals(HMDModuleName, ESearchCase::IgnoreCase))
|
|
{
|
|
bUnregisterHMDModule = !HMDModule->PreInit();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bUnregisterHMDModule = !HMDModule->PreInit();
|
|
}
|
|
|
|
if (bUnregisterHMDModule)
|
|
{
|
|
// Unregister modules which don't match ExplicitHMDName, or which fail PreInit
|
|
ModularFeatures.UnregisterModularFeature(Type, HMDModule);
|
|
}
|
|
}
|
|
// Note we do not disable or warn here if no HMD modules matched ExplicitHMDName, as not all HMD plugins have been loaded yet.
|
|
}
|
|
#endif // #if WITH_ENGINE && !UE_SERVER
|
|
}
|
|
|
|
void FPreInitContext::Cleanup()
|
|
{
|
|
#if WITH_ENGINE && !UE_SERVER
|
|
SlateRenderer = nullptr;
|
|
#endif // WITH_ENGINE && !UE_SERVER
|
|
|
|
delete SlowTaskPtr;
|
|
SlowTaskPtr = nullptr;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|