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