// Copyright Epic Games, Inc. All Rights Reserved. #include "LaunchEngineLoop.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 "Misc/FileHelper.h" #include "Internationalization/TextLocalizationManagerGlobals.h" #include "Logging/LogSuppressionInterface.h" #include "Async/TaskGraphInterfaces.h" #include "Async/Fundamental/Scheduler.h" #include "MemPro/MemProProfiler.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/ScopeExit.h" #include "HAL/PlatformFileManager.h" #include "HAL/FileManagerGeneric.h" #include "HAL/ExceptionHandling.h" #include "HAL/IPlatformFileManagedStorageWrapper.h" #include "Stats/StatsMallocProfilerProxy.h" #include "Trace/Trace.inl" #include "ProfilingDebugging/TraceAuxiliary.h" #include "ProfilingDebugging/CsvProfiler.h" #include "ProfilingDebugging/BootProfiling.h" #if WITH_ENGINE #include "HAL/PlatformSplash.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 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" #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 "Editor/EditorEngine.h" #include "UnrealEdMisc.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #include "Settings/EditorExperimentalSettings.h" #include "PIEPreviewDeviceProfileSelectorModule.h" #include "AssetCompilingManager.h" #include "Serialization/BulkDataRegistry.h" #include "ShaderCompiler.h" #include "Virtualization/VirtualizationSystem.h" #if PLATFORM_WINDOWS #include "Windows/AllowWindowsPlatformTypes.h" #include #include "Windows/HideWindowsPlatformTypes.h" #endif #endif //WITH_EDITOR #if WITH_ENGINE #include "Engine/GameEngine.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" #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 "MeshCardRepresentation.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 "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 "RenderGraphBuilder.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 #include "STaskGraph.h" #include "IProfilerServiceModule.h" #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 #include "Windows/HideWindowsPlatformTypes.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" #include "ProfilingDebugging/TracingProfiler.h" #endif #if defined(WITH_LAUNCHERCHECK) && WITH_LAUNCHERCHECK #include "ILauncherCheckModule.h" #endif #include #if WITH_COREUOBJECT #ifndef USE_LOCALIZED_PACKAGE_CACHE #define USE_LOCALIZED_PACKAGE_CACHE 1 #endif #else #define USE_LOCALIZED_PACKAGE_CACHE 0 #endif #ifndef RHI_COMMAND_LIST_DEBUG_TRACES #define RHI_COMMAND_LIST_DEBUG_TRACES 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 int32 GUseDisregardForGCOnDedicatedServers = 1; static FAutoConsoleVariableRef CVarUseDisregardForGCOnDedicatedServers( TEXT("gc.UseDisregardForGCOnDedicatedServers"), GUseDisregardForGCOnDedicatedServers, TEXT("If false, DisregardForGC will be disabled for dedicated servers."), ECVF_Default ); static TAutoConsoleVariable 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 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 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 TickWithSlate; public: FExecuteConcurrentWithSlateTickTask(TFunction 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; }; // Pipe output to std output // This enables UBT to collect the output for it's own use class FOutputDeviceStdOutput : public FOutputDevice { public: FOutputDeviceStdOutput() { #if PLATFORM_WINDOWS bIsConsoleOutput = IsStdoutAttachedToConsole() && !FParse::Param(FCommandLine::Get(), TEXT("GenericConsoleOutput")); #endif if (FParse::Param(FCommandLine::Get(), TEXT("AllowStdOutLogVerbosity"))) { AllowedLogVerbosity = ELogVerbosity::Log; } if (FParse::Param(FCommandLine::Get(), TEXT("FullStdOutLogOutput"))) { AllowedLogVerbosity = ELogVerbosity::All; } if (stdout == nullptr) { AllowedLogVerbosity = ELogVerbosity::NoLogging; } } virtual bool CanBeUsedOnAnyThread() const override { return true; } virtual bool CanBeUsedOnPanicThread() const override { return true; } virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override { if (Verbosity <= AllowedLogVerbosity) { FString line = FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes); #if PLATFORM_WINDOWS if (bIsConsoleOutput) { line.AppendChar(TEXT('\n')); WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), *line, line.Len(), NULL, NULL); return; } // fall through to standard printf path #endif #if PLATFORM_TCHAR_IS_CHAR16 printf("%s\n", TCHAR_TO_UTF8(*line)); #elif PLATFORM_USE_LS_SPEC_FOR_WIDECHAR // printf prints wchar_t strings just fine with %ls, while mixing printf()/wprintf() is not recommended (see https://stackoverflow.com/questions/8681623/printf-and-wprintf-in-single-c-code) printf("%ls\n", *line); #else wprintf(TEXT("%s\n"), *line); #endif fflush(stdout); } } private: ELogVerbosity::Type AllowedLogVerbosity = ELogVerbosity::Display; bool bIsConsoleOutput = false; #if PLATFORM_WINDOWS static bool IsStdoutAttachedToConsole() { HANDLE StdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); if (StdoutHandle != INVALID_HANDLE_VALUE) { DWORD FileType = GetFileType(StdoutHandle); if (FileType == FILE_TYPE_CHAR) { return true; } } return false; } #endif }; // Exits the game/editor if any of the specified phrases appears in the log output class FOutputDeviceTestExit : public FOutputDevice { TArray ExitPhrases; FString* FoundExitPhrase = nullptr; public: FOutputDeviceTestExit(TArray&& 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_APPLICATION_CORE static TUniquePtr GScopedLogConsole; #endif static TUniquePtr GScopedStdOut; static TUniquePtr GScopedTestExit; #if WITH_ENGINE static void StopRHIThread() { #if HAS_GPU_STATS FRealtimeGPUProfiler::SafeRelease(); #endif // Stop the RHI Thread (using IsRHIThreadRunning() is unreliable since RT may be stopped) if (FTaskGraphInterface::IsRunning() && FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::RHIThread)) { DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread Finish"), STAT_WaitForRHIThreadFinish, STATGROUP_TaskGraphTasks); FGraphEventRef QuitTask = TGraphTask::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(ENamedThreads::RHIThread); FTaskGraphInterface::Get().WaitUntilTaskCompletes(QuitTask, ENamedThreads::GameThread_Local); } } #endif /** * 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(); GLog->AddOutputDevice(GScopedStdOut.Get()); } bool ParseGameProjectFromCommandLine(const TCHAR* InCmdLine, FString& OutProjectFilePath, FString& OutGameName) { const TCHAR *CmdLine = InCmdLine; 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(); // OutProjectFilePath = TEXT(""); OutGameName = TEXT(""); if ( FirstCommandLineToken.Len() && !FirstCommandLineToken.StartsWith(TEXT("-")) ) { // The first command line argument could be the project file if it exists or the game name if not launching with a project file const FString ProjectFilePath = FString(FirstCommandLineToken); if ( FPaths::GetExtension(ProjectFilePath) == FProjectDescriptor::GetExtension() ) { OutProjectFilePath = FirstCommandLineToken; // Here we derive the game name from the project file OutGameName = FPaths::GetBaseFilename(OutProjectFilePath); return true; } else if (FPaths::IsRelative(FirstCommandLineToken) && FPlatformProperties::IsMonolithicBuild() == false) { // Full game name is assumed to be the first token OutGameName = MoveTemp(FirstCommandLineToken); // 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, false); } 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()) { #if PLATFORM_WINDOWS // GetFilenameOnDisk on Windows will resolve directory junctions and resolving those here has negative consequences // for workflows that use a junction at their root (eg: p4 gets confused about paths and operations fail). // There is a way to get a case-accurate path on Windows without resolving directory junctions, but it is slow. // We can use it here for this one-off situation without causing all uses of GetFilenameOnDisk to be slower. WIN32_FIND_DATAW Data; FString ProjectFilePath = FPaths::GetProjectFilePath(); for (FStringView PendingFix = ProjectFilePath; PendingFix.Len() > 0;) { const FStringView CurrentPathComponent = FPathViews::GetCleanFilename(PendingFix); // Skip over all segments that are either empty or contain relative transforms or start with the volume separator, they should remain as-is const bool bIsIgnoredSegment = CurrentPathComponent.IsEmpty() || CurrentPathComponent.Equals(TEXTVIEW(".")) || CurrentPathComponent.Equals(TEXTVIEW("..")) || CurrentPathComponent.EndsWith(TEXT(':')); if (!bIsIgnoredSegment) { // Temporarily null-terminate the current path component for the system call TCHAR Terminator = TEXT('\0'); Swap(ProjectFilePath.GetCharArray()[PendingFix.Len()], Terminator); HANDLE Handle = FindFirstFileW(*ProjectFilePath, &Data); if (Handle != INVALID_HANDLE_VALUE) { check(CurrentPathComponent.Equals(Data.cFileName, ESearchCase::IgnoreCase)); FCString::Strncpy(GetData(ProjectFilePath) + PendingFix.Len() - CurrentPathComponent.Len(), Data.cFileName, CurrentPathComponent.Len() + 1); FindClose(Handle); } Swap(ProjectFilePath.GetCharArray()[PendingFix.Len()], Terminator); } PendingFix.LeftChopInline(CurrentPathComponent.Len() + 1); } FPaths::SetProjectFilePath(ProjectFilePath); #else FPaths::SetProjectFilePath(IFileManager::Get().GetFilenameOnDisk(*FPaths::GetProjectFilePath())); #endif } } 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); } } // 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 (!NetworkPlatformFile) { IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine); if (PlatformFile) { CurrentPlatformFile = PlatformFile; FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile); } 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& PrevExpansions, bool& bChanged) { bChanged = false; if (const FConfigSection* Section = Config.Find(TEXT("CommandLineAliases"))) { TArray 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::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& 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 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 folder exists. If it doesn't, it could instead be 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 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::OnFileOpenedForReadFromPakFile.AddRaw(this, &FFileInPakFileHistoryHelper::OnFileOpenedForRead); } ~FFileInPakFileHistoryHelper() { FCoreDelegates::OnFileOpenedForReadFromPakFile.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 FileInPakFileHistoryHelper; #endif // !UE_BUILD_SHIPPING void RecordFileReadsFromPaks() { #if !UE_BUILD_SHIPPING FileInPakFileHistoryHelper = MakeUnique(); #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 { } 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(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 PLATFORM_WINDOWS && !UE_BUILD_SHIPPING const uint32 ProcessId = (uint32)GetCurrentProcessId(); float ProcessUsageFraction = 0.f, IdleUsageFraction = 0.f; FWindowsPlatformProcess::GetPerFrameProcessorUsage(ProcessId, ProcessUsageFraction, IdleUsageFraction); CSV_CUSTOM_STAT_GLOBAL(CPUUsage_Process, ProcessUsageFraction, ECsvCustomStatOp::Set); CSV_CUSTOM_STAT_GLOBAL(CPUUsage_Idle, IdleUsageFraction, ECsvCustomStatOp::Set); #endif } #if CSV_PROFILER_ALLOW_DEBUG_FEATURES // Handle CsvExecCmds for this frame if (GEngine && GWorld) { TArray 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(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 } } static void UpdateCoreCsvStats_EndFrame() { if (!IsRunningDedicatedServer()) { CSV_CUSTOM_STAT_GLOBAL(RenderThreadTime, FPlatformTime::ToMilliseconds(GRenderThreadTime), ECsvCustomStatOp::Set); CSV_CUSTOM_STAT_GLOBAL(GameThreadTime, FPlatformTime::ToMilliseconds(GGameThreadTime), ECsvCustomStatOp::Set); CSV_CUSTOM_STAT_GLOBAL(GPUTime, FPlatformTime::ToMilliseconds(GGPUFrameTime), ECsvCustomStatOp::Set); if (IsRunningRHIInSeparateThread()) { CSV_CUSTOM_STAT_GLOBAL(RHIThreadTime, FPlatformTime::ToMilliseconds(GRHIThreadTime), ECsvCustomStatOp::Set); } if (GInputLatencyTime > 0) { CSV_CUSTOM_STAT_GLOBAL(InputLatencyTime, FPlatformTime::ToMilliseconds64(GInputLatencyTime), ECsvCustomStatOp::Set); } FPlatformMemoryStats MemoryStats = FPlatformMemory::GetStats(); 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_GLOBAL(MemoryFreeMB, PhysicalMBFree, ECsvCustomStatOp::Set); #if !UE_BUILD_SHIPPING 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); #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(); } #if WITH_EDITOR static void ShaderAutogenInit() { // Force generation of the autogen files for the host platform FShaderCompileUtilities::GenerateBrdfHeaders(GMaxRHIShaderPlatform); // also do this for all active target platforms (e.g. when cooking) ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); if (TPM) { const TArray& Platforms = TPM->GetActiveTargetPlatforms(); for (int32 Index = 0; Index < Platforms.Num(); ++Index) { TArray DesiredShaderFormats; checkf(Platforms[Index], TEXT("Null platform on the list of active platforms!")); Platforms[Index]->GetAllTargetedShaderFormats(DesiredShaderFormats); for (int32 FormatIndex = 0; FormatIndex < DesiredShaderFormats.Num(); ++FormatIndex) { FShaderCompileUtilities::GenerateBrdfHeaders(DesiredShaderFormats[FormatIndex]); } } } // also do this for the editor mobile preview EShaderPlatform MobilePreviewShaderPlatform = GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1]; if (MobilePreviewShaderPlatform != SP_NumPlatforms) { FShaderCompileUtilities::GenerateBrdfHeaders(MobilePreviewShaderPlatform); } } #endif // WITH_EDITOR 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 { GEnginePreInitPreStartupScreenEndTime = FPlatformTime::Seconds(); }; FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::StartOfEnginePreInit); SCOPED_BOOT_TIMING("FEngineLoop::PreInitPreStartupScreen"); // GLog is initialized lazily and its default master 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 master thread. Make this the master thread // until initialization is far enough along to try to start a dedicated master thread. if (GLog) { GLog->SetCurrentThreadAsMasterThread(); } // 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(); if (FParse::Param(CmdLine, TEXT("UTF8Output"))) { FPlatformMisc::SetUTF8Output(); } #if !UE_BUILD_SHIPPING if (FParse::Param(CmdLine, TEXT("IgnoreDebugger"))) { GIgnoreDebugger = true; } #endif // !UE_BUILD_SHIPPING // Switch into executable's directory. FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir(); // 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()) { // 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 // 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. GMalloc = FStatsMallocProfilerProxy::Get(); } #endif // STATS #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(FPlatformApplicationMisc::CreateConsoleOutputDevice()); } #endif // Always enable the backlog so we get all messages, we will disable and clear it in the game // as soon as we determine whether GIsEditor == false GLog->EnableBacklog(true); // Try to start the dedicated master thread now that the command line is available. if (!FParse::Param(FCommandLine::Get(), TEXT("NoLogThread"))) { GLog->TryStartDedicatedMasterThread(); } // 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 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 ExitPhrasesList; if (ExitPhrases.ParseIntoArray(ExitPhrasesList, TEXT("+"), true) > 0) { GScopedTestExit = MakeUnique(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; GMalloc = FMallocFrameProfiler::OverrideIfEnabled(GMalloc); } #endif // !UE_BUILD_SHIPPING // Switch into executable's directory (may be required by some of the platform file overrides) FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir(); // 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(); } } } } // 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 } // 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 GeneratedConfigDir as nullptr instead of FPaths::GeneratedConfigDir() so that saved directory is not cached before -saveddir argument can be added const TCHAR* GeneratedConfigDir = nullptr; FConfigCacheIni::LoadExternalIniFile(CommandLineAliasesConfigFile, TEXT("CommandLineAliases"), nullptr, *FPaths::Combine(FPaths::ProjectDir(), TEXT("Config")), /*bIsBaseIniName=*/ false, /*Platform=*/ nullptr, /*bForceReload=*/ false, /*bWriteDestIni=*/ false, /*bAllowGeneratedIniWhenCooked=*/ true, GeneratedConfigDir); } TArray PrevAliasExpansions; TArray 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 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::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; const SIZE_T CommandLineSize = FCString::Strlen(CmdLine) + 1; TCHAR* CommandLineCopy = new TCHAR[CommandLineSize]; FCString::Strcpy(CommandLineCopy, CommandLineSize, CmdLine); const TCHAR* ParsedCmdLine = CommandLineCopy; FString Token = FParse::Token(ParsedCmdLine, 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 Token.TrimStartAndEndInline(); auto IsModeSelected = [&bHasCommandletToken, &bHasEditorToken, &bIsRegularClient, &bIsRunningAsDedicatedServer]() { return bHasCommandletToken || bHasEditorToken || bIsRegularClient || bIsRunningAsDedicatedServer; }; #if UE_EDITOR || WITH_ENGINE || WITH_EDITOR const TCHAR* CommandletCommandLine = nullptr; auto SetIsRunningAsCommandlet = [&CommandletCommandLine, &ParsedCmdLine, &bHasCommandletToken, &bIsRunningAsDedicatedServer, &bHasEditorToken, &bIsRegularClient, &IsModeSelected] (FStringView CommandletName) { checkf(!IsModeSelected(), TEXT("SetIsRunningAsCommandlet should not be called after mode has been selected.")); bHasCommandletToken = true; GIsClient = true; GIsServer = true; #if WITH_EDITORONLY_DATA GIsEditor = true; #endif #if STATS // Leave the stats enabled. if (!FStats::EnabledForCommandlet()) { FThreadStats::MasterDisableForever(); } #endif CommandletCommandLine = ParsedCmdLine; #if WITH_EDITOR if (CommandletName == TEXTVIEW("cookcommandlet")) { PRIVATE_GIsRunningCookCommandlet = true; } #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]() { checkf(!IsModeSelected(), TEXT("SetIsRunningAsRegularClient should not be called after mode has been selected.")); bIsRegularClient = true; 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 WITH_EDITOR if (GIsUCCMakeStandaloneHeaderGenerator) { // Header generation is treated as a commandlet checkf(!IsRunningDedicatedServer(), TEXT("GIsUCCMakeStandaloneHeaderGenerator is not supported in a ServerOnly configuration, or with -server on commandline.")); Token = TEXT("GIsUCCMakeStandaloneHeaderGenerator"); SetIsRunningAsCommandlet(Token); } else #endif { if (IsRunningDedicatedServer()) { SetIsRunningAsDedicatedServer(); } } #if UE_EDITOR || WITH_ENGINE || WITH_EDITOR TArray NonSwitchTokens; TArray Switches; UCommandlet::ParseCommandLine(CommandLineCopy, NonSwitchTokens, Switches); if (!IsModeSelected()) { for (const FString& ParsedToken : NonSwitchTokens) { if (ParsedToken.EndsWith(TEXT("Commandlet"))) { Token = ParsedToken; Token.TrimStartAndEndInline(); SetIsRunningAsCommandlet(Token); break; } } if (!bHasCommandletToken) { for (const FString& ParsedSwitch : Switches) { if (ParsedSwitch.StartsWith(TEXT("RUN="))) { Token = ParsedSwitch.RightChop(4); Token.TrimStartAndEndInline(); if (!Token.EndsWith(TEXT("Commandlet"))) { Token += TEXT("Commandlet"); } SetIsRunningAsCommandlet(Token); break; } } } } #endif // UE_EDITOR || WITH_ENGINE // 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) { // 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 FString NormalizedToken = Token; FPaths::NormalizeFilename(NormalizedToken); const bool bFirstTokenIsGameName = (FApp::HasProjectName() && Token == FApp::GetProjectName()); const bool bFirstTokenIsGameProjectFilePath = (FPaths::IsProjectFilePathSet() && NormalizedToken == FPaths::GetProjectFilePath()); const bool bFirstTokenIsGameProjectFileShortName = (FPaths::IsProjectFilePathSet() && Token == FPaths::GetCleanFilename(FPaths::GetProjectFilePath())); if (bFirstTokenIsGameName || bFirstTokenIsGameProjectFilePath || bFirstTokenIsGameProjectFileShortName) { // The first item on command line was the game name, ignore it and get the next item for the Token FString RemainingCommandline = ParsedCmdLine; FCString::Strcpy(CommandLineCopy, CommandLineSize, *RemainingCommandline); ParsedCmdLine = CommandLineCopy; // Set a new command-line that doesn't include the game name as the first argument FCommandLine::Set(ParsedCmdLine); Token = FParse::Token(ParsedCmdLine, 0); Token.TrimStartInline(); // if the next token is a project file, then we skip it (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) while (FPaths::GetExtension(Token) == FProjectDescriptor::GetExtension()) { Token = FParse::Token(ParsedCmdLine, 0); Token.TrimStartInline(); } if (bFirstTokenIsGameProjectFilePath || bFirstTokenIsGameProjectFileShortName) { // 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 (Token == TEXT("-GAME") || Token == TEXT("-SERVER")) { // This isn't necessarily pretty, but many requests have been made to allow // UnrealEditor.exe -game // or // UnrealEditor.exe -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 RemainingCommandline = ParsedCmdLine; RemainingCommandline.TrimStartInline(); RemainingCommandline += FString::Printf(TEXT(" %s"), *Token); FCommandLine::Set(*RemainingCommandline); } #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 (Token == 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 // "-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")); // 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 FPlatformFileManager::Get().GetPlatformFile().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 !IS_PROGRAM if (FApp::HasProjectName()) { // Tell the module manager what the game binaries folder is const FString ProjectBinariesDirectory = FPaths::Combine(FPlatformMisc::ProjectDir(), 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()); #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); } FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::TaskGraphSystemReady); #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 (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 NumThreadsInLargeThreadPool = FMath::Max(FPlatformMisc::NumberOfCoresIncludingHyperthreads() - 2, 2); 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 extern CORE_API int32 GUseNewTaskBackend; if (!GUseNewTaskBackend) { GLargeThreadPool = FQueuedThreadPool::Allocate(); } else { GLargeThreadPool = new FQueuedLowLevelThreadPool(); } // TaskGraph has it's HP threads slightly below normal, we want to be below the taskgraph HP threads to avoid interfering with the game-thread. verify(GLargeThreadPool->Create(NumThreadsInLargeThreadPool, StackSize, TPri_BelowNormal, TEXT("LargeThreadPool"))); // 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 { extern CORE_API int32 GUseNewTaskBackend; if (!GUseNewTaskBackend) { GThreadPool = FQueuedThreadPool::Allocate(); int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn(); // we are only going to give dedicated servers one pool thread if (FPlatformProperties::IsServerOnly()) { NumThreadsInThreadPool = 1; } verify(GThreadPool->Create(NumThreadsInThreadPool, StackSize, TPri_SlightlyBelowNormal, TEXT("ThreadPool"))); } 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"))); } } #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 FCoreDelegates::OnBeginFrame.AddStatic(UpdateCoreCsvStats_BeginFrame); FCoreDelegates::OnEndFrame.AddStatic(UpdateCoreCsvStats_EndFrame); 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); return 1; #endif //WITH_EDITOR } #endif // WITH_ENGINE // If we're not in the editor stop collecting the backlog now that we know if (!GIsEditor) { GLog->EnableBacklog(false); } #if WITH_ENGINE && TRACING_PROFILER PRAGMA_DISABLE_DEPRECATION_WARNINGS FTracingProfiler::Get()->Init(); PRAGMA_ENABLE_DEPRECATION_WARNINGS #endif #if WITH_ENGINE && FRAMEPRO_ENABLED FFrameProProfiler::Initialize(); #endif // FRAMEPRO_ENABLED // Start the application { SCOPED_BOOT_TIMING("AppInit"); if (!AppInit()) { return 1; } } 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(); } #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); #endif FEmbeddedCommunication::ForceTick(2); #if WITH_ENGINE // allow for game explorer processing (including parental controls) and firewalls installation if (!FPlatformMisc::CommandLineCommands()) { FPlatformMisc::RequestExit(false); } #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 bool bIsLateCommandletToken = false; if (!IsModeSelected()) { //@hack: We need to set these before calling StaticLoadClass so all required data gets loaded for the commandlets. TGuardValue ScopedGIsClient(GIsClient, true); TGuardValue ScopedGIsServer(GIsServer, true); #if WITH_EDITOR TGuardValue ScopedGIsEditor(GIsEditor, true); #endif // WITH_EDITOR TGuardValue ScopedGIsRunningCommandlet(PRIVATE_GIsRunningCommandlet, true); bool bIsPossiblyCommandletName = Token.Len() && !Token.Contains(TEXT("-")); if (bIsPossiblyCommandletName) { UClass* TempCommandletClass = FindFirstObject(*(Token + TEXT("Commandlet")), 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.")); Token += TEXT("Commandlet"); bIsLateCommandletToken = true; } } } if (bIsLateCommandletToken) { SetIsRunningAsCommandlet(Token); } // 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; #if !(UE_BUILD_SHIPPING && WITH_EDITOR) // 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 = GIsFirstInstance; #endif 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); } // Are we creating a slate application? bool bSlateApplication = !IsRunningDedicatedServer() && (bIsRegularClient || bHasEditorToken); if (bSlateApplication) { if (FPlatformProcess::SupportsMultithreading()) { 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); PreInitContext.SlowTaskPtr = new FScopedSlowTask(100, NSLOCTEXT("EngineLoop", "EngineLoop_Initializing", "Initializing...")); FScopedSlowTask& SlowTask = *PreInitContext.SlowTaskPtr; 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("FUniformBufferStruct::InitializeStructs()"); FShaderParametersMetadata::InitializeAllUniformBufferStructs(); } { SCOPED_BOOT_TIMING("RHIInit"); // Initialize the RHI. RHIInit(bHasEditorToken); } { SCOPED_BOOT_TIMING("RenderUtilsInit"); // One-time initialization of global variables based on engine configuration. RenderUtilsInit(); } #if WITH_EDITOR { // pre-generate certain shader code based on the engine configuration (editor only) ShaderAutogenInit(); } #endif { 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. SCOPED_BOOT_TIMING("InitDerivedData"); UE::DerivedData::GetBuild(); UE::DerivedData::GetCache(); GetDerivedDataCacheRef(); } #endif #if WITH_EDITOR // The virtualization system will be initialized when ever IVirtualizationSystem::Get is called // but here we try to explicitly initialize it. UE::Virtualization::Initialize(); #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(); } } { 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()) { SCOPED_BOOT_TIMING("InitializeShaderTypes"); // 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(); } if (GUseThreadedRendering) { if (GRHISupportsRHIThread) { const bool DefaultUseRHIThread = true; GUseRHIThread_InternalUseOnly = DefaultUseRHIThread; if (FParse::Param(FCommandLine::Get(), TEXT("rhithread"))) { GUseRHIThread_InternalUseOnly = true; } else if (FParse::Param(FCommandLine::Get(), TEXT("norhithread"))) { GUseRHIThread_InternalUseOnly = false; } } SCOPED_BOOT_TIMING("StartRenderingThread"); StartRenderingThread(); } #endif FEmbeddedCommunication::ForceTick(4); { #if !UE_SERVER// && !UE_EDITOR if (!IsRunningDedicatedServer() && !IsRunningCommandlet()) { TSharedPtr SlateRenderer = GUsingNullRHI ? FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer() : FModuleManager::Get().GetModuleChecked("SlateRHIRenderer").CreateSlateRHIRenderer(); TSharedRef 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; } #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 bool bDisableDisregardForGC = bHasEditorToken; if (bIsRunningAsDedicatedServer) { bDisableDisregardForGC |= FPlatformProperties::RequiresCookedData() && (GUseDisregardForGCOnDedicatedServers == 0); } PreInitContext.bDisableDisregardForGC = bDisableDisregardForGC; PreInitContext.bIsRegularClient = bIsRegularClient; #endif // WITH_ENGINE PreInitContext.bIsPossiblyUnrecognizedCommandlet = bIsRegularClient && Token.Len() && !Token.Contains(TEXT("-")); PRAGMA_DISABLE_DEPRECATION_WARNINGS; PreInitContext.bTokenDoesNotHaveDash = PreInitContext.bIsPossiblyUnrecognizedCommandlet; PRAGMA_ENABLE_DEPRECATION_WARNINGS; PreInitContext.Token = Token; #if UE_EDITOR || WITH_ENGINE PreInitContext.CommandletCommandLine = CommandletCommandLine; #endif // UE_EDITOR || WITH_ENGINE PreInitContext.CommandLineCopy = CommandLineCopy; return 0; } int32 FEngineLoop::PreInitPostStartupScreen(const TCHAR* CmdLine) { ON_SCOPE_EXIT{ GEnginePreInitPostStartupScreenEndTime = FPlatformTime::Seconds(); }; SCOPED_BOOT_TIMING("FEngineLoop::PreInitPostStartupScreen"); if (IsEngineExitRequested()) { return 0; } 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 bDisableDisregardForGC = PreInitContext.bDisableDisregardForGC; 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 TCHAR* CommandLineCopy = PreInitContext.CommandLineCopy; FScopedSlowTask& SlowTask = *PreInitContext.SlowTaskPtr; #if WITH_ENGINE { TSharedPtr BundleManager = IInstallBundleManager::GetPlatformInstallBundleManager(); #if !UE_SERVER// && !UE_EDITOR if (!IsRunningDedicatedServer() && !IsRunningCommandlet()) { SCOPED_BOOT_TIMING("PreInitPostStartupScreen_StartupGraphics"); TSharedPtr SlateRenderer = PreInitContext.SlateRenderer; TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); { 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 (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 #if 0 && CONFIG_REMEMBER_ACCESS_PATTERN TArray ConfigFilenames; GConfig->GetConfigFilenames(ConfigFilenames); for (const FString& ConfigFilename : ConfigFilenames) { const FConfigFile* Config = GConfig->FindConfigFile(ConfigFilename); for (auto& ConfigSection : *Config) { TSet ProcessedValues; const FName SectionName = FName(*ConfigSection.Key); for (auto& ConfigValue : ConfigSection.Value) { const FName& ValueName = ConfigValue.Key; if (ProcessedValues.Contains(ValueName)) continue; ProcessedValues.Add(ValueName); TArray ValueArray; ConfigSection.Value.MultiFind(ValueName, ValueArray, true); bool bHasBeenAccessed = false; for (const auto& ValueArrayEntry : ValueArray) { if (ValueArrayEntry.HasBeenRead()) { bHasBeenAccessed = true; break; } } if (bHasBeenAccessed) { UE_LOG(LogInit, Display, TEXT("Accessed Ini Setting %s %s %s"), *ConfigFilename, *SectionName.ToString(), *ValueName.ToString()); } } } } #endif } else { 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 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 BeginInitGameTextLocalization(); 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"); // 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 ProcessNewlyLoadedUObjects(); EndInitGameTextLocalization(); FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::ObjectSystemReady); FEmbeddedCommunication::ForceTick(6); #if WITH_EDITOR if(FPIEPreviewDeviceModule::IsRequestingPreviewDevice()) { auto PIEPreviewDeviceModule = FModuleManager::LoadModulePtr("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); // Setup GC optimizations if (bDisableDisregardForGC) { SCOPED_BOOT_TIMING("DisableDisregardForGC"); GUObjectArray.DisableDisregardForGC(); } 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 (!IsRunningDedicatedServer() && !IsRunningCommandlet() && !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(); if (GUseThreadedRendering) { if (GRHISupportsRHIThread) { const bool DefaultUseRHIThread = true; GUseRHIThread_InternalUseOnly = DefaultUseRHIThread; if (FParse::Param(FCommandLine::Get(), TEXT("rhithread"))) { GUseRHIThread_InternalUseOnly = true; } else if (FParse::Param(FCommandLine::Get(), TEXT("norhithread"))) { GUseRHIThread_InternalUseOnly = false; } } StartRenderingThread(); } } #endif // !PLATFORM_SUPPORTS_EARLY_MOVIE_PLAYBACK // Playing a movie can only happen after the rendering thread is started. #if !UE_SERVER// && !UE_EDITOR if (!IsRunningDedicatedServer() && !IsRunningCommandlet() && !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(!GetMoviePlayer()->IsMovieCurrentlyPlaying()); } else #endif { // show splash screen FPlatformMisc::PlatformHandleSplashScreen(true); } } if(!GIsEditor) { 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(); #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(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(*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 extern bool GIsConsoleExecutable; 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(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()->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(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(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 FCoreDelegates::OnAllModuleLoadingPhasesComplete.Broadcast(); //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(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 FStats::TickCommandletStats(); #if WITH_ENGINE PRIVATE_GRunningCommandletClass = CommandletClass; #endif int32 ErrorLevel = Commandlet->Main( CommandletCommandLine ); #if WITH_ENGINE PRIVATE_GRunningCommandletClass = nullptr; #endif FStats::TickCommandletStats(); RequestEngineExit(FString::Printf(TEXT("Commandlet %s finished execution (result %d)"), *Commandlet->GetName(), ErrorLevel)); // Log warning/ error summary. if( Commandlet->ShowErrorCount ) { TArray AllErrors; TArray 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 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); } } } 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() ); ErrorLevel = 1; } CLEAR_WARN_COLOR(); } else { UE_LOG(LogInit, Display, TEXT("Finished.") ); } double CommandletExecutionTime = FPlatformTime::Seconds() - CommandletExecutionStartTime; UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT( "Execution of commandlet took: %.2f seconds"), CommandletExecutionTime ); // 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(*(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 ) { 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("TaskGraph")); if (FPlatformProcess::SupportsMultithreading()) { FModuleManager::Get().LoadModule(TEXT("ProfilerService")); FModuleManager::Get().GetModuleChecked("ProfilerService").CreateProfilerServiceManager(); } #endif // Init HighRes screenshot system, unless running on server if (!IsRunningDedicatedServer()) { GetHighResScreenshotConfig().Init(); } #else // WITH_ENGINE InitEngineTextLocalization(); InitGameTextLocalization(); #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(); } 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) { 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 #if USE_PER_MODULE_UOBJECT_BOOTSTRAP // otherwise do it later FModuleManager::Get().OnProcessLoadedObjectsCallback().AddStatic(ProcessNewlyLoadedUObjects); #endif 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("SlateRHIRenderer"); } } #endif FModuleManager::Get().LoadModule(TEXT("Landscape")); // 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 && !(UE_BUILD_SHIPPING || UE_BUILD_TEST)) // Load audio editor module before engine class CDOs are loaded FModuleManager::Get().LoadModule(TEXT("AudioEditor")); FModuleManager::Get().LoadModule(TEXT("AnimationModifiers")); #endif } #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")); } SlowTask.EnterProgressFrame(10); FPlatformApplicationMisc::LoadStartupModules(); // initialize messaging SlowTask.EnterProgressFrame(10); if (FPlatformProcess::SupportsMultithreading()) { FModuleManager::LoadModuleChecked("Messaging"); } // Init Scene Reconstruction support #if !UE_SERVER if (!IsRunningDedicatedServer()) { FModuleManager::LoadModuleChecked("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"); #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)) // 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")); IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked("AudioEditor"); AudioEditorModule->RegisterAssetActions(); // 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_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")); #endif FModuleManager::Get().LoadModule(TEXT("PacketHandler")); FModuleManager::Get().LoadModule(TEXT("NetworkReplayStreaming")); return bSuccess; } bool FEngineLoop::LoadStartupModules() { FScopedSlowTask SlowTask(3); 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() { FlushPendingDeleteRHIResources_GameThread(); FStats::AdvanceFrame( true, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) ); } int32 FEngineLoop::Init() { ON_SCOPE_EXIT{ GEngineInitEndTime = FPlatformTime::Seconds(); }; 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 UnrealEd Engine class '%s'."), *GameEngineClassName); } GEngine = NewObject(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(GetTransientPackage(), EngineClass); #else check(0); #endif } FEmbeddedCommunication::ForceTick(11); check( GEngine ); 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("SessionServices").GetSessionService(); if (SessionService.IsValid()) { SessionService->Start(); } } EngineService = new FEngineService(); } { 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 { SCOPED_BOOT_TIMING("OnAllModuleLoadingPhasesComplete.Broadcast"); FCoreDelegates::OnAllModuleLoadingPhasesComplete.Broadcast(); } { 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 { SCOPED_BOOT_TIMING("WaitForMovieToFinish"); GetMoviePlayer()->WaitForMovieToFinish(); } FTraceAuxiliary::EnableChannels(); #if !UE_SERVER // initialize media framework IMediaModule* MediaModule = FModuleManager::LoadModulePtr("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("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; } void FEngineLoop::Exit() { STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "EngineLoop.Exit" ) ); TRACE_CPUPROFILER_EVENT_SCOPE(FEngineLoop::Exit); TRACE_BOOKMARK(TEXT("EngineLoop.Exit")); 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().Shutdown(); #endif FAssetCompilingManager::Get().Shutdown(); #if WITH_ENGINE // shut down messaging delete EngineService; EngineService = nullptr; if (SessionService.IsValid()) { SessionService->Stop(); SessionService.Reset(); } if (GDistanceFieldAsyncQueue) { delete GDistanceFieldAsyncQueue; GDistanceFieldAsyncQueue = nullptr; } if (GCardRepresentationAsyncQueue) { delete GCardRepresentationAsyncQueue; GCardRepresentationAsyncQueue = nullptr; } #endif // WITH_ENGINE if ( GEngine != nullptr ) { GEngine->PreExit(); } if (GEngine != nullptr) { GEngine->ReleaseAudioDeviceManager(); } // Make sure we're not in the middle of loading something. { // From now on it's not allowed to request new async loads SetAsyncLoadingAllowed(false); bool bFlushOnExit = true; if (GConfig) { FBoolConfigValueHelper FlushStreamingOnExitHelper(TEXT("/Script/Engine.StreamingSettings"), TEXT("s.FlushStreamingOnExit"), GEngineIni); bFlushOnExit = FlushStreamingOnExitHelper; } if (bFlushOnExit) { FlushAsyncLoading(); } else { CancelAsyncLoading(); } } // 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 !PLATFORM_ANDROID // AppPreExit doesn't work on Android AppPreExit(); TermGamePhys(); #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); #if PLATFORM_ANDROID // AppPreExit() stops malloc profiler, do it here instead MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); ); #endif // !ANDROID #if WITH_EDITOR IBulkDataRegistry::Shutdown(); #endif // Stop the rendering thread. StopRenderingThread(); // Disable the PSO cache FShaderPipelineCache::Shutdown(); // 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(); #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 !PLATFORM_ANDROID // UnloadModules doesn't work on Android #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(); } #endif // !ANDROID IStreamingManager::Shutdown(); StopRHIThread(); DestroyMoviePlayer(); // Move earlier? #if STATS FThreadStats::StopThread(); #endif FTaskGraphInterface::Shutdown(); RHIExit(); FPlatformMisc::ShutdownTaggedStorage(); #if WITH_ENGINE && FRAMEPRO_ENABLED FFrameProProfiler::TearDown(); #endif // FRAMEPRO_ENABLED } 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(PlayerController->Player)) { TOptional 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); } } } 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 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 CVarLogGameThreadMallocChurn_PrintFrequency( TEXT("LogGameThreadMallocChurn.PrintFrequency"), 300, TEXT("Number of frames between churn reports.")); static TAutoConsoleVariable CVarLogGameThreadMallocChurn_Threshhold( TEXT("LogGameThreadMallocChurn.Threshhold"), 10, TEXT("Minimum average number of allocs per frame to include in the report.")); static TAutoConsoleVariable 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 CVarLogGameThreadMallocChurn_StackIgnore( TEXT("LogGameThreadMallocChurn.StackIgnore"), 2, TEXT("Number of items to discard from the top of a stack frame.")); static TAutoConsoleVariable 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 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* GGameThreadMallocHook; struct FScopedSampleMallocChurn { static FStackTracker GGameThreadMallocChurnTracker; static uint64 DumpFrame; bool bEnabled; int32 CountDown; TFunction 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 static uint32 TraceFrameEventThreadId = (uint32) -1; static inline void BeginFrameRenderThread(FRHICommandListImmediate& RHICmdList, uint64 CurrentFrameCounter) { if ( !FApp::CanEverRender() ) { GFrameNumberRenderThread++; RHICmdList.BeginFrame(); return; } TRACE_BEGIN_FRAME(TraceFrameType_Rendering); GRHICommandList.LatchBypass(); 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 if (GTriggerGPUProfile && !GTriggerGPUHitchProfile) { IssueScalableLongGPUTask(RHICmdList); } #endif #if CPUPROFILERTRACE_ENABLED TraceFrameEventThreadId = (uint32) -1; if (UE_TRACE_CHANNELEXPR_IS_ENABLED(CpuChannel)) { TraceFrameEventThreadId = FPlatformTLS::GetCurrentThreadId(); FCpuProfilerTrace::OutputBeginDynamicEvent(TEXT("Frame"), __FILE__, __LINE__); } #endif //CPUPROFILERTRACE_ENABLED FString FrameString = FString::Printf(TEXT("Frame %d"), CurrentFrameCounter); #if ENABLE_NAMED_EVENTS #if PLATFORM_LIMIT_PROFILER_UNIQUE_NAMED_EVENTS FPlatformMisc::BeginNamedEvent(FColor::Yellow, TEXT("Frame")); #else FPlatformMisc::BeginNamedEvent(FColor::Yellow, *FrameString); #endif #endif // ENABLE_NAMED_EVENTS RHICmdList.PushEvent(*FrameString, FColor::Green); #endif // !UE_BUILD_SHIPPING GPU_STATS_BEGINFRAME(RHICmdList); RHICmdList.BeginFrame(); FCoreDelegates::OnBeginFrameRT.Broadcast(); RHICmdList.EnqueueLambda([CurrentFrameCounter](FRHICommandListImmediate& InRHICmdList) { GEngine->SetRenderSubmitLatencyMarkerStart(CurrentFrameCounter); }); #if CSV_PROFILER FCsvProfiler::BeginExclusiveStat("RenderThreadOther"); #endif } static inline void EndFrameRenderThread(FRHICommandListImmediate& RHICmdList, uint64 CurrentFrameCounter) { if ( !FApp::CanEverRender() ) { RHICmdList.EndFrame(); return; } #if CSV_PROFILER FCsvProfiler::EndExclusiveStat("RenderThreadOther"); #endif RHICmdList.EnqueueLambda([CurrentFrameCounter](FRHICommandListImmediate& InRHICmdList) { GEngine->SetRenderSubmitLatencyMarkerEnd(CurrentFrameCounter); }); FCoreDelegates::OnEndFrameRT.Broadcast(); RHICmdList.EndFrame(); GPU_STATS_ENDFRAME(RHICmdList); #if !UE_BUILD_SHIPPING RHICmdList.PopEvent(); #if ENABLE_NAMED_EVENTS FPlatformMisc::EndNamedEvent(); #endif #if CPUPROFILERTRACE_ENABLED if (TraceFrameEventThreadId == FPlatformTLS::GetCurrentThreadId()) { FCpuProfilerTrace::OutputEndEvent(); } #endif // CPUPROFILERTRACE_ENABLED #endif // !UE_BUILD_SHIPPING 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); } #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(); } // Make sure something is ticking the rendering tickables in -onethread mode to avoid leaks/bugs. if (!GUseThreadedRendering && !GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed)) { TRACE_CPUPROFILER_EVENT_SCOPE(TickRenderingTickables); TickRenderingTickables(); } // Ensure we aren't starting a frame while loading or playing a loading movie FMoviePlayerProxy::BlockingForceFinished(); ensure(GetMoviePlayer()->IsLoadingFinished() && !GetMoviePlayer()->IsMovieCurrentlyPlaying()); #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 TCHAR IndexedFrameString[32] = { 0 }; const TCHAR* FrameString = nullptr; #if CPUPROFILERTRACE_ENABLED const bool bTraceCpuChannelEnabled = UE_TRACE_CHANNELEXPR_IS_ENABLED(CpuChannel); static FAutoNamedEventsToggler TraceNamedEventsToggler; TraceNamedEventsToggler.Update(bTraceCpuChannelEnabled && UE::Trace::IsTracing()); if (bTraceCpuChannelEnabled) { FrameString = TEXT("FEngineLoop"); } else #endif { #if PLATFORM_LIMIT_PROFILER_UNIQUE_NAMED_EVENTS FrameString = TEXT("FEngineLoop"); #else FCString::Snprintf(IndexedFrameString, 32, TEXT("Frame %d"), CurrentFrameCounter); FrameString = IndexedFrameString; #endif } SCOPED_NAMED_EVENT_TCHAR(FrameString, FColor::Red); #endif // 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 // 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 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(0); } // 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); } for (const FWorldContext& Context : GEngine->GetWorldContexts()) { UWorld* CurrentWorld = Context.World(); if (CurrentWorld) { FSceneInterface* Scene = CurrentWorld->Scene; ENQUEUE_RENDER_COMMAND(UpdateScenePrimitives)( [Scene](FRHICommandListImmediate& RHICmdList) { Scene->UpdateAllPrimitiveSceneInfos(RHICmdList); }); } } // beginning of RHI frame ENQUEUE_RENDER_COMMAND(BeginFrame)([CurrentFrameCounter](FRHICommandListImmediate& RHICmdList) { BeginFrameRenderThread(RHICmdList, CurrentFrameCounter); }); for (const FWorldContext& Context : GEngine->GetWorldContexts()) { UWorld* CurrentWorld = Context.World(); if (CurrentWorld) { FSceneInterface* Scene = CurrentWorld->Scene; ENQUEUE_RENDER_COMMAND(SceneStartFrame)([Scene](FRHICommandListImmediate& RHICmdList) { Scene->StartFrame(); }); } } #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); GMalloc->UpdateStats(); } #endif } FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) ); { SCOPE_CYCLE_COUNTER( STAT_FrameTime ); // Calculates average FPS/MS (outside STATS on purpose) CalculateFPSTimings(); // Note the start of a new frame MALLOC_PROFILER(GMalloc->Exec(nullptr, *FString::Printf(TEXT("SNAPSHOTMEMORYFRAME")),*GLog)); // handle some per-frame tasks on the rendering thread ENQUEUE_RENDER_COMMAND(ResetDeferredUpdates)( [](FRHICommandList& RHICmdList) { FDeferredUpdateResource::ResetNeedsUpdate(); FlushPendingDeleteRHIResources_RenderThread(); }); // 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(); } } // init for RDG resource dump #if WITH_ENGINE && WITH_DUMPGPU FRDGBuilder::InitResourceDump(); #endif // 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(GEngine); if (GameEngine) { GameEngine->SwitchGameWindowToUseGameViewport(); } } #if !UE_SERVER // Is it ok to start up the movie player? if (!IsRunningDedicatedServer() && !IsRunningCommandlet() && !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 { 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::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) { FMoviePlayerProxy::SetIsSlateThreadAllowed(false); FSlateApplication::Get().Tick(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; } { ENQUEUE_RENDER_COMMAND(WaitForOutstandingTasksOnly_for_DelaySceneRenderCompletion)( [](FRHICommandList& RHICmdList) { QUICK_SCOPE_CYCLE_COUNTER(STAT_DelaySceneRenderCompletion_TaskWait); FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly); }); } #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(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(AutomationWorkerModuleName).Tick(); } } #endif // 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); // Increment global frame counter. Once for each engine tick. GFrameCounter++; ENQUEUE_RENDER_COMMAND(FrameCounter)( [CurrentFrameCounter = GFrameCounter](FRHICommandListImmediate& RHICmdList) { GFrameCounterRenderThread = CurrentFrameCounter; }); // Disregard first few ticks for total tick time as it includes loading and such. if (GFrameCounter > 6) { TotalTickTime += FApp::GetDeltaTime(); } // Find the objects which need to be cleaned up the next frame. FPendingCleanupObjects* PreviousPendingCleanupObjects = PendingCleanupObjects; PendingCleanupObjects = GetPendingCleanupObjects(); { SCOPE_CYCLE_COUNTER(STAT_FrameSyncTime); // this could be perhaps moved down to get greater parallelism // Sync game and render thread. Either total sync or allowing one frame lag. static FFrameEndSync FrameEndSync; static auto CVarAllowOneFrameThreadLag = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag")); FrameEndSync.Sync( CVarAllowOneFrameThreadLag->GetValueOnGameThread() != 0 ); } // 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(MediaModuleName); if (MediaModule != nullptr) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_MediaTickPostRender); MediaModule->TickPostRender(); } #endif FCoreDelegates::OnEndFrame.Broadcast(); // end of RDG resource dump #if WITH_ENGINE && WITH_DUMPGPU FRDGBuilder::EndResourceDump(); #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_GC_TRACK_OBJ_AVAILABLE SET_DWORD_STAT(STAT_Hash_NumObjects, GUObjectArray.GetObjectArrayNumMinusAvailable()); #endif } 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() { delete PendingCleanupObjects; PendingCleanupObjects = nullptr; } #endif // WITH_ENGINE static TAutoConsoleVariable 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 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 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. FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir(); { 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")); } #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(); } // 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()) { // Check all the plugins are present if(!PluginManager.AreRequiredPluginsAvailable()) { return false; } // Find the editor target FString EditorTargetFileName; FString DefaultEditorTarget; GConfig->GetString(TEXT("/Script/BuildSettings.BuildSettings"), TEXT("DefaultEditorTarget"), DefaultEditorTarget, GEngineIni); for (const FTargetInfo& Target : FDesktopPlatformModule::Get()->GetTargetsForProject(FPaths::GetProjectFilePath())) { if (Target.Type == EBuildTargetType::Editor && (DefaultEditorTarget.Len() == 0 || Target.Name == DefaultEditorTarget)) { if (FPaths::IsUnderDirectory(Target.Path, FPlatformMisc::ProjectDir())) { EditorTargetFileName = FTargetReceipt::GetDefaultPath(FPlatformMisc::ProjectDir(), *Target.Name, FPlatformProcess::GetBinariesSubdirectory(), FApp::GetBuildConfiguration(), nullptr); } else if (FPaths::IsUnderDirectory(Target.Path, FPaths::EngineDir())) { EditorTargetFileName = FTargetReceipt::GetDefaultPath(*FPaths::EngineDir(), *Target.Name, FPlatformProcess::GetBinariesSubdirectory(), FApp::GetBuildConfiguration(), nullptr); } break; } } // If we're not running the correct executable for the current target, and the listed executable exists, run that instead if(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 IncompatibleFiles; ProjectManager.CheckModuleCompatibility(IncompatibleFiles); TArray 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::No) { return false; } bNeedCompile = true; } 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 StillIncompatibleFiles; ProjectManager.CheckModuleCompatibility(StillIncompatibleFiles); TArray 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 // 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"); // Load "pre-init" plugin modules if (!ProjectManager.LoadModulesForProject(ELoadingPhase::PostConfigInit) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit)) { return false; } } // Register the callback that allows the text localization manager to load data for plugins FCoreDelegates::GatherAdditionalLocResPathsCallback.AddLambda([](TArray& OutLocResPaths) { IPluginManager::Get().GetLocalizationPathsForEnabledPlugins(OutLocResPaths); }); FEmbeddedCommunication::ForceTick(17); PreInitHMDDevice(); // 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("SILENT"))) { GIsSilent = true; } if (FParse::Param(FCommandLine::Get(), TEXT("RUNNINGUNATTENDEDSCRIPT"))) { GIsRunningUnattendedScript = true; } #endif // !UE_BUILD_SHIPPING // Show log if wanted. if (GLogConsole && FParse::Param(FCommandLine::Get(), TEXT("LOG"))) { GLogConsole->Show(true); } // 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("PIEPreviewDeviceProfileSelector"); if (PIEPreviewDeviceProfileSelectorModule) { Scalability::ChangeScalabilityPreviewPlatform(PIEPreviewDeviceProfileSelectorModule->GetPreviewPlatformName()); } } #endif FEmbeddedCommunication::ForceTick(19); return true; } void FEngineLoop::AppPreExit( ) { SCOPED_BOOT_TIMING("AppPreExit"); UE_LOG(LogExit, Log, TEXT("Preparing to exit.") ); FCoreDelegates::OnPreExit.Broadcast(); MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); ); #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. Note that simply incrementing the value doesn't work, because FGenericRHIGPUFence::WriteInternal adds // GNumAlternateFrameRenderingGroups to the current frame number to account for multi-GPU, and we don't want to depend on that RHI export here. GFrameNumberRenderThread = MAX_uint32; // 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(); } if (GIOThreadPool != nullptr) { GIOThreadPool->Destroy(); } #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() { // 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 PixelFormatByteWidth; PixelFormatByteWidth.AddUninitialized(PF_MAX); for (int i = 0; i < PF_MAX; i++) { PixelFormatByteWidth[i] = GPixelFormats[i].BlockBytes; } RHIPostInit(PixelFormatByteWidth); #if WITH_ENGINE && (!UE_BUILD_SHIPPING) IRHITestModule* RHIUnitTests = static_cast(FModuleManager::Get().GetModule(TEXT("RHITests"))); if (RHIUnitTests) { RHIUnitTests->RunAllTests(); } #endif //(!UE_BUILD_SHIPPING) #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 HMDModules = ModularFeatures.GetModularFeatureImplementations(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 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 CommandletCommandLine = nullptr; delete[] CommandLineCopy; CommandLineCopy = nullptr; delete SlowTaskPtr; SlowTaskPtr = nullptr; } #undef LOCTEXT_NAMESPACE