You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3175510 on 2016/10/26 by Josh.Adams - New Wolf SDK support (11). - Added new input plugin now that extra NDA is lifted Change 3176629 on 2016/10/27 by Josh.Adams Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform) Change 3177232 on 2016/10/27 by Josh.Adams - Minor comment change Change 3177348 on 2016/10/27 by Dmitry.Rekman Linux: default to GL4. Change 3177523 on 2016/10/27 by Dmitry.Rekman Linux: update libc++ to 3.9 and add AArch64. Change 3178208 on 2016/10/28 by Daniel.Lamb Enable multithreaded lightmap encoding. Change 3178273 on 2016/10/28 by Luke.Thatcher [PLATFORM] [PS4] [!] Fix crash in PS4 packaging step. - Parallel-for accessing the same log files, causing IOException. Change 3178573 on 2016/10/28 by Dmitry.Rekman Linux: fix for projects not having proper version associations (UE-5954). - Fixed by CengizT. Change3180487on 2016/10/31 by Josh.Adams Moved new file to peoper spot Change 3180508 on 2016/10/31 by Josh.Adams - Fixed crash on audio free for Wolf Change 3181821 on 2016/11/01 by Josh.Adams - Fixed ShooterGame cooking after sync from main Change 3182469 on 2016/11/01 by Josh.Adams - test/shipping build wolf fixes Change 3183078 on 2016/11/02 by Josh.Adams - Added AllDesktop back in for Windows (File | Package) Change 3183229 on 2016/11/02 by Josh.Adams - Fixed wrong path in JunkManifest.txt Change 3184245 on 2016/11/02 by Dmitry.Rekman Linux: add AArch64 (ARM64) libs. Change 3184326 on 2016/11/02 by Dmitry.Rekman Linux: add more files for AArch64. Change 3184353 on 2016/11/02 by Dmitry.Rekman Linux: Add missed AArch64 libpng. Change 3184871 on 2016/11/03 by Luke.Thatcher [PLATFORM] [PS4] [!] Fix broken DownloadImage blueprint node on PS4. - Node should return a UTexture2DDynamic, otherwise the RHI assumes the data has been pre-formatted for the GPU, and we get pitch/layout issues. #jira UE-36365 Change 3185407 on 2016/11/03 by Dmitry.Rekman Linux: fix PhysX on AArch64. (Edigrating 3184484 from Wombat to Dev-Platform). Change 3187488 on 2016/11/04 by Josh.Adams Copying //Tasks/UE4/Private-Platform-Switch to Dev-Platform-Minimal (//UE4/Dev-Platform-Minimal) Change 3187740 on 2016/11/04 by Josh.Adams - Re-copying the Switch files, now with proper case in the directory names Change 3188304 on 2016/11/07 by Dan.Mahashin - Removed deprecated functions in NVN window creation Change3188865on 2016/11/07 by Luke.Thatcher [PLATFORM] [PS4] [~] Move PS4 console input handler into engine classes from OrionGame. - Enables console input from Sony's "Console Output" tool for all games, in debug/development builds. #jira UE-37672 Change 3189517 on 2016/11/07 by Jeff.Campeau Fix incorrect local platform identification in device manager. #jira UE-38312 Change 3189897 on 2016/11/08 by Luke.Thatcher [PLATFORM] [!] Fix width/height mismatch in DownloadImage blueprint node. Change 3190042 on 2016/11/08 by Josh.Adams - Fixed default and Shooter App Ids for Switch Change 3190181 on 2016/11/08 by Joe.Barnes [UE-37275] Split reflection capture error message into two UE_LOG()s. Line length causes truncation and line wrap on some platforms. Change 3190185 on 2016/11/08 by Joe.Barnes Fix another instance of UE_LOG() where the string was being truncated on Switch platform. Change 3190272 on 2016/11/08 by Daniel.Lamb Add file hashes to depependency tracking info. Moved partial gc controlling code outside of the cooker. Store cooked file hashes in cooked asset registry. Cooked asset registry is now part of the cooker instead of chunking manifest. #test cook paragon Change 3190332 on 2016/11/08 by Omar.Rodriguez Fixing issues with iOS remote notifications * Update UPlatformGameInstance::FPlatformRegisteredForRemoteNotificationsDelegate signature so the parameter is const& which will work with BlueprintAssignable * Fix misspelling when doing respondsToSelector check * Update generated Xcode project to use the generated entitlements file * Add remote-notification as a background mode * Update the generated entitlements file contents to include APS environment for push notifications * Added bEnableRemoteNotificationsSupport ini parameter to control whether iOS push notifications code is compiled Change 3190391 on 2016/11/08 by Brent.Pease UE-31739 - Crash when Deploying to iPad Air with BC4 Texture Compression Setting (Josh's suggestion worked out of the box) Change 3190786 on 2016/11/08 by Bart.Hawthorne Fix some missing PLATFORM_WOLF changes to PLATFORM_SWITCH in ShooterGame Change 3190902 on 2016/11/08 by Alicia.Cano Allow RTTI and exceptions to be enabled for Android #jira UE-37845 #android Change 3190973 on 2016/11/08 by Chris.Babcock Add ability to set element value field with new text parameter for UPL #jira UE-37390 #PR #2869 #ue4 #upl Change 3191411 on 2016/11/09 by Josh.Stoddard Warn when user tries to use a shared pak reader on the wrong thread #jira UE-38049 Change 3191635 on 2016/11/09 by Josh.Stoddard More useful message during cook when AT9 assets fail to encode using SCE's tool #jira UE-38053 Change 3191663 on 2016/11/09 by Peter.Sauerbrei fix for ios build from PC Change 3191701 on 2016/11/09 by Brent.Pease implement iOS device logs on windows Change 3191794 on 2016/11/09 by Daniel.Lamb Fixed up compile error missing header file. #test Compile editor #jira UE-38414 Change 3191807 on 2016/11/09 by Josh.Adams - Fixed one chage that was missed in the WolfPlat->Switch rename Change 3191867 on 2016/11/09 by Josh.Adams - Enabled Switch for ShooterGame project Change 3191958 on 2016/11/09 by Jeff.Campeau Add warning for anyone still using XP Change 3192185 on 2016/11/09 by Josh.Adams - Updated to SDK 0.11.12 - Added TrackLotCheckItem API to track guidelines with limits (nothing using it yet) Change 3192241 on 2016/11/09 by Josh.Adams Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform) Change 3192324 on 2016/11/09 by Josh.Adams - Worked around an issue with RunOnTarget stripping quotes around paths with spaces #jira UE-38388 Change 3192387 on 2016/11/09 by Josh.Adams - Updating editor icon for Switch #jira UE-38295 Change 3192476 on 2016/11/09 by Dmitry.Rekman Linux: put correct OpenAL lib per architecture. (Edigrating CL 3185947 from Wombat to Dev-Platform) Change 3192527 on 2016/11/09 by Josh.Adams - Fixed a shadow variable warning #jira UE-38408 Change 3192606 on 2016/11/09 by Jeff.Campeau XP option removed #jira UEPLAT-1542 Change 3192644 on 2016/11/09 by Josh.Adams - Fixed a CIS error (not sure why I don't get it, but hey) Change 3192659 on 2016/11/09 by Josh.Adams - Fixed a crash in DeploymentServer Change 3192672 on 2016/11/09 by Jeff.Campeau Fix WinXP message spamming Change 3193252 on 2016/11/10 by Josh.Adams - Remove assertion in SwitchTextureFormat when the SDK can't be found (if you are sharing DLLs) Change 3193756 on 2016/11/10 by Dmitry.Rekman Linux: add support for touch events. (Edigrating CL 3188159 from Wombat to Dev-Platform). Change 3194297 on 2016/11/10 by Jeff.Campeau HarfBuzz implementation for Xbox One #jira UE-28590 Change 3194299 on 2016/11/10 by Jeff.Campeau Pump Xbox One messaging during slow startup tasks #jira UEPLAT-1276 Change 3194300 on 2016/11/10 by Jeff.Campeau Use response files when building for Xbox One #jira UEPLAT-1296 Change 3194313 on 2016/11/11 by Jeff.Campeau Stop uploading symbols on the first error Show a more detailed error message if symbol uploading fails Add a command line option to disable upload of symbols #1852 #jira UE-24425 Change 3194327 on 2016/11/11 by Jeff.Campeau Deduplicate Xbox One build.cs setup for several modules #jira UE-37540 Change 3194402 on 2016/11/11 by Dmitry.Rekman Linux: do not apply mouse workaround unnecessarily. - Only matters when there is more than one window. (Edigrating 3194399 from Wombat to Dev-Platform). Change 3194434 on 2016/11/11 by Dan.Mahashin - Ported fix CL 3193690: Add workaround to file I/O error about ResultInvalidCurrentMemory when reloading levels - remove uncached attribute during memory pool finalization Change 3194569 on 2016/11/11 by Daniel.Lamb Fixed issue with CreateLinker failing to return LinkerLoad but creating a UPackage which can't be cleaned up. Change 3194570 on 2016/11/11 by Daniel.Lamb Fix for DiffSerializeArchive not using the correct archive when saving packages. #test Cook paragon Change 3194571 on 2016/11/11 by Daniel.Lamb Make sure dependent packages are valid before using them. Added FastBuildCookRun bat file for paragon testing. #test Cook Paragon Change 3194575 on 2016/11/11 by Daniel.Lamb Reworked a warning for the cooker. Change 3194698 on 2016/11/11 by Daniel.Lamb Skip skin verify only runs on build machines now. Saves paragon cook time. Change 3194699 on 2016/11/11 by Daniel.Lamb Changed the wording of skip editor content setting so it's more clear. #test none Change 3194702 on 2016/11/11 by Daniel.Lamb Potential fix for default materials not being in chunk zero. #test run ps4 cooked build paragon Change3194711on 2016/11/11 by Alicia.Cano Allow RTTI and exceptions to be enabled for Android Allow RTTI to be enabled for IOS, Mac #jira UE-37845, UE-20314 #android #ios #mac Change 3194956 on 2016/11/11 by Josh.Adams - Removed the crash with unknown socket error code, left in the warning Change 3195028 on 2016/11/11 by Dmitry.Rekman Linux: repair launch on. (Edigrating 3194384 from //UE4/Private-Wombat/... to //UE4/Dev-Platform/...) Change 3195041 on 2016/11/11 by Dmitry.Rekman Linux: support selecting architectures per project. (Edigrating 3192783 from Wombat to Dev-Platform). Change 3195058 on 2016/11/11 by Dmitry.Rekman Linux: fix code to determine number of cores. - ARM topology seems not to be in line with the assumptions made by x86-centric code. (Merging 3184632 from Wombat to Dev-Platform). Change 3195082 on 2016/11/11 by Josh.Adams - Fixed name of packaged Switch builds to have the config in it (Shipping, etc) #jira UE-38394 Change 3195151 on 2016/11/11 by Bart.Hawthorne - Add game server settings to project settings to connect to the actual game server, instead of the debug login - Use the system software dialog box to show error codes for login failures Change 3195153 on 2016/11/11 by Josh.Adams - Fixed copy and paste logs errors Change 3195156 on 2016/11/11 by Josh.Adams - Fixed some default values, especially for save games (uses their default of 4MB size) - Added some LotCheck write tracking Change 3195285 on 2016/11/11 by Jeff.Campeau Fix HarfBuzz warning on Xbox One Change 3195477 on 2016/11/11 by Josh.Adams - Fixed up some IsGameOnly calls #jira UE-37575 Change 3195490 on 2016/11/11 by Dmitry.Rekman UAT: fix CIS (removed old variables). Change 3195724 on 2016/11/11 by Josh.Adams - Final fix for name of .nsp (content only projects in Shipping config, etc) #jira UE-38394 Change 3195755 on 2016/11/11 by Josh.Adams - Made translucent Switch icons Change 3195771 on 2016/11/11 by Josh.Adams - Fixed some Switch "space in path" issues #jira UE-38393 Change 3195801 on 2016/11/11 by Josh.Adams - Handle making sure the save is completed before we shutdown #jira UE-38215 Change 3196593 on 2016/11/14 by Michael.Trepka Implemented Info string in AvfMedia for display in Media Player Editor #jira UE-35386 Change 3196782 on 2016/11/14 by Josh.Adams - Added a comment for a workaround Change 3196784 on 2016/11/14 by Michael.Trepka Alembic importer for Mac #jira UE-37708 Change 3196901 on 2016/11/14 by Alicia.Cano ADB over wifi fails to deploy on Launch on. #jira UE-37957 #android Change 3197055 on 2016/11/14 by Josh.Adams - Fixed BinnedAllocator crash that happened with PoisonProxy and large allocations with large alignment Change 3197059 on 2016/11/14 by Josh.Adams - Removed some stat code with no STATS Change 3197066 on 2016/11/14 by Josh.Adams - Fixed the generic growableallocator to not free metadata before it's used for stats, and cleaned up a couple minor things Change 3197176 on 2016/11/14 by Josh.Adams - Added some helper scripts to switch in and out of debug mode on Switch Change 3197183 on 2016/11/14 by Bart.Hawthorne Error dialog fixes based on peer review feedback from JoshA Change 3197339 on 2016/11/14 by Josh.Adams Allow -htcs on the commandline now to override disabling Htcs in packaged builds Change 3197401 on 2016/11/14 by Josh.Adams - Fixed the Switch package installation script to remove the path of the package, since it causes problems with spaces, and also it makes the script less portable! #jira UE-38556 Change 3197691 on 2016/11/14 by Dmitry.Rekman Linux: save added devices. (Edigrating 3196529 from Wombat to Dev-Platform). Change 3197854 on 2016/11/15 by Dan.Mahashin - MemoryProfiler2: fixed Switch parser file path in the csproj Change 3197960 on 2016/11/15 by Dan.Mahashin - NVN RHITransitionResources() directly uses a barrier instead of relying on CopyToResolveTarget() side effect (UE-33834) Change 3198488 on 2016/11/15 by Bart.Hawthorne Submit missing NoRedist/DefaultEngine.ini file Change 3198970 on 2016/11/15 by Michael.Trepka Don't try to use installed Mono 4.6 on Mac as it's known to have issues on macOS 10.12 (for example building the editor with UBT often fails with Mono running out of file desriptors) Change 3199050 on 2016/11/15 by Daniel.Lamb Some more output to help track down iterative cooking scenarios #test Cook paragon Change 3199097 on 2016/11/15 by Josh.Adams - Fixed up Switch packaging to re-generate the meta data in case it changed since compile time (esp with content only projects - Fixed default Program Id in code - Fixed a problem with Run with a space in the path #jira UE-38608 Change 3199181 on 2016/11/15 by Dmitry.Rekman Fix CIS (compiling LinuxTargetDevice without engine). Change 3199253 on 2016/11/15 by Dmitry.Rekman Hopeful fix for a static analysis warning. Change 3199325 on 2016/11/15 by Joe.Barnes Start a new CommandBuffer immediately upon ending one. Prevents fetching when there's no CommandBuffer. Needed for Loading Screen movie playback. Change 3199814 on 2016/11/15 by Dmitry.Rekman Linux: remove forced -windowed when launching. (Merging CL 3199789 from Wombat to Dev-Platform) Change 3200580 on 2016/11/16 by Josh.Adams Updasted DeploymentServer Change 3200595 on 2016/11/16 by Joe.Barnes Removed inadvertent SleepThread() when starting movie playback. Change 3200604 on 2016/11/16 by Josh.Adams - Added NN_MIDDLEWARE macros to tag ths apps as using UE4 middleware Change 3200632 on 2016/11/16 by Brent.Pease Update PlatformShowcase with latest tests Change 3200704 on 2016/11/16 by Dmitry.Rekman Linux: fix native compilation. Change 3200711 on 2016/11/16 by Brent.Pease - Support ios audio streaming from disk - Flushed out ADPCMAudioInfo to be more flexible with buffer management in addition to support streaming from disk. This should make more code platform independent. + Other platforms should work fine but will need to be updated to use the new buffer flexability (and hence simplify their own code and buffer management) - IOS audio implementation simplified to use new ADPCMAudioInfo functionality - Fixed adpcm seamless looping NOTE: While everything works with my testing (admittedly simple tests) a little more code cleanup needs to happen... Change 3201015 on 2016/11/16 by Josh.Adams Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform) Change 3201023 on 2016/11/16 by Josh.Stoddard Fix splash screen assignment for iPad #jira UE-38623 Change 3201215 on 2016/11/16 by Brent.Pease Hopefully final fix for build breakage Change 3201259 on 2016/11/16 by Josh.Adams - Removed the clock rate settings from the Project Settings, and the cvars - it was just confusing - Further improved the metadata recreation during packaging (can get rid of the temp meta/desc files now I believe) - Reduced audio pool alignment to not waste massive memory from it Change3202332on 2016/11/17 by Daniel.Lamb Changed build scripts to support iterative cooking #test Ran new build scripts Change 3202371 on 2016/11/17 by Michael.Trepka Changed FAppleHttpResponse::GetContentLength to return expected content size instead of payload size so it's consistent with other implementations #jira UE-38392 Change 3202421 on 2016/11/17 by Michael.Trepka Decrease the number of max open files for a thread on Apple platforms from 256 to 192 to leave more file descriptors to Cocoa #jira UE-18343 Change 3202462 on 2016/11/17 by Michael.Trepka Fixed HTTP If-None-Match response code on Mac and iOS Fixed by iktomi, https://answers.unrealengine.com/questions/492514/http-if-none-match-logic-isnt-working-on-mac-due-t.html #jira UE-36317 Change 3202620 on 2016/11/17 by Daniel.Lamb Fixed issue with some objects being garbage collected which shouldn't be because the collection handler didn't get registered. Commandlets now do not always have GIsRequestingExit true. Made crash handler check for commandlets running and exit appropriately. #test Rebuild lighting QAGame Change 3202955 on 2016/11/17 by Daniel.Lamb Add support for clearing all the cached cooked platform data for a platform when requested. #test cook QA game #jira UE-38361 Change 3202983 on 2016/11/17 by Daniel.Lamb Added support to rebuild lightmaps commandlet for building lightmaps in seperate files. #test rebuild lighting Custom QAGame maps. #jira OR-31907 Change 3203128 on 2016/11/17 by Josh.Adams - Fixed split screen user selection in ShooterGame (brought over some changes from NickD for it as well) Change 3203537 on 2016/11/18 by Dmitry.Rekman Fix ProjectWorldToScreen node for letterboxed viewports. (Merging CL 3201546 from Wombat to Dev-Platform). Change 3203540 on 2016/11/18 by Dmitry.Rekman Linux: be more verbose when setting vblank sync. (Merging CL 3199633 from Private-Wombat to Dev-Platform). Change 3203599 on 2016/11/18 by Dmitry.Rekman Speedup bForceCompilationAtStartup=True when nothing changed (UE-37067). - PR #2849: Contributed by slonopotamus. Change 3203610 on 2016/11/18 by Dmitry.Rekman Add CEF support for Linux (UE-6743). Change 3203615 on 2016/11/18 by Dmitry.Rekman Linux: fix bootstrap script so it is independent of working dir (UE-37016). - PR #2842 contributed by slonopotamus Change 3203645 on 2016/11/18 by Dmitry.Rekman Linux: fix UnrealCEFSubProcess. Change 3203658 on 2016/11/18 by Dmitry.Rekman Remove hard-coded paths to mono binary (UE-35228). - Another way to implement pull request #2741. Change 3203770 on 2016/11/18 by Josh.Adams - Brought over some changes from Dev-Core to not crash in AsyncLoading with debug code Change 3204244 on 2016/11/18 by Dmitry.Rekman Unsuppress mistakenly suppressed warnings and fix one more (UE-38788). Change 3204277 on 2016/11/18 by Brent.Pease + Fix seamless looping bug found on Dan's QAGame test + Fix static analyzer warning (which was a real bug with uncompressed streaming) + Code review feedback from Aaron + Small addition from channel sync ios bug fix Change 3204576 on 2016/11/18 by Omar.Rodriguez Expose the bEnableRemoteNotificationsSupport ini setting in the iOS project settings. Change 3204629 on 2016/11/18 by Chris.Babcock Fix case of VulkanSwapChain.h #include #jira UE-38843 #ue4 #vulkan Change 3204708 on 2016/11/18 by Josh.Adams - Set SwitchMoviePlayer to include the libs from the proper directory Change 3204730 on 2016/11/18 by Josh.Adams - Changed a check to a checkf to narrow down why FMaterialUniformExpressionType::GetTypeMap().FindRef(TypeName) is returning nullptr on tvOS Change 3204865 on 2016/11/18 by Brent.Pease + Turn off ios console logs on Windows to help sort through ios packaging and launch-on issues - This is NOT a fix but it should make it easier to track down the problem with it off. Change 3204883 on 2016/11/18 by Dmitry.Rekman Linux: fix native LaunchOn (UE-38616). Change 3204914 on 2016/11/18 by Brent.Pease + Turn off the device check to prevent it from conflicting with remote packaging/launch-on Change 3204940 on 2016/11/18 by Josh.Adams Backing out changes to the profiler for Switch. Shouldn't have checked it in today during smoke Change 3204952 on 2016/11/18 by Dmitry.Rekman Linux: fix bootstrap script (UE-38851). - Caused by UE-37016. Change 3205630 on 2016/11/21 by Brent.Pease + Fix audio sound queuing bug by ensuring audio buffers are not reused by different sound source objects. + Cleaned up the locking mechanism around stopping sound sources to make its intent and function are clear + Cleaned up memory tracking and freeing. #jira ue-38846 Change 3205787 on 2016/11/21 by Josh.Adams Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform) [CL 3206922 by Josh Adams in Main branch]
3576 lines
111 KiB
C++
3576 lines
111 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LaunchPrivatePCH.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "Ticker.h"
|
|
#include "ConsoleManager.h"
|
|
#include "ExceptionHandling.h"
|
|
#include "FileManagerGeneric.h"
|
|
#include "TaskGraphInterfaces.h"
|
|
#include "StatsMallocProfilerProxy.h"
|
|
|
|
#include "Projects.h"
|
|
#include "UProjectInfo.h"
|
|
#include "EngineVersion.h"
|
|
|
|
#include "ModuleManager.h"
|
|
#include "../Resources/Version.h"
|
|
#include "VersionManifest.h"
|
|
#include "UObject/DevObjectVersion.h"
|
|
#include "HAL/ThreadHeartBeat.h"
|
|
#include "MallocProfiler.h"
|
|
|
|
#include "NetworkVersion.h"
|
|
|
|
#if WITH_COREUOBJECT
|
|
#include "Internationalization/PackageLocalizationManager.h"
|
|
#include "CoreUObject.h"
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
#include "EditorStyle.h"
|
|
#include "RemoteConfigIni.h"
|
|
#include "EditorCommandLineUtils.h"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "AllowWindowsPlatformTypes.h"
|
|
#include <objbase.h>
|
|
#include "HideWindowsPlatformTypes.h"
|
|
#endif
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
#include "AudioThread.h"
|
|
#include "AutomationController.h"
|
|
#include "Database.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "RenderCore.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "DistanceFieldAtlas.h"
|
|
#include "GlobalShader.h"
|
|
#include "ParticleHelper.h"
|
|
#include "PhysicsPublic.h"
|
|
#include "PlatformFeatures.h"
|
|
#include "DeviceProfiles/DeviceProfileManager.h"
|
|
#include "Commandlets/Commandlet.h"
|
|
#include "EngineService.h"
|
|
#include "ContentStreaming.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "HotReloadInterface.h"
|
|
#include "ISessionService.h"
|
|
#include "ISessionServicesModule.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Net/OnlineEngineInterface.h"
|
|
#include "Internationalization/EnginePackageLocalizationCache.h"
|
|
#include "Engine/DemoNetDriver.h"
|
|
|
|
#if !UE_SERVER
|
|
#include "HeadMountedDisplay.h"
|
|
#include "ISlateRHIRendererModule.h"
|
|
#include "ISlateNullRendererModule.h"
|
|
#include "EngineFontServices.h"
|
|
#endif
|
|
|
|
#include "MoviePlayer.h"
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
#include "STaskGraph.h"
|
|
#include "IProfilerServiceModule.h"
|
|
#endif
|
|
|
|
#if WITH_AUTOMATION_WORKER
|
|
#include "AutomationWorker.h"
|
|
#endif
|
|
|
|
#endif //WITH_ENGINE
|
|
|
|
#if WITH_EDITOR
|
|
#include "FeedbackContextEditor.h"
|
|
static FFeedbackContextEditor UnrealEdWarn;
|
|
#include "AudioEditorModule.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
#if UE_EDITOR
|
|
#include "DesktopPlatformModule.h"
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "LaunchEngineLoop"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "AllowWindowsPlatformTypes.h"
|
|
#include <ObjBase.h>
|
|
#include "HideWindowsPlatformTypes.h"
|
|
#endif
|
|
|
|
#if ENABLE_VISUAL_LOG
|
|
#include "VisualLogger/VisualLogger.h"
|
|
#endif
|
|
|
|
#if WITH_LAUNCHERCHECK
|
|
#include "LauncherCheck.h"
|
|
#endif
|
|
|
|
#if WITH_COREUOBJECT
|
|
#ifndef USE_LOCALIZED_PACKAGE_CACHE
|
|
#define USE_LOCALIZED_PACKAGE_CACHE 1
|
|
#endif
|
|
#else
|
|
#define USE_LOCALIZED_PACKAGE_CACHE 0
|
|
#endif
|
|
|
|
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksRandomize(
|
|
TEXT("tick.DoAsyncEndOfFrameTasks.Randomize"),
|
|
0,
|
|
TEXT("Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread.")
|
|
);
|
|
|
|
static 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
|
|
);
|
|
|
|
/** Task that executes concurrently with Slate when tick.DoAsyncEndOfFrameTasks is true. */
|
|
class FExecuteConcurrentWithSlateTickTask
|
|
{
|
|
TFunctionRef<void()> TickWithSlate;
|
|
|
|
public:
|
|
|
|
FExecuteConcurrentWithSlateTickTask(TFunctionRef<void()> InTickWithSlate)
|
|
: TickWithSlate(InTickWithSlate)
|
|
{
|
|
}
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
// Pipe output to std output
|
|
// This enables UBT to collect the output for it's own use
|
|
class FOutputDeviceStdOutput : public FOutputDevice
|
|
{
|
|
public:
|
|
|
|
FOutputDeviceStdOutput()
|
|
{
|
|
bAllowLogVerbosity = FParse::Param(FCommandLine::Get(), TEXT("AllowStdOutLogVerbosity"));
|
|
}
|
|
|
|
virtual ~FOutputDeviceStdOutput()
|
|
{
|
|
}
|
|
|
|
virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override
|
|
{
|
|
if ( (bAllowLogVerbosity && Verbosity <= ELogVerbosity::Log) || (Verbosity <= ELogVerbosity::Display) )
|
|
{
|
|
#if PLATFORM_USE_LS_SPEC_FOR_WIDECHAR
|
|
wprintf(TEXT("\n%ls"), *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
|
|
#else
|
|
wprintf(TEXT("\n%s"), *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
|
|
#endif
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool bAllowLogVerbosity;
|
|
};
|
|
|
|
|
|
// Exits the game/editor if any of the specified phrases appears in the log output
|
|
class FOutputDeviceTestExit : public FOutputDevice
|
|
{
|
|
TArray<FString> ExitPhrases;
|
|
public:
|
|
FOutputDeviceTestExit(const TArray<FString>& InExitPhrases)
|
|
: ExitPhrases(InExitPhrases)
|
|
{
|
|
}
|
|
virtual ~FOutputDeviceTestExit()
|
|
{
|
|
}
|
|
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
|
|
{
|
|
if (!GIsRequestingExit)
|
|
{
|
|
for (auto& Phrase : ExitPhrases)
|
|
{
|
|
if (FCString::Stristr(V, *Phrase) && !FCString::Stristr(V, TEXT("-testexit=")))
|
|
{
|
|
#if WITH_ENGINE
|
|
if (GEngine != nullptr)
|
|
{
|
|
if (GIsEditor)
|
|
{
|
|
GEngine->DeferredCommands.Add(TEXT("CLOSE_SLATE_MAINFRAME"));
|
|
}
|
|
else
|
|
{
|
|
GEngine->Exec(nullptr, TEXT("QUIT"));
|
|
}
|
|
}
|
|
#else
|
|
FPlatformMisc::RequestExit(true);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
static TScopedPointer<FOutputDeviceConsole> GScopedLogConsole;
|
|
static TScopedPointer<FOutputDeviceStdOutput> GScopedStdOut;
|
|
static TScopedPointer<FOutputDeviceTestExit> GScopedTestExit;
|
|
|
|
|
|
#if WITH_ENGINE
|
|
static void RHIExitAndStopRHIThread()
|
|
{
|
|
#if HAS_GPU_STATS
|
|
FRealtimeGPUProfiler::Get()->Release();
|
|
#endif
|
|
RHIExit();
|
|
|
|
// Stop the RHI Thread
|
|
if (GUseRHIThread)
|
|
{
|
|
DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread Finish"), STAT_WaitForRHIThreadFinish, STATGROUP_TaskGraphTasks);
|
|
FGraphEventRef QuitTask = TGraphTask<FReturnGraphTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(ENamedThreads::RHIThread);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(QuitTask, ENamedThreads::GameThread_Local);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Initializes std out device and adds it to GLog
|
|
**/
|
|
void InitializeStdOutDevice()
|
|
{
|
|
// Check if something is trying to initialize std out device twice.
|
|
check(!GScopedStdOut.IsValid());
|
|
|
|
GScopedStdOut = new FOutputDeviceStdOutput();
|
|
GLog->AddOutputDevice(GScopedStdOut.GetOwnedPointer());
|
|
}
|
|
|
|
|
|
bool ParseGameProjectFromCommandLine(const TCHAR* InCmdLine, FString& OutProjectFilePath, FString& OutGameName)
|
|
{
|
|
const TCHAR *CmdLine = InCmdLine;
|
|
FString FirstCommandLineToken = FParse::Token(CmdLine, 0);
|
|
|
|
// trim any whitespace at edges of string - this can happen if the token was quoted with leading or trailing whitespace
|
|
// VC++ tends to do this in its "external tools" config
|
|
FirstCommandLineToken = FirstCommandLineToken.Trim();
|
|
|
|
//
|
|
OutProjectFilePath = TEXT("");
|
|
OutGameName = TEXT("");
|
|
|
|
if ( FirstCommandLineToken.Len() && !FirstCommandLineToken.StartsWith(TEXT("-")) )
|
|
{
|
|
// The first command line argument could be the project file if it exists or the game name if not launching with a project file
|
|
const FString ProjectFilePath = FString(FirstCommandLineToken);
|
|
if ( FPaths::GetExtension(ProjectFilePath) == FProjectDescriptor::GetExtension() )
|
|
{
|
|
OutProjectFilePath = FirstCommandLineToken;
|
|
// Here we derive the game name from the project file
|
|
OutGameName = FPaths::GetBaseFilename(OutProjectFilePath);
|
|
return true;
|
|
}
|
|
else if (FPaths::IsRelative(FirstCommandLineToken) && FPlatformProperties::IsMonolithicBuild() == false)
|
|
{
|
|
// Full game name is assumed to be the first token
|
|
OutGameName = MoveTemp(FirstCommandLineToken);
|
|
// Derive the project path from the game name. All games must have a uproject file, even if they are in the root folder.
|
|
OutProjectFilePath = FPaths::Combine(*FPaths::RootDir(), *OutGameName, *FString(OutGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (FEditorCommandLineUtils::ParseGameProjectPath(InCmdLine, OutProjectFilePath, OutGameName))
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LaunchSetGameName(const TCHAR *InCmdLine, FString& OutGameProjectFilePathUnnormalized)
|
|
{
|
|
if (GIsGameAgnosticExe)
|
|
{
|
|
// Initialize GameName to an empty string. Populate it below.
|
|
FApp::SetGameName(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::SetGameName(*LocalGameName);
|
|
}
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
#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 = LocalGameName.Left(FirstCharToRemove);
|
|
}
|
|
FApp::SetGameName(*LocalGameName);
|
|
|
|
// Check it's not UE4Game, otherwise assume a uproject file relative to the game project directory
|
|
if (LocalGameName != TEXT("UE4Game"))
|
|
{
|
|
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::HasGameName())
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("Running engine for game: %s"), FApp::GetGameName());
|
|
}
|
|
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::GetGameName())
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games cannot load projects on cooked platforms - try running UE4Game."));
|
|
}
|
|
}
|
|
// Only set the game name if this is NOT a program...
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
FApp::SetGameName(*LocalGameName);
|
|
}
|
|
OutGameProjectFilePathUnnormalized = ProjFilePath;
|
|
FPaths::SetProjectFilePath(ProjFilePath);
|
|
}
|
|
|
|
// In a non-game agnostic exe, the game name should already be assigned by now.
|
|
if (!FApp::HasGameName())
|
|
{
|
|
UE_LOG(LogInit, Fatal,TEXT("Could not set game name!"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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::HasGameName());
|
|
|
|
// 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(IFileManager::Get().GetFilenameOnDisk(*FPaths::GetProjectFilePath())));
|
|
|
|
const bool bGameNameMatchesProjectCaseSensitive = (FCString::Strcmp(*GameName, FApp::GetGameName()) == 0);
|
|
if (!bGameNameMatchesProjectCaseSensitive && (FApp::IsGameNameEmpty() || GIsGameAgnosticExe || (GameName.Len() > 0 && GIsGameAgnosticExe)))
|
|
{
|
|
if (GameName == FApp::GetGameName()) // case insensitive compare
|
|
{
|
|
FApp::SetGameName(*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::GetGameName()));
|
|
if (!GIsBuildMachine)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("%s"), *Message.ToString());
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
}
|
|
FApp::SetGameName(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();
|
|
|
|
// Try to create pak file wrapper
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
PlatformFile = ConditionallyCreateFileWrapper(TEXT("CachedReadFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
// Try to create sandbox wrapper
|
|
{
|
|
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SandboxFile"), CurrentPlatformFile, CmdLine);
|
|
if (PlatformFile)
|
|
{
|
|
CurrentPlatformFile = PlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING // UFS clients are not available in shipping builds.
|
|
// Streaming network wrapper (it has a priority over normal network wrapper)
|
|
bool bNetworkFailedToInitialize = false;
|
|
do
|
|
{
|
|
bool bShouldUseStreamingFile = false;
|
|
IPlatformFile* NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("StreamingFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize, &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, &bNetworkFailedToInitialize);
|
|
if (NetworkPlatformFile)
|
|
{
|
|
CurrentPlatformFile = NetworkPlatformFile;
|
|
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
|
|
}
|
|
}
|
|
|
|
|
|
if (bNetworkFailedToInitialize)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
while (bNetworkFailedToInitialize);
|
|
#endif
|
|
|
|
#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;
|
|
}
|
|
|
|
|
|
bool LaunchHasIncompleteGameName()
|
|
{
|
|
if ( FApp::HasGameName() && !FPaths::IsProjectFilePathSet() )
|
|
{
|
|
// Verify this is a legitimate game name
|
|
// Launched with a game name. See if the <GameName> folder exists. If it doesn't, it could instead be <GameName>Game
|
|
const FString NonSuffixedGameFolder = FPaths::RootDir() / FApp::GetGameName();
|
|
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::SetGameName(*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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
FEngineLoop implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
FEngineLoop::FEngineLoop()
|
|
#if WITH_ENGINE
|
|
: EngineService(nullptr)
|
|
#endif
|
|
{ }
|
|
|
|
|
|
int32 FEngineLoop::PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline)
|
|
{
|
|
FMemory::SetupTLSCachesOnCurrentThread();
|
|
|
|
FString CmdLine;
|
|
|
|
// loop over the parameters, skipping the first one (which is the executable name)
|
|
for (int32 Arg = 1; Arg < ArgC; Arg++)
|
|
{
|
|
FString ThisArg = ArgV[Arg];
|
|
if (ThisArg.Contains(TEXT(" ")) && !ThisArg.Contains(TEXT("\"")))
|
|
{
|
|
int32 EqualsAt = ThisArg.Find(TEXT("="));
|
|
if (EqualsAt > 0 && ThisArg.Find(TEXT(" ")) > EqualsAt)
|
|
{
|
|
ThisArg = ThisArg.Left(EqualsAt + 1) + FString("\"") + ThisArg.RightChop(EqualsAt + 1) + FString("\"");
|
|
|
|
}
|
|
else
|
|
{
|
|
ThisArg = FString("\"") + ThisArg + FString("\"");
|
|
}
|
|
}
|
|
|
|
CmdLine += ThisArg;
|
|
// put a space between each argument (not needed after the end)
|
|
if (Arg + 1 < ArgC)
|
|
{
|
|
CmdLine += TEXT(" ");
|
|
}
|
|
}
|
|
|
|
// append the additional extra command line
|
|
if (AdditionalCommandline)
|
|
{
|
|
CmdLine += TEXT(" ");
|
|
CmdLine += 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)
|
|
{
|
|
FWorldContext& WorldContext = GEngine->GetWorldContextFromHandleChecked(WorldContextHandle);
|
|
check(WorldContext.WorldType == EWorldType::Game || WorldContext.WorldType == EWorldType::PIE);
|
|
World = WorldContext.World();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ensure(WorldContextHandle == NAME_None);
|
|
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
|
|
|
|
if (GameEngine)
|
|
{
|
|
World = GameEngine->GetGameWorld();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Failed to determine if OSS is server in PIE, OSS requests will fail"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ENetMode NetMode = World ? World->GetNetMode() : NM_Standalone;
|
|
return (NetMode == NM_ListenServer || NetMode == NM_DedicatedServer);
|
|
}
|
|
#endif
|
|
|
|
DECLARE_CYCLE_STAT( TEXT( "FEngineLoop::PreInit.AfterStats" ), STAT_FEngineLoop_PreInit_AfterStats, STATGROUP_LoadTime );
|
|
|
|
int32 FEngineLoop::PreInit( const TCHAR* CmdLine )
|
|
{
|
|
if (FParse::Param(CmdLine, TEXT("UTF8Output")))
|
|
{
|
|
FPlatformMisc::SetUTF8Output();
|
|
}
|
|
|
|
// Switch into executable's directory.
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
|
|
// this is set later with shorter command lines, but we want to make sure it is set ASAP as some subsystems will do the tests themselves...
|
|
// also realize that command lines can be pulled from the network at a slightly later time.
|
|
if (!FCommandLine::Set(CmdLine))
|
|
{
|
|
// Fail, shipping builds will crash if setting command line fails
|
|
return -1;
|
|
}
|
|
|
|
#if WITH_LAUNCHERCHECK
|
|
if (ILauncherCheckModule::Get().WasRanFromLauncher() == false)
|
|
{
|
|
// Tell Launcher to run us instead
|
|
ILauncherCheckModule::Get().RunLauncher(ELauncherAction::AppLaunch);
|
|
// We wish to exit
|
|
GIsRequestingExit = true;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if STATS
|
|
// Create the stats malloc profiler proxy.
|
|
if( FStatsMallocProfilerProxy::HasMemoryProfilerToken() )
|
|
{
|
|
if (PLATFORM_USES_FIXED_GMalloc_CLASS)
|
|
{
|
|
UE_LOG(LogMemory, Fatal, TEXT("Cannot do malloc profiling with PLATFORM_USES_FIXED_GMalloc_CLASS."));
|
|
}
|
|
// Assumes no concurrency here.
|
|
GMalloc = FStatsMallocProfilerProxy::Get();
|
|
}
|
|
#endif // STATS
|
|
|
|
// Name of project file before normalization (as specified in command line).
|
|
// Used to fixup project name if necessary.
|
|
FString GameProjectFilePathUnnormalized;
|
|
|
|
// Set GameName, based on the command line
|
|
if (LaunchSetGameName(CmdLine, GameProjectFilePathUnnormalized) == false)
|
|
{
|
|
// If it failed, do not continue
|
|
return 1;
|
|
}
|
|
|
|
// Initialize log console here to avoid statics initialization issues when launched from the command line.
|
|
GScopedLogConsole = FPlatformOutputDevices::GetLogConsole();
|
|
|
|
// Always enable the backlog so we get all messages, we will disable and clear it in the game
|
|
// as soon as we determine whether GIsEditor == false
|
|
GLog->EnableBacklog(true);
|
|
|
|
// Initialize std out device as early as possible if requested in the command line
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("stdout")))
|
|
{
|
|
InitializeStdOutDevice();
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (FPlatformProperties::SupportsQuit())
|
|
{
|
|
FString ExitPhrases;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("testexit="), ExitPhrases))
|
|
{
|
|
TArray<FString> ExitPhrasesList;
|
|
if (ExitPhrases.ParseIntoArray(ExitPhrasesList, TEXT("+"), true) > 0)
|
|
{
|
|
GScopedTestExit = new FOutputDeviceTestExit(ExitPhrasesList);
|
|
GLog->AddOutputDevice(GScopedTestExit.GetOwnedPointer());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("emitdrawevents")))
|
|
{
|
|
GEmitDrawEvents = true;
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// Switch into executable's directory (may be required by some of the platform file overrides)
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
|
|
// This fixes up the relative project path, needs to happen before we set platform file paths
|
|
if (FPlatformProperties::IsProgram() == false)
|
|
{
|
|
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"), *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::GetGameName(), 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// allow the command line to override the platform file singleton
|
|
bool bFileOverrideFound = false;
|
|
if (LaunchCheckForFileOverride(CmdLine, bFileOverrideFound) == false)
|
|
{
|
|
// if it failed, we cannot continue
|
|
return 1;
|
|
}
|
|
|
|
// Initialize file manager
|
|
IFileManager::Get().ProcessCommandLineOptions();
|
|
|
|
if( GIsGameAgnosticExe )
|
|
{
|
|
// If we launched without a project file, but with a game name that is incomplete, warn about the improper use of a Game suffix
|
|
if ( LaunchHasIncompleteGameName() )
|
|
{
|
|
// We did not find a non-suffixed folder and we DID find the suffixed one.
|
|
// The engine MUST be launched with <GameName>Game.
|
|
const FText GameNameText = FText::FromString(FApp::GetGameName());
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format( LOCTEXT("RequiresGamePrefix", "Error: UE4Editor does not append 'Game' to the passed in game name.\nYou must use the full name.\nYou specified '{0}', use '{0}Game'."), GameNameText ) );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// remember thread id of the main thread
|
|
GGameThreadId = FPlatformTLS::GetCurrentThreadId();
|
|
GIsGameThreadIdInitialized = true;
|
|
|
|
FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetMainGameMask());
|
|
FPlatformProcess::SetupGameThread();
|
|
|
|
// Figure out whether we're the editor, ucc or the game.
|
|
const SIZE_T CommandLineSize = FCString::Strlen(CmdLine)+1;
|
|
TCHAR* CommandLineCopy = new TCHAR[ CommandLineSize ];
|
|
FCString::Strcpy( CommandLineCopy, CommandLineSize, CmdLine );
|
|
const TCHAR* ParsedCmdLine = CommandLineCopy;
|
|
|
|
FString Token = FParse::Token( ParsedCmdLine, 0);
|
|
|
|
#if WITH_ENGINE
|
|
TArray<FString> Tokens;
|
|
TArray<FString> Switches;
|
|
UCommandlet::ParseCommandLine(CommandLineCopy, Tokens, Switches);
|
|
|
|
bool bHasCommandletToken = false;
|
|
|
|
for( int32 TokenIndex = 0; TokenIndex < Tokens.Num(); ++TokenIndex )
|
|
{
|
|
if( Tokens[TokenIndex].EndsWith(TEXT("Commandlet")) )
|
|
{
|
|
bHasCommandletToken = true;
|
|
Token = Tokens[TokenIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( int32 SwitchIndex = 0; SwitchIndex < Switches.Num() && !bHasCommandletToken; ++SwitchIndex )
|
|
{
|
|
if( Switches[SwitchIndex].StartsWith(TEXT("RUN=")) )
|
|
{
|
|
bHasCommandletToken = true;
|
|
Token = Switches[SwitchIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHasCommandletToken)
|
|
{
|
|
// will be reset later once the commandlet class loaded
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
}
|
|
|
|
#endif // WITH_ENGINE
|
|
|
|
|
|
// trim any whitespace at edges of string - this can happen if the token was quoted with leading or trailing whitespace
|
|
// VC++ tends to do this in its "external tools" config
|
|
Token = Token.Trim();
|
|
|
|
// Path returned by FPaths::GetProjectFilePath() is normalized, so may have symlinks and ~ resolved and may differ from the original path to .uproject passed in the command line
|
|
FString NormalizedToken = Token;
|
|
FPaths::NormalizeFilename(NormalizedToken);
|
|
|
|
const bool bFirstTokenIsGameName = (FApp::HasGameName() && Token == FApp::GetGameName());
|
|
const bool bFirstTokenIsGameProjectFilePath = (FPaths::IsProjectFilePathSet() && NormalizedToken == FPaths::GetProjectFilePath());
|
|
const bool bFirstTokenIsGameProjectFileShortName = (FPaths::IsProjectFilePathSet() && Token == FPaths::GetCleanFilename(FPaths::GetProjectFilePath()));
|
|
|
|
if (bFirstTokenIsGameName || bFirstTokenIsGameProjectFilePath || bFirstTokenIsGameProjectFileShortName)
|
|
{
|
|
// first item on command line was the game name, remove it in all cases
|
|
FString RemainingCommandline = ParsedCmdLine;
|
|
FCString::Strcpy( CommandLineCopy, CommandLineSize, *RemainingCommandline );
|
|
ParsedCmdLine = CommandLineCopy;
|
|
|
|
// Set a new command-line that doesn't include the game name as the first argument
|
|
FCommandLine::Set(ParsedCmdLine);
|
|
|
|
Token = FParse::Token( ParsedCmdLine, 0);
|
|
Token = Token.Trim();
|
|
|
|
// if the next token is a project file, then we skip it (which can happen on some platforms that combine
|
|
// commandlines... this handles extra .uprojects, but if you run with MyGame MyGame, we can't tell if
|
|
// the second MyGame is a map or not)
|
|
while (FPaths::GetExtension(Token) == FProjectDescriptor::GetExtension())
|
|
{
|
|
Token = FParse::Token(ParsedCmdLine, 0);
|
|
Token = Token.Trim();
|
|
}
|
|
|
|
if (bFirstTokenIsGameProjectFilePath || bFirstTokenIsGameProjectFileShortName)
|
|
{
|
|
// Convert it to relative if possible...
|
|
FString RelativeGameProjectFilePath = FFileManagerGeneric::DefaultConvertToRelativePath(*FPaths::GetProjectFilePath());
|
|
if (RelativeGameProjectFilePath != FPaths::GetProjectFilePath())
|
|
{
|
|
FPaths::SetProjectFilePath(RelativeGameProjectFilePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// look early for the editor token
|
|
bool bHasEditorToken = false;
|
|
|
|
#if UE_EDITOR
|
|
// Check each token for '-game', '-server' or '-run='
|
|
bool bIsNotEditor = false;
|
|
|
|
// This isn't necessarily pretty, but many requests have been made to allow
|
|
// UE4Editor.exe <GAMENAME> -game <map>
|
|
// or
|
|
// UE4Editor.exe <GAMENAME> -game 127.0.0.0
|
|
// We don't want to remove the -game from the commandline just yet in case
|
|
// we need it for something later. So, just move it to the end for now...
|
|
const bool bFirstTokenIsGame = (Token == TEXT("-GAME"));
|
|
const bool bFirstTokenIsServer = (Token == TEXT("-SERVER"));
|
|
const bool bFirstTokenIsModeOverride = bFirstTokenIsGame || bFirstTokenIsServer || bHasCommandletToken;
|
|
const TCHAR* CommandletCommandLine = nullptr;
|
|
if (bFirstTokenIsModeOverride)
|
|
{
|
|
bIsNotEditor = true;
|
|
if (bFirstTokenIsGame || bFirstTokenIsServer)
|
|
{
|
|
// Move the token to the end of the list...
|
|
FString RemainingCommandline = ParsedCmdLine;
|
|
RemainingCommandline = RemainingCommandline.Trim();
|
|
RemainingCommandline += FString::Printf(TEXT(" %s"), *Token);
|
|
FCommandLine::Set(*RemainingCommandline);
|
|
}
|
|
if (bHasCommandletToken)
|
|
{
|
|
#if STATS
|
|
// Leave the stats enabled.
|
|
if (!FStats::EnabledForCommandlet())
|
|
{
|
|
FThreadStats::MasterDisableForever();
|
|
}
|
|
#endif
|
|
if (Token.StartsWith(TEXT("run=")))
|
|
{
|
|
Token = Token.RightChop(4);
|
|
if (!Token.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
Token += TEXT("Commandlet");
|
|
}
|
|
}
|
|
CommandletCommandLine = ParsedCmdLine;
|
|
}
|
|
}
|
|
|
|
if (bHasCommandletToken)
|
|
{
|
|
// will be reset later once the commandlet class loaded
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
}
|
|
|
|
if( !bIsNotEditor && GIsGameAgnosticExe )
|
|
{
|
|
// If we launched 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::HasGameName() && !bFileOverrideFound && !bIsBuildMachine && !FParse::Param( CmdLine, TEXT("norecentproject") );
|
|
if (bLoadMostRecentProjectFileIfItExists )
|
|
{
|
|
LaunchUpdateMostRecentProjectFile();
|
|
}
|
|
}
|
|
|
|
FString CheckToken = Token;
|
|
bool bFoundValidToken = false;
|
|
while (!bFoundValidToken && (CheckToken.Len() > 0))
|
|
{
|
|
if (!bIsNotEditor)
|
|
{
|
|
bool bHasNonEditorToken = (CheckToken == TEXT("-GAME")) || (CheckToken == TEXT("-SERVER")) || (CheckToken.StartsWith(TEXT("RUN="))) || CheckToken.EndsWith(TEXT("Commandlet"));
|
|
if (bHasNonEditorToken)
|
|
{
|
|
bIsNotEditor = true;
|
|
bFoundValidToken = true;
|
|
}
|
|
}
|
|
|
|
CheckToken = FParse::Token(ParsedCmdLine, 0);
|
|
}
|
|
|
|
bHasEditorToken = !bIsNotEditor;
|
|
#elif WITH_ENGINE
|
|
const TCHAR* CommandletCommandLine = nullptr;
|
|
if (bHasCommandletToken)
|
|
{
|
|
#if STATS
|
|
// Leave the stats enabled.
|
|
if (!FStats::EnabledForCommandlet())
|
|
{
|
|
FThreadStats::MasterDisableForever();
|
|
}
|
|
#endif
|
|
if (Token.StartsWith(TEXT("run=")))
|
|
{
|
|
Token = Token.RightChop(4);
|
|
if (!Token.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
Token += TEXT("Commandlet");
|
|
}
|
|
}
|
|
CommandletCommandLine = ParsedCmdLine;
|
|
}
|
|
#if 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?
|
|
bHasEditorToken = Token == TEXT("EDITOR");
|
|
#else
|
|
// Game, server and commandlets never set the editor token
|
|
bHasEditorToken = false;
|
|
#endif
|
|
#endif //UE_EDITOR
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Benchmarking.
|
|
FApp::SetBenchmarking(FParse::Param(FCommandLine::Get(),TEXT("BENCHMARK")));
|
|
#else
|
|
FApp::SetBenchmarking(false);
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// "-Deterministic" is a shortcut for "-UseFixedTimeStep -FixedSeed"
|
|
bool bDeterministic = FParse::Param(FCommandLine::Get(), TEXT("Deterministic"));
|
|
|
|
FApp::SetUseFixedTimeStep(bDeterministic || FParse::Param(FCommandLine::Get(), TEXT("UseFixedTimeStep")));
|
|
|
|
FApp::bUseFixedSeed = bDeterministic || FApp::IsBenchmarking() || FParse::Param(FCommandLine::Get(),TEXT("FixedSeed"));
|
|
|
|
// Initialize random number generator.
|
|
{
|
|
uint32 Seed1 = 0;
|
|
uint32 Seed2 = 0;
|
|
|
|
if(!FApp::bUseFixedSeed)
|
|
{
|
|
Seed1 = FPlatformTime::Cycles();
|
|
Seed2 = FPlatformTime::Cycles();
|
|
}
|
|
|
|
FMath::RandInit(Seed1);
|
|
FMath::SRandInit(Seed2);
|
|
|
|
UE_LOG(LogInit, Display, TEXT("RandInit(%d) SRandInit(%d)."), Seed1, Seed2);
|
|
}
|
|
|
|
// Set up the module list and version information, if it's not compiled-in
|
|
#if !IS_MONOLITHIC && BUILT_FROM_CHANGELIST == 0
|
|
static FVersionedModuleEnumerator ModuleEnumerator;
|
|
if(ModuleEnumerator.RegisterWithModuleManager())
|
|
{
|
|
const FVersionManifest& Manifest = ModuleEnumerator.GetInitialManifest();
|
|
if(Manifest.Changelist != 0 && !FEngineVersion::OverrideCurrentVersionChangelist(Manifest.Changelist, Manifest.CompatibleChangelist))
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Couldn't update engine changelist to %d."), Manifest.Changelist);
|
|
}
|
|
UE_LOG(LogInit, Log, TEXT("Using version manifest at CL %d with build ID '%s'"), Manifest.Changelist, *Manifest.BuildId);
|
|
}
|
|
#endif
|
|
|
|
#if !IS_PROGRAM
|
|
if ( !GIsGameAgnosticExe && FApp::HasGameName() && !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::GameDir(), *FString::Printf(TEXT("%s.%s"), FApp::GetGameName(), *FProjectDescriptor::GetExtension()));
|
|
FPaths::SetProjectFilePath(ProjectFilePath);
|
|
}
|
|
#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
|
|
)
|
|
{
|
|
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 !IS_PROGRAM
|
|
if( FApp::HasGameName() )
|
|
{
|
|
// Tell the module manager what the game binaries folder is
|
|
const FString GameBinariesDirectory = FPaths::Combine( FPlatformMisc::GameDir(), TEXT( "Binaries" ), FPlatformProcess::GetBinariesSubdirectory() );
|
|
FModuleManager::Get().SetGameBinariesDirectory(*GameBinariesDirectory);
|
|
|
|
LaunchFixGameNameCase();
|
|
}
|
|
#endif
|
|
|
|
// initialize task graph sub-system with potential multiple threads
|
|
FTaskGraphInterface::Startup( FPlatformMisc::NumberOfCores() );
|
|
FTaskGraphInterface::Get().AttachToThread( ENamedThreads::GameThread );
|
|
|
|
#if STATS
|
|
FThreadStats::StartThread();
|
|
#endif
|
|
|
|
FScopeCycleCounter CycleCount_AfterStats( GET_STATID( STAT_FEngineLoop_PreInit_AfterStats ) );
|
|
|
|
// Load Core modules required for everything else to work (needs to be loaded before InitializeRenderingCVarsCaching)
|
|
if (!LoadCoreModules())
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Failed to load Core modules."));
|
|
return 1;
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
extern ENGINE_API void InitializeRenderingCVarsCaching();
|
|
InitializeRenderingCVarsCaching();
|
|
#endif
|
|
|
|
bool bTokenDoesNotHaveDash = Token.Len() && FCString::Strnicmp(*Token, TEXT("-"), 1) != 0;
|
|
|
|
#if WITH_EDITOR
|
|
// If we're running as an game 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", "UE4RequiresProjectFiles", "UE4 games require a project file as the first parameter."));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (GIsUCCMakeStandaloneHeaderGenerator)
|
|
{
|
|
// Rebuilding script requires some hacks in the engine so we flag that.
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
{
|
|
GThreadPool = FQueuedThreadPool::Allocate();
|
|
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
|
|
|
|
// we are only going to give dedicated servers one pool thread
|
|
if (FPlatformProperties::IsServerOnly())
|
|
{
|
|
NumThreadsInThreadPool = 1;
|
|
}
|
|
verify(GThreadPool->Create(NumThreadsInThreadPool, 128 * 1024));
|
|
}
|
|
#if USE_NEW_ASYNC_IO
|
|
{
|
|
GIOThreadPool = FQueuedThreadPool::Allocate();
|
|
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfIOWorkerThreadsToSpawn();
|
|
if (FPlatformProperties::IsServerOnly())
|
|
{
|
|
NumThreadsInThreadPool = 2;
|
|
}
|
|
verify(GIOThreadPool->Create(NumThreadsInThreadPool, 16 * 1024, TPri_AboveNormal));
|
|
}
|
|
#endif // USE_NEW_ASYNC_IO
|
|
|
|
#if WITH_EDITOR
|
|
// 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 = FQueuedThreadPool::Allocate();
|
|
int32 NumThreadsInLargeThreadPool = FMath::Max(FPlatformMisc::NumberOfCoresIncludingHyperthreads() - 2, 2);
|
|
|
|
verify(GLargeThreadPool->Create(NumThreadsInLargeThreadPool, 128 * 1024));
|
|
#endif
|
|
}
|
|
|
|
// Get a pointer to the log output device
|
|
GLogConsole = GScopedLogConsole.GetOwnedPointer();
|
|
|
|
LoadPreInitModules();
|
|
|
|
// Start the application
|
|
if(!AppInit())
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
// Initialize system settings before anyone tries to use it...
|
|
GSystemSettings.Initialize( bHasEditorToken );
|
|
|
|
// Apply renderer settings from console variables stored in the INI.
|
|
ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererSettings"),*GEngineIni, ECVF_SetByProjectSetting);
|
|
ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererOverrideSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
ApplyCVarSettingsFromIni(TEXT("/Script/Engine.StreamingSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
ApplyCVarSettingsFromIni(TEXT("/Script/Engine.GarbageCollectionSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
ApplyCVarSettingsFromIni(TEXT("/Script/Engine.NetworkSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
|
|
#if !UE_SERVER
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
if (!IsRunningCommandlet())
|
|
{
|
|
// 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
|
|
|
|
// 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.
|
|
// before InitializeCVarsForActiveDeviceProfile() so the platform can override user settings
|
|
Scalability::LoadState((bHasEditorToken && !GEditorSettingsIni.IsEmpty()) ? GEditorSettingsIni : GGameUserSettingsIni);
|
|
|
|
// Set all CVars which have been setup in the device profiles.
|
|
UDeviceProfileManager::InitializeCVarsForActiveDeviceProfile();
|
|
|
|
if (FApp::ShouldUseThreadingForPerformance() && FPlatformMisc::AllowRenderThread())
|
|
{
|
|
GUseThreadedRendering = true;
|
|
}
|
|
#endif
|
|
|
|
FConfigCacheIni::LoadConsoleVariablesFromINI();
|
|
|
|
{
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Platform Initialization"), STAT_PlatformInit, STATGROUP_LoadTime);
|
|
|
|
// platform specific initialization now that the SystemSettings are loaded
|
|
FPlatformMisc::PlatformInit();
|
|
FPlatformMemory::Init();
|
|
}
|
|
|
|
// 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.").ToString() );
|
|
FPlatformMisc::RequestExit(false);
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
// allow for game explorer processing (including parental controls) and firewalls installation
|
|
if (!FPlatformMisc::CommandLineCommands())
|
|
{
|
|
FPlatformMisc::RequestExit(false);
|
|
}
|
|
|
|
bool bIsSeekFreeDedicatedServer = false;
|
|
bool bIsRegularClient = false;
|
|
|
|
if (!bHasEditorToken)
|
|
{
|
|
// See whether the first token on the command line is a commandlet.
|
|
|
|
//@hack: We need to set these before calling StaticLoadClass so all required data gets loaded for the commandlets.
|
|
GIsClient = true;
|
|
GIsServer = true;
|
|
#if WITH_EDITOR
|
|
GIsEditor = true;
|
|
#endif //WITH_EDITOR
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
|
|
// 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"));
|
|
|
|
// We need to disregard the empty token as we try finding Token + "Commandlet" which would result in finding the
|
|
// UCommandlet class if Token is empty.
|
|
bool bDefinitelyCommandlet = (bTokenDoesNotHaveDash && Token.EndsWith(TEXT("Commandlet")));
|
|
if (!bTokenDoesNotHaveDash)
|
|
{
|
|
if (Token.StartsWith(TEXT("run=")))
|
|
{
|
|
Token = Token.RightChop(4);
|
|
bDefinitelyCommandlet = true;
|
|
if (!Token.EndsWith(TEXT("Commandlet")))
|
|
{
|
|
Token += TEXT("Commandlet");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bDefinitelyCommandlet)
|
|
{
|
|
UClass* TempCommandletClass = FindObject<UClass>(ANY_PACKAGE, *(Token+TEXT("Commandlet")), false);
|
|
|
|
if (TempCommandletClass)
|
|
{
|
|
check(TempCommandletClass->IsChildOf(UCommandlet::StaticClass())); // ok so you have a class that ends with commandlet that is not a commandlet
|
|
|
|
Token += TEXT("Commandlet");
|
|
bDefinitelyCommandlet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bDefinitelyCommandlet)
|
|
{
|
|
bIsRegularClient = true;
|
|
GIsClient = true;
|
|
GIsServer = false;
|
|
#if WITH_EDITORONLY_DATA
|
|
GIsEditor = false;
|
|
#endif
|
|
PRIVATE_GIsRunningCommandlet = false;
|
|
}
|
|
}
|
|
|
|
if (IsRunningDedicatedServer())
|
|
{
|
|
GIsClient = false;
|
|
GIsServer = true;
|
|
PRIVATE_GIsRunningCommandlet = false;
|
|
#if WITH_EDITOR
|
|
GIsEditor = false;
|
|
#endif
|
|
bIsSeekFreeDedicatedServer = FPlatformProperties::RequiresCookedData();
|
|
}
|
|
|
|
// 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.IsValid() && !bHasEditorToken && !bIsRegularClient && !IsRunningDedicatedServer())
|
|
{
|
|
InitializeStdOutDevice();
|
|
}
|
|
|
|
#if !USE_NEW_ASYNC_IO
|
|
FIOSystem::Get(); // force it to be created if it isn't already
|
|
#endif
|
|
|
|
// allow the platform to start up any features it may need
|
|
IPlatformFeaturesModule::Get();
|
|
|
|
// Init physics engine before loading anything, in case we want to do things like cook during post-load.
|
|
InitGamePhys();
|
|
|
|
// Delete temporary files in cache.
|
|
FPlatformProcess::CleanFileCache();
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
GIsDemoMode = FParse::Param( FCommandLine::Get(), TEXT( "DEMOMODE" ) );
|
|
#endif
|
|
|
|
if (bHasEditorToken)
|
|
{
|
|
#if WITH_EDITOR
|
|
|
|
// We're the editor.
|
|
GIsClient = true;
|
|
GIsServer = true;
|
|
GIsEditor = true;
|
|
PRIVATE_GIsRunningCommandlet = false;
|
|
|
|
GWarn = &UnrealEdWarn;
|
|
|
|
#else
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("Engine", "EditorNotSupported", "Editor not supported in this mode."));
|
|
FPlatformMisc::RequestExit(false);
|
|
return 1;
|
|
#endif //WITH_EDITOR
|
|
}
|
|
|
|
#endif // WITH_ENGINE
|
|
// If we're not in the editor stop collecting the backlog now that we know
|
|
if (!GIsEditor)
|
|
{
|
|
GLog->EnableBacklog( false );
|
|
}
|
|
#if WITH_ENGINE
|
|
|
|
EndInitTextLocalization();
|
|
|
|
if (FApp::ShouldUseThreadingForPerformance() && FPlatformMisc::AllowAudioThread())
|
|
{
|
|
bool bUseThreadedAudio = false;
|
|
if (!GIsEditor)
|
|
{
|
|
GConfig->GetBool(TEXT("Audio"), TEXT("UseAudioThread"), bUseThreadedAudio, GEngineIni);
|
|
}
|
|
FAudioThread::SetUseThreadedAudio(bUseThreadedAudio);
|
|
}
|
|
|
|
if (FPlatformProcess::SupportsMultithreading() && !IsRunningDedicatedServer() && (bIsRegularClient || bHasEditorToken))
|
|
{
|
|
FPlatformSplash::Show();
|
|
}
|
|
|
|
if (!IsRunningDedicatedServer() && (bHasEditorToken || bIsRegularClient))
|
|
{
|
|
// Init platform application
|
|
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();
|
|
FCoreStyle::ResetToDefault();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
FScopedSlowTask SlowTask(100, NSLOCTEXT("EngineLoop", "EngineLoop_Initializing", "Initializing..."));
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
FPackageLocalizationManager::Get().InitializeFromLazyCallback([](FPackageLocalizationManager& InPackageLocalizationManager)
|
|
{
|
|
InPackageLocalizationManager.InitializeFromCache(MakeShareable(new FEnginePackageLocalizationCache()));
|
|
});
|
|
#endif // USE_LOCALIZED_PACKAGE_CACHE
|
|
|
|
// Initialize the RHI.
|
|
RHIInit(bHasEditorToken);
|
|
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
check(!GShaderCompilingManager);
|
|
GShaderCompilingManager = new FShaderCompilingManager();
|
|
|
|
check(!GDistanceFieldAsyncQueue);
|
|
GDistanceFieldAsyncQueue = new FDistanceFieldAsyncQueue();
|
|
}
|
|
|
|
{
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Initial UObject load"), STAT_InitialUObjectLoad, STATGROUP_LoadTime);
|
|
|
|
// Initialize shader types before loading any shaders
|
|
InitializeShaderTypes();
|
|
|
|
SlowTask.EnterProgressFrame(30);
|
|
|
|
// Load the global shaders.
|
|
// if (!IsRunningCommandlet())
|
|
// hack: don't load global shaders if we are cooking we will load the shaders for the correct platform later
|
|
FString Commandline = FCommandLine::Get();
|
|
if (!IsRunningDedicatedServer() &&
|
|
Commandline.Contains(TEXT("cookcommandlet")) == false &&
|
|
Commandline.Contains(TEXT("run=cook")) == false )
|
|
// if (FParse::Param(FCommandLine::Get(), TEXT("Multiprocess")) == false)
|
|
{
|
|
if (GetGlobalShaderMap(GMaxRHIFeatureLevel) == nullptr && GIsRequestingExit)
|
|
{
|
|
// This means we can't continue without the global shader map.
|
|
return 1;
|
|
}
|
|
}
|
|
else if (FPlatformProperties::RequiresCookedData() == false)
|
|
{
|
|
GetDerivedDataCacheRef();
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Make sure all UObject classes are registered and default properties have been initialized
|
|
ProcessNewlyLoadedUObjects();
|
|
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
// 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
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Setup GC optimizations
|
|
if (bIsSeekFreeDedicatedServer || bHasEditorToken)
|
|
{
|
|
GUObjectArray.DisableDisregardForGC();
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
if ( !LoadStartupCoreModules() )
|
|
{
|
|
// At least one startup module failed to load, return 1 to indicate an error
|
|
return 1;
|
|
}
|
|
|
|
#if !UE_SERVER// && !UE_EDITOR
|
|
if (!IsRunningDedicatedServer() && !IsRunningCommandlet())
|
|
{
|
|
TSharedRef<FSlateRenderer> SlateRenderer = GUsingNullRHI ?
|
|
FModuleManager::Get().LoadModuleChecked<ISlateNullRendererModule>("SlateNullRenderer").CreateSlateNullRenderer() :
|
|
FModuleManager::Get().GetModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer").CreateSlateRHIRenderer();
|
|
|
|
// If Slate is being used, initialize the renderer after RHIInit
|
|
FSlateApplication& CurrentSlateApp = FSlateApplication::Get();
|
|
CurrentSlateApp.InitializeRenderer( SlateRenderer );
|
|
|
|
GetMoviePlayer()->SetSlateRenderer(SlateRenderer);
|
|
}
|
|
|
|
// Create the engine font services now that the Slate renderer is ready
|
|
FEngineFontServices::Create();
|
|
#endif
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
// 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
|
|
if ( !IsRunningDedicatedServer() )
|
|
{
|
|
// @todo ps4: If a loading movie starts earlier, which it probably should, then please see PS4's PlatformPostInit() implementation!
|
|
|
|
// 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();
|
|
|
|
GetMoviePlayer()->Initialize();
|
|
GetMoviePlayer()->PlayMovie();
|
|
|
|
// do any post appInit processing, before the render thread is started.
|
|
FPlatformMisc::PlatformPostInit(!GetMoviePlayer()->IsMovieCurrentlyPlaying());
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// do any post appInit processing, before the render thread is started.
|
|
FPlatformMisc::PlatformPostInit(true);
|
|
}
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
RHIPostInit();
|
|
|
|
if (GUseThreadedRendering)
|
|
{
|
|
if (GRHISupportsRHIThread)
|
|
{
|
|
const bool DefaultUseRHIThread = true;
|
|
GUseRHIThread = DefaultUseRHIThread;
|
|
if (FParse::Param(FCommandLine::Get(),TEXT("rhithread")))
|
|
{
|
|
GUseRHIThread = true;
|
|
}
|
|
else if (FParse::Param(FCommandLine::Get(),TEXT("norhithread")))
|
|
{
|
|
GUseRHIThread = false;
|
|
}
|
|
}
|
|
StartRenderingThread();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// We need to mount the shared resources for templates (if there are any) before we try and load and game classes
|
|
FUnrealEdMisc::Get().MountTemplateSharedPaths();
|
|
#endif
|
|
|
|
if ( !LoadStartupModules() )
|
|
{
|
|
// At least one startup module failed to load, return 1 to indicate an error
|
|
return 1;
|
|
}
|
|
|
|
// load up the seek-free startup packages
|
|
if ( !FStartupPackages::LoadAll() )
|
|
{
|
|
// At least one startup package failed to load, return 1 to indicate an error
|
|
return 1;
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
#if WITH_COREUOBJECT
|
|
if (GUObjectArray.IsOpenForDisregardForGC())
|
|
{
|
|
GUObjectArray.CloseDisregardForGC();
|
|
}
|
|
#endif // WITH_COREUOBJECT
|
|
|
|
#if WITH_ENGINE
|
|
if (UOnlineEngineInterface::Get()->IsLoaded())
|
|
{
|
|
SetIsServerForOnlineSubsystemsDelegate(FQueryIsRunningServer::CreateStatic(&IsServerDelegateForOSS));
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(5);
|
|
|
|
if (!bHasEditorToken)
|
|
{
|
|
UClass* CommandletClass = nullptr;
|
|
|
|
if (!bIsRegularClient)
|
|
{
|
|
CommandletClass = FindObject<UClass>(ANY_PACKAGE,*Token,false);
|
|
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);
|
|
GIsRequestingExit = true;
|
|
return 1;
|
|
}
|
|
|
|
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
|
|
extern bool GIsConsoleExecutable;
|
|
if (GIsConsoleExecutable)
|
|
{
|
|
if (GLogConsole != nullptr && GLogConsole->IsAttached())
|
|
{
|
|
GLog->RemoveOutputDevice(GLogConsole);
|
|
}
|
|
// Setup Ctrl-C handler for console application
|
|
FPlatformMisc::SetGracefulTerminationHandler();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Bring up console unless we're a silent build.
|
|
if( GLogConsole && !GIsSilent )
|
|
{
|
|
GLogConsole->Show( true );
|
|
}
|
|
}
|
|
|
|
// print output immediately
|
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Executing %s"), *CommandletClass->GetFullName() );
|
|
|
|
// Allow commandlets to individually override those settings.
|
|
UCommandlet* Default = CastChecked<UCommandlet>(CommandletClass->GetDefaultObject());
|
|
|
|
if ( GIsRequestingExit )
|
|
{
|
|
// commandlet set GIsRequestingExit 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());
|
|
GIsRequestingExit = true;
|
|
return 1;
|
|
}
|
|
#endif
|
|
PRIVATE_GIsRunningCommandlet = true;
|
|
// Reset aux log if we don't want to log to the console window.
|
|
if( !Default->LogToConsole )
|
|
{
|
|
GLog->RemoveOutputDevice( GLogConsole );
|
|
}
|
|
|
|
// allow the commandlet the opportunity to create a custom engine
|
|
CommandletClass->GetDefaultObject<UCommandlet>()->CreateCustomEngine(CommandletCommandLine);
|
|
if ( GEngine == nullptr )
|
|
{
|
|
#if WITH_EDITOR
|
|
if ( GIsEditor )
|
|
{
|
|
FString EditorEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("EditorEngine"), EditorEngineClassName, GEngineIni);
|
|
UClass* EditorEngineClass = StaticLoadClass( UEditorEngine::StaticClass(), nullptr, *EditorEngineClassName);
|
|
if (EditorEngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load Editor Engine class '%s'."), *EditorEngineClassName);
|
|
}
|
|
|
|
GEngine = GEditor = NewObject<UEditorEngine>(GetTransientPackage(), EditorEngineClass);
|
|
|
|
GEngine->ParseCommandline();
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Editor Engine..."));
|
|
GEditor->InitEditor(this);
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Editor Engine Completed"));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
FString GameEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
|
|
|
|
UClass* EngineClass = StaticLoadClass( UEngine::StaticClass(), nullptr, *GameEngineClassName);
|
|
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load Engine class '%s'."), *GameEngineClassName);
|
|
}
|
|
|
|
// must do this here so that the engine object that we create on the next line receives the correct property values
|
|
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
|
|
check(GEngine);
|
|
|
|
GEngine->ParseCommandline();
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Game Engine..."));
|
|
GEngine->Init(this);
|
|
UE_LOG(LogInit, Log, TEXT("Initializing Game Engine Completed"));
|
|
}
|
|
}
|
|
|
|
// Load all the post-engine init modules
|
|
ensure(IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit));
|
|
ensure(IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit));
|
|
|
|
//run automation smoke tests now that the commandlet has had a chance to override the above flags and GEngine is available
|
|
#if !PLATFORM_HTML5 && !PLATFORM_HTML5_WIN32
|
|
FAutomationTestFramework::Get().RunSmokeTests();
|
|
#endif
|
|
|
|
UCommandlet* Commandlet = NewObject<UCommandlet>(GetTransientPackage(), CommandletClass);
|
|
check(Commandlet);
|
|
Commandlet->AddToRoot();
|
|
|
|
// Execute the commandlet.
|
|
double CommandletExecutionStartTime = FPlatformTime::Seconds();
|
|
|
|
// Commandlets don't always handle -run= properly in the commandline so we'll provide them
|
|
// with a custom version that doesn't have it.
|
|
Commandlet->ParseParms( CommandletCommandLine );
|
|
#if STATS
|
|
// We have to close the scope, otherwise we will end with broken stats.
|
|
CycleCount_AfterStats.StopAndResetStatId();
|
|
#endif // STATS
|
|
FStats::TickCommandletStats();
|
|
int32 ErrorLevel = Commandlet->Main( CommandletCommandLine );
|
|
FStats::TickCommandletStats();
|
|
|
|
GIsRequestingExit = true;
|
|
|
|
// Log warning/ error summary.
|
|
if( Commandlet->ShowErrorCount )
|
|
{
|
|
TArray<FString> AllErrors;
|
|
TArray<FString> AllWarnings;
|
|
GWarn->GetErrors(AllErrors);
|
|
GWarn->GetWarnings(AllWarnings);
|
|
|
|
if (AllErrors.Num() || AllWarnings.Num())
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_LOG(LogInit, Display, TEXT(""));
|
|
UE_LOG(LogInit, Display, TEXT("Warning/Error Summary (Unique only)"));
|
|
UE_LOG(LogInit, Display, TEXT("-----------------------------------"));
|
|
|
|
const int32 MaxMessagesToShow = (GIsBuildMachine || FParse::Param(FCommandLine::Get(), TEXT("DUMPALLWARNINGS"))) ?
|
|
FMath::Max(AllErrors.Num(), AllWarnings.Num()) : 50;
|
|
|
|
TSet<FString> ShownMessages;
|
|
ShownMessages.Empty(MaxMessagesToShow);
|
|
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
|
|
for (const FString& ErrorMessage : AllErrors)
|
|
{
|
|
bool bAlreadyShown = false;
|
|
ShownMessages.Add(ErrorMessage, &bAlreadyShown);
|
|
|
|
if (!bAlreadyShown)
|
|
{
|
|
if (ShownMessages.Num() > MaxMessagesToShow)
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_CLOG(MaxMessagesToShow < AllErrors.Num(), LogInit, Display, TEXT("NOTE: Only first %d errors displayed."), MaxMessagesToShow);
|
|
break;
|
|
}
|
|
|
|
UE_LOG(LogInit, Display, TEXT("%s"), *ErrorMessage);
|
|
}
|
|
}
|
|
|
|
SET_WARN_COLOR(COLOR_YELLOW);
|
|
ShownMessages.Empty(MaxMessagesToShow);
|
|
|
|
for (const FString& WarningMessage : AllWarnings)
|
|
{
|
|
bool bAlreadyShown = false;
|
|
ShownMessages.Add(WarningMessage, &bAlreadyShown);
|
|
|
|
if (!bAlreadyShown)
|
|
{
|
|
if (ShownMessages.Num() > MaxMessagesToShow)
|
|
{
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_CLOG(MaxMessagesToShow < AllWarnings.Num(), LogInit, Display, TEXT("NOTE: Only first %d warnings displayed."), MaxMessagesToShow);
|
|
break;
|
|
}
|
|
|
|
UE_LOG(LogInit, Display, TEXT("%s"), *WarningMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogInit, Display, TEXT(""));
|
|
|
|
if( ErrorLevel != 0 )
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogInit, Display, TEXT("Commandlet->Main return this error code: %d"), ErrorLevel );
|
|
UE_LOG(LogInit, Display, TEXT("With %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
}
|
|
else if( ( AllErrors.Num() == 0 ) )
|
|
{
|
|
SET_WARN_COLOR(AllWarnings.Num() ? COLOR_YELLOW : COLOR_GREEN);
|
|
UE_LOG(LogInit, Display, TEXT("Success - %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
}
|
|
else
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogInit, Display, TEXT("Failure - %d error(s), %d warning(s)"), AllErrors.Num(), AllWarnings.Num() );
|
|
ErrorLevel = 1;
|
|
}
|
|
CLEAR_WARN_COLOR();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInit, Display, TEXT("Finished.") );
|
|
}
|
|
|
|
double CommandletExecutionTime = FPlatformTime::Seconds() - CommandletExecutionStartTime;
|
|
UE_LOG(LogInit, Display, LINE_TERMINATOR TEXT( "Execution of commandlet took: %.2f seconds"), CommandletExecutionTime );
|
|
|
|
// We're ready to exit!
|
|
return ErrorLevel;
|
|
}
|
|
else
|
|
{
|
|
// We're a regular client.
|
|
check(bIsRegularClient);
|
|
|
|
if (bTokenDoesNotHaveDash)
|
|
{
|
|
// here we give people a reasonable warning if they tried to use the short name of a commandlet
|
|
UClass* TempCommandletClass = FindObject<UClass>(ANY_PACKAGE,*(Token+TEXT("Commandlet")),false);
|
|
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( GIsRequestingExit )
|
|
{
|
|
if ( GEngine != nullptr )
|
|
{
|
|
GEngine->PreExit();
|
|
}
|
|
AppPreExit();
|
|
// appExit is called outside guarded block.
|
|
return 1;
|
|
}
|
|
|
|
FString MatineeName;
|
|
|
|
if(FParse::Param(FCommandLine::Get(),TEXT("DUMPMOVIE")) || FParse::Value(FCommandLine::Get(), TEXT("-MATINEESSCAPTURE="), MatineeName))
|
|
{
|
|
// -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;
|
|
}
|
|
|
|
// 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
|
|
|
|
delete [] CommandLineCopy;
|
|
|
|
// initialize the pointer, as it is deleted before being assigned in the first frame
|
|
PendingCleanupObjects = nullptr;
|
|
|
|
// Initialize profile visualizers.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
FModuleManager::Get().LoadModule(TEXT("TaskGraph"));
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("ProfilerService"));
|
|
FModuleManager::Get().GetModuleChecked<IProfilerServiceModule>("ProfilerService").CreateProfilerServiceManager();
|
|
}
|
|
#endif
|
|
|
|
// Init HighRes screenshot system, unless running on server
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
GetHighResScreenshotConfig().Init();
|
|
}
|
|
|
|
#else // WITH_ENGINE
|
|
EndInitTextLocalization();
|
|
#if USE_LOCALIZED_PACKAGE_CACHE
|
|
FPackageLocalizationManager::Get().InitializeFromDefaultCache();
|
|
#endif // USE_LOCALIZED_PACKAGE_CACHE
|
|
FPlatformMisc::PlatformPostInit();
|
|
#endif // WITH_ENGINE
|
|
|
|
//run automation smoke tests now that everything is setup to run
|
|
FAutomationTestFramework::Get().RunSmokeTests();
|
|
|
|
// Note we still have 20% remaining on the slow task: this will be used by the Editor/Engine initialization next
|
|
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
|
|
bool bResult = FModuleManager::Get().LoadModule(TEXT("CoreUObject")).IsValid();
|
|
return bResult;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
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"));
|
|
|
|
FPlatformMisc::LoadPreInitModules();
|
|
|
|
#if !UE_SERVER
|
|
if (!IsRunningDedicatedServer() )
|
|
{
|
|
if (!GUsingNullRHI)
|
|
{
|
|
// This needs to be loaded before InitializeShaderTypes is called
|
|
FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("Landscape"));
|
|
|
|
// Initialize ShaderCore before loading or compiling any shaders,
|
|
// But after Renderer and any other modules which implement shader types.
|
|
FModuleManager::Get().LoadModule(TEXT("ShaderCore"));
|
|
|
|
#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
|
|
#endif // WITH_ENGINE
|
|
|
|
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
|
|
// Load audio editor module before engine class CDOs are loaded
|
|
FModuleManager::Get().LoadModule(TEXT("AudioEditor"));
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
#if WITH_ENGINE
|
|
|
|
bool FEngineLoop::LoadStartupCoreModules()
|
|
{
|
|
FScopedSlowTask SlowTask(100);
|
|
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Startup Modules"), STAT_StartupModules, STATGROUP_LoadTime);
|
|
|
|
bool bSuccess = true;
|
|
|
|
// Load all Runtime modules
|
|
SlowTask.EnterProgressFrame(10);
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("Core"));
|
|
FModuleManager::Get().LoadModule(TEXT("Networking"));
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
FPlatformMisc::LoadStartupModules();
|
|
|
|
// initialize messaging
|
|
SlowTask.EnterProgressFrame(10);
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
#if WITH_EDITOR
|
|
FModuleManager::LoadModuleChecked<IEditorStyleModule>("EditorStyle");
|
|
#endif //WITH_EDITOR
|
|
|
|
// Load UI modules
|
|
SlowTask.EnterProgressFrame(10);
|
|
if ( !IsRunningDedicatedServer() )
|
|
{
|
|
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
|
|
// In dedicated server builds with the editor, we need to load UMG/UMGEditor for compiling blueprints.
|
|
// UMG must be loaded for runtime and cooking.
|
|
FModuleManager::Get().LoadModule("UMG");
|
|
#else
|
|
if ( !IsRunningDedicatedServer() )
|
|
{
|
|
// UMG must be loaded for runtime and cooking.
|
|
FModuleManager::Get().LoadModule("UMG");
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
// Load all Development modules
|
|
SlowTask.EnterProgressFrame(20);
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
FModuleManager::Get().LoadModule("MessageLog");
|
|
FModuleManager::Get().LoadModule("CollisionAnalyzer");
|
|
#endif //WITH_UNREAL_DEVELOPER_TOOLS
|
|
}
|
|
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
FModuleManager::Get().LoadModule("FunctionalTesting");
|
|
#endif //WITH_UNREAL_DEVELOPER_TOOLS
|
|
|
|
SlowTask.EnterProgressFrame(30);
|
|
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
|
|
// HACK: load BT editor as early as possible for statically initialized assets (non cooked BT assets needs it)
|
|
// cooking needs this module too
|
|
FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));
|
|
|
|
// Ability tasks are based on GameplayTasks, so we need to make sure that module is loaded as well
|
|
FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));
|
|
|
|
IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
|
|
AudioEditorModule->RegisterAssetActions();
|
|
|
|
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"));
|
|
}
|
|
// -----------------------------------------------------
|
|
|
|
// HACK: load EQS editor as early as possible for statically initialized assets (non cooked EQS assets needs it)
|
|
// cooking needs this module too
|
|
bool bEnvironmentQueryEditor = false;
|
|
GConfig->GetBool(TEXT("EnvironmentQueryEd"), TEXT("EnableEnvironmentQueryEd"), bEnvironmentQueryEditor, GEngineIni);
|
|
if (bEnvironmentQueryEditor
|
|
#if WITH_EDITOR
|
|
|| GetDefault<UEditorExperimentalSettings>()->bEQSEditor
|
|
#endif // WITH_EDITOR
|
|
)
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("EnvironmentQueryEditor"));
|
|
}
|
|
|
|
// We need this for blueprint projects that have online functionality.
|
|
//FModuleManager::Get().LoadModule(TEXT("OnlineBlueprintSupport"));
|
|
|
|
if (IsRunningCommandlet())
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("IntroTutorials"));
|
|
FModuleManager::Get().LoadModule(TEXT("Blutility"));
|
|
}
|
|
|
|
#endif //(WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
|
|
|
|
#if WITH_ENGINE
|
|
// Load runtime client modules (which are also needed at cook-time)
|
|
if( !IsRunningDedicatedServer() )
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("GameLiveStreaming"));
|
|
FModuleManager::Get().LoadModule(TEXT("MediaAssets"));
|
|
}
|
|
#endif
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
bool FEngineLoop::LoadStartupModules()
|
|
{
|
|
FScopedSlowTask SlowTask(3);
|
|
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load any modules that want to be loaded before default modules are loaded up.
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load modules that are configured to load in the default phase
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::Default) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(1);
|
|
// Load any modules that want to be loaded after default modules are loaded up.
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FEngineLoop::InitTime()
|
|
{
|
|
// Init variables used for benchmarking and ticking.
|
|
FApp::SetCurrentTime(FPlatformTime::Seconds());
|
|
MaxFrameCounter = 0;
|
|
MaxTickTime = 0;
|
|
TotalTickTime = 0;
|
|
LastFrameCycles = FPlatformTime::Cycles();
|
|
|
|
float FloatMaxTickTime = 0;
|
|
#if !UE_BUILD_SHIPPING
|
|
FParse::Value(FCommandLine::Get(),TEXT("SECONDS="),FloatMaxTickTime);
|
|
MaxTickTime = FloatMaxTickTime;
|
|
|
|
// look of a version of seconds that only is applied if FApp::IsBenchmarking() is set. This makes it easier on
|
|
// say, iOS, where we have a toggle setting to enable benchmarking, but don't want to have to make user
|
|
// also disable the seconds setting as well. -seconds= will exit the app after time even if benchmarking
|
|
// is not enabled
|
|
// NOTE: This will override -seconds= if it's specified
|
|
if (FApp::IsBenchmarking())
|
|
{
|
|
if (FParse::Value(FCommandLine::Get(),TEXT("BENCHMARKSECONDS="),FloatMaxTickTime) && FloatMaxTickTime)
|
|
{
|
|
MaxTickTime = FloatMaxTickTime;
|
|
}
|
|
}
|
|
|
|
// Use -FPS=X to override fixed tick rate if e.g. -BENCHMARK is used.
|
|
float FixedFPS = 0;
|
|
FParse::Value(FCommandLine::Get(),TEXT("FPS="),FixedFPS);
|
|
if( FixedFPS > 0 )
|
|
{
|
|
FApp::SetFixedDeltaTime(1 / FixedFPS);
|
|
}
|
|
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// convert FloatMaxTickTime into number of frames (using 1 / FApp::GetFixedDeltaTime() to convert fps to seconds )
|
|
MaxFrameCounter = FMath::TruncToInt(MaxTickTime / FApp::GetFixedDeltaTime());
|
|
}
|
|
|
|
|
|
//called via FCoreDelegates::StarvedGameLoop
|
|
void GameLoopIsStarved()
|
|
{
|
|
FlushPendingDeleteRHIResources_GameThread();
|
|
FStats::AdvanceFrame( true, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) );
|
|
}
|
|
|
|
|
|
int32 FEngineLoop::Init()
|
|
{
|
|
CheckImageIntegrity();
|
|
|
|
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FEngineLoop::Init" ), STAT_FEngineLoop_Init, STATGROUP_LoadTime );
|
|
|
|
FScopedSlowTask SlowTask(100);
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
// Figure out which UEngine variant to use.
|
|
UClass* EngineClass = nullptr;
|
|
if( !GIsEditor )
|
|
{
|
|
// We're the game.
|
|
FString GameEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
|
|
EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName);
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *GameEngineClassName);
|
|
}
|
|
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
|
|
}
|
|
else
|
|
{
|
|
#if WITH_EDITOR
|
|
// We're UnrealEd.
|
|
FString UnrealEdEngineClassName;
|
|
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni);
|
|
EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName);
|
|
if (EngineClass == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *UnrealEdEngineClassName);
|
|
}
|
|
GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);
|
|
#else
|
|
check(0);
|
|
#endif
|
|
}
|
|
|
|
check( GEngine );
|
|
|
|
GetMoviePlayer()->PassLoadingScreenWindowBackToGame();
|
|
|
|
GEngine->ParseCommandline();
|
|
|
|
InitTime();
|
|
|
|
SlowTask.EnterProgressFrame(60);
|
|
|
|
GEngine->Init(this);
|
|
|
|
UEngine::OnPostEngineInit.Broadcast();
|
|
|
|
SlowTask.EnterProgressFrame(30);
|
|
|
|
// initialize engine instance discovery
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
if (!IsRunningCommandlet())
|
|
{
|
|
SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService();
|
|
SessionService->Start();
|
|
}
|
|
|
|
EngineService = new FEngineService();
|
|
}
|
|
|
|
// Load all the post-engine init modules
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
|
|
{
|
|
GIsRequestingExit = true;
|
|
return 1;
|
|
}
|
|
|
|
GEngine->Start();
|
|
|
|
GetMoviePlayer()->WaitForMovieToFinish();
|
|
|
|
// initialize automation worker
|
|
#if WITH_AUTOMATION_WORKER
|
|
FModuleManager::Get().LoadModule("AutomationWorker");
|
|
#endif
|
|
|
|
// Automation tests can be invoked locally in non-editor builds configuration (e.g. performance profiling in Test configuration)
|
|
#if WITH_ENGINE && !UE_BUILD_SHIPPING
|
|
FModuleManager::Get().LoadModule("AutomationController");
|
|
FModuleManager::GetModuleChecked<IAutomationControllerModule>("AutomationController").Init();
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
FModuleManager::Get().LoadModule(TEXT("ProfilerClient"));
|
|
}
|
|
|
|
FModuleManager::Get().LoadModule(TEXT("SequenceRecorder"));
|
|
FModuleManager::Get().LoadModule(TEXT("SequenceRecorderSections"));
|
|
#endif
|
|
|
|
GIsRunning = true;
|
|
|
|
if (!GIsEditor)
|
|
{
|
|
// hide a couple frames worth of rendering
|
|
FViewport::SetGameRenderingEnabled(true, 3);
|
|
}
|
|
|
|
// Begin the async platform hardware survey
|
|
GEngine->StartHardwareSurvey();
|
|
|
|
FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved);
|
|
|
|
// Ready to measure thread heartbeat
|
|
FThreadHeartBeat::Get().Start();
|
|
|
|
FCoreDelegates::OnFEngineLoopInitComplete.Broadcast();
|
|
return 0;
|
|
}
|
|
|
|
|
|
void FEngineLoop::Exit()
|
|
{
|
|
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "EngineLoop.Exit" ) );
|
|
|
|
GIsRunning = 0;
|
|
GLogConsole = nullptr;
|
|
|
|
// shutdown visual logger and flush all data
|
|
#if ENABLE_VISUAL_LOG
|
|
FVisualLogger::Get().Shutdown();
|
|
#endif
|
|
|
|
GetMoviePlayer()->Shutdown();
|
|
|
|
// Make sure we're not in the middle of loading something.
|
|
FlushAsyncLoading();
|
|
|
|
// Block till all outstanding resource streaming requests are fulfilled.
|
|
if (!IStreamingManager::HasShutdown())
|
|
{
|
|
UTexture2D::CancelPendingTextureStreaming();
|
|
IStreamingManager::Get().BlockTillAllRequestsFinished();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
// shut down messaging
|
|
delete EngineService;
|
|
EngineService = nullptr;
|
|
|
|
if (SessionService.IsValid())
|
|
{
|
|
SessionService->Stop();
|
|
SessionService.Reset();
|
|
}
|
|
|
|
if (GDistanceFieldAsyncQueue)
|
|
{
|
|
GDistanceFieldAsyncQueue->Shutdown();
|
|
delete GDistanceFieldAsyncQueue;
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
if ( GEngine != nullptr )
|
|
{
|
|
GEngine->ShutdownAudioDeviceManager();
|
|
}
|
|
|
|
if ( GEngine != nullptr )
|
|
{
|
|
GEngine->PreExit();
|
|
}
|
|
|
|
// close all windows
|
|
FSlateApplication::Shutdown();
|
|
|
|
#if !UE_SERVER
|
|
if ( FEngineFontServices::IsInitialized() )
|
|
{
|
|
FEngineFontServices::Destroy();
|
|
}
|
|
#endif
|
|
|
|
#if !PLATFORM_ANDROID // AppPreExit doesn't work on Android
|
|
AppPreExit();
|
|
|
|
TermGamePhys();
|
|
ParticleVertexFactoryPool_FreePool();
|
|
#else
|
|
// AppPreExit() stops malloc profiler, do it here instead
|
|
MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); );
|
|
#endif // !ANDROID
|
|
|
|
// Stop the rendering thread.
|
|
StopRenderingThread();
|
|
|
|
// Tear down the RHI.
|
|
RHIExitAndStopRHIThread();
|
|
|
|
#if !PLATFORM_ANDROID // UnloadModules doesn't work on Android
|
|
#if WITH_ENGINE
|
|
// Save the hot reload state
|
|
IHotReloadInterface* HotReload = IHotReloadInterface::GetPtr();
|
|
if(HotReload != nullptr)
|
|
{
|
|
HotReload->SaveConfig();
|
|
}
|
|
#endif
|
|
|
|
// Unload all modules. Note that this doesn't actually unload the module DLLs (that happens at
|
|
// process exit by the OS), but it does call ShutdownModule() on all loaded modules in the reverse
|
|
// order they were loaded in, so that systems can unregister and perform general clean up.
|
|
FModuleManager::Get().UnloadModulesAtShutdown();
|
|
#endif // !ANDROID
|
|
|
|
// Move earlier?
|
|
#if STATS
|
|
FThreadStats::StopThread();
|
|
#endif
|
|
|
|
FTaskGraphInterface::Shutdown();
|
|
IStreamingManager::Shutdown();
|
|
#if !USE_NEW_ASYNC_IO
|
|
FIOSystem::Shutdown();
|
|
#endif
|
|
}
|
|
|
|
|
|
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;
|
|
if (PlayerController)
|
|
{
|
|
ULocalPlayer* LocalPlayer = Cast< ULocalPlayer >(PlayerController->Player);
|
|
if (LocalPlayer)
|
|
{
|
|
FReply& TheReply = LocalPlayer->GetSlateOperations();
|
|
SlateApp.ProcessReply(PathToWidget, TheReply, nullptr, nullptr, LocalPlayer->GetControllerId());
|
|
|
|
TheReply = FReply::Unhandled();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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()
|
|
&& !FPlatformProcess::IsThisApplicationForeground())
|
|
{
|
|
bIdleMode = true;
|
|
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (!Context.World()->AreAlwaysLoadedLevelsLoaded())
|
|
{
|
|
bIdleMode = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bIdleMode;
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST
|
|
|
|
#include "StackTracker.h"
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn(
|
|
TEXT("LogGameThreadMallocChurn.Enable"),
|
|
0,
|
|
TEXT("If > 0, then collect sample game thread malloc, realloc and free, periodically print a report of the worst offenders."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_PrintFrequency(
|
|
TEXT("LogGameThreadMallocChurn.PrintFrequency"),
|
|
300,
|
|
TEXT("Number of frames between churn reports."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_Threshhold(
|
|
TEXT("LogGameThreadMallocChurn.Threshhold"),
|
|
10,
|
|
TEXT("Minimum average number of allocs per frame to include in the report."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_SampleFrequency(
|
|
TEXT("LogGameThreadMallocChurn.SampleFrequency"),
|
|
100,
|
|
TEXT("Number of allocs to skip between samples. This is used to prevent churn sampling from slowing the game down too much."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_StackIgnore(
|
|
TEXT("LogGameThreadMallocChurn.StackIgnore"),
|
|
2,
|
|
TEXT("Number of items to discard from the top of a stack frame."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_RemoveAliases(
|
|
TEXT("LogGameThreadMallocChurn.RemoveAliases"),
|
|
1,
|
|
TEXT("If > 0 then remove aliases from the counting process. This essentialy merges addresses that have the same human readable string. It is slower."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogGameThreadMallocChurn_StackLen(
|
|
TEXT("LogGameThreadMallocChurn.StackLen"),
|
|
3,
|
|
TEXT("Maximum number of stack frame items to keep. This improves aggregation because calls that originate from multiple places but end up in the same place will be accounted together."));
|
|
|
|
|
|
extern CORE_API TFunction<void(int32)>* GGameThreadMallocHook;
|
|
|
|
struct FScopedSampleMallocChurn
|
|
{
|
|
static FStackTracker GGameThreadMallocChurnTracker;
|
|
static uint64 DumpFrame;
|
|
|
|
bool bEnabled;
|
|
int32 CountDown;
|
|
TFunction<void(int32)> Hook;
|
|
|
|
FScopedSampleMallocChurn()
|
|
: bEnabled(CVarLogGameThreadMallocChurn.GetValueOnGameThread() > 0)
|
|
, CountDown(CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread())
|
|
, Hook(
|
|
[this](int32 Index)
|
|
{
|
|
if (--CountDown <= 0)
|
|
{
|
|
CountDown = CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread();
|
|
CollectSample();
|
|
}
|
|
}
|
|
)
|
|
{
|
|
if (bEnabled)
|
|
{
|
|
check(IsInGameThread());
|
|
check(!GGameThreadMallocHook);
|
|
if (!DumpFrame)
|
|
{
|
|
DumpFrame = GFrameCounter + CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread();
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
GGameThreadMallocChurnTracker.ToggleTracking(true, true);
|
|
GGameThreadMallocHook = &Hook;
|
|
}
|
|
else
|
|
{
|
|
check(IsInGameThread());
|
|
GGameThreadMallocChurnTracker.ToggleTracking(false, true);
|
|
if (DumpFrame)
|
|
{
|
|
DumpFrame = 0;
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
}
|
|
}
|
|
~FScopedSampleMallocChurn()
|
|
{
|
|
if (bEnabled)
|
|
{
|
|
check(IsInGameThread());
|
|
check(GGameThreadMallocHook == &Hook);
|
|
GGameThreadMallocHook = nullptr;
|
|
GGameThreadMallocChurnTracker.ToggleTracking(false, true);
|
|
check(DumpFrame);
|
|
if (GFrameCounter > DumpFrame)
|
|
{
|
|
PrintResultsAndReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollectSample()
|
|
{
|
|
check(IsInGameThread());
|
|
GGameThreadMallocChurnTracker.CaptureStackTrace(CVarLogGameThreadMallocChurn_StackIgnore.GetValueOnGameThread(), nullptr, CVarLogGameThreadMallocChurn_StackLen.GetValueOnGameThread(), CVarLogGameThreadMallocChurn_RemoveAliases.GetValueOnGameThread() > 0);
|
|
}
|
|
void PrintResultsAndReset()
|
|
{
|
|
DumpFrame = GFrameCounter + CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread();
|
|
FOutputDeviceRedirector* Log = FOutputDeviceRedirector::Get();
|
|
float SampleAndFrameCorrection = float(CVarLogGameThreadMallocChurn_SampleFrequency.GetValueOnGameThread()) / float(CVarLogGameThreadMallocChurn_PrintFrequency.GetValueOnGameThread());
|
|
GGameThreadMallocChurnTracker.DumpStackTraces(CVarLogGameThreadMallocChurn_Threshhold.GetValueOnGameThread(), *Log, SampleAndFrameCorrection);
|
|
GGameThreadMallocChurnTracker.ResetTracking();
|
|
}
|
|
};
|
|
FStackTracker FScopedSampleMallocChurn::GGameThreadMallocChurnTracker;
|
|
uint64 FScopedSampleMallocChurn::DumpFrame = 0;
|
|
|
|
#endif
|
|
|
|
void FEngineLoop::Tick()
|
|
{
|
|
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST
|
|
FScopedSampleMallocChurn ChurnTracker;
|
|
#endif
|
|
|
|
// Send a heartbeat for the diagnostics thread
|
|
FThreadHeartBeat::Get().HeartBeat();
|
|
|
|
// Ensure we aren't starting a frame while loading or playing a loading movie
|
|
ensure(GetMoviePlayer()->IsLoadingFinished() && !GetMoviePlayer()->IsMovieCurrentlyPlaying());
|
|
|
|
FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::GetActiveProfiler();
|
|
if (ActiveProfiler)
|
|
{
|
|
ActiveProfiler->FrameSync();
|
|
}
|
|
|
|
SCOPED_NAMED_EVENT(FEngineLoopTick, FColor::Red);
|
|
|
|
// early in the Tick() to get the callbacks for cvar changes called
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_CallAllConsoleVariableSinks);
|
|
IConsoleManager::Get().CallAllConsoleVariableSinks();
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_FrameTime );
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
BeginFrame,
|
|
{
|
|
GRHICommandList.LatchBypass();
|
|
GFrameNumberRenderThread++;
|
|
RHICmdList.PushEvent(*FString::Printf(TEXT("Frame%d"),GFrameNumberRenderThread), FColor(0, 255, 0, 255));
|
|
GPU_STATS_BEGINFRAME(RHICmdList);
|
|
RHICmdList.BeginFrame();
|
|
});
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_FlushThreadedLogs);
|
|
// Flush debug output which has been buffered by other threads.
|
|
GLog->FlushThreadedLogs();
|
|
}
|
|
|
|
// Exit if frame limit is reached in benchmark mode.
|
|
if( (FApp::IsBenchmarking() && MaxFrameCounter && (GFrameCounter > MaxFrameCounter))
|
|
// or time limit is reached if set.
|
|
|| (MaxTickTime && (TotalTickTime > MaxTickTime)) )
|
|
{
|
|
FPlatformMisc::RequestExit(0);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_UpdateTimeAndHandleMaxTickRate);
|
|
// Set FApp::CurrentTime, FApp::DeltaTime and potentially wait to enforce max tick rate.
|
|
GEngine->UpdateTimeAndHandleMaxTickRate();
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_TickFPSChart);
|
|
GEngine->TickPerformanceMonitoring( FApp::GetDeltaTime() );
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Malloc_UpdateStats);
|
|
|
|
// Update memory allocator stats.
|
|
GMalloc->UpdateStats();
|
|
}
|
|
|
|
FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) );
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_FrameTime );
|
|
|
|
// Calculates average FPS/MS (outside STATS on purpose)
|
|
CalculateFPSTimings();
|
|
|
|
// Note the start of a new frame
|
|
MALLOC_PROFILER(GMalloc->Exec(nullptr, *FString::Printf(TEXT("SNAPSHOTMEMORYFRAME")),*GLog));
|
|
|
|
// handle some per-frame tasks on the rendering thread
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
ResetDeferredUpdates,
|
|
{
|
|
FDeferredUpdateResource::ResetNeedsUpdate();
|
|
FlushPendingDeleteRHIResources_RenderThread();
|
|
});
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PumpMessages);
|
|
FPlatformMisc::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.
|
|
#if WITH_ENGINE
|
|
extern ENGINE_API float GNewWorldToMetersScale;
|
|
UWorld* HackGWorld = GWorld;
|
|
if( GNewWorldToMetersScale != 0.0f && HackGWorld != nullptr )
|
|
{
|
|
if( GNewWorldToMetersScale != HackGWorld->GetWorldSettings()->WorldToMeters )
|
|
{
|
|
HackGWorld->GetWorldSettings()->WorldToMeters = GNewWorldToMetersScale;
|
|
}
|
|
}
|
|
#endif // WITH_ENGINE
|
|
|
|
if (FSlateApplication::IsInitialized() && !bIdleMode)
|
|
{
|
|
SCOPE_TIME_GUARD(TEXT("SlateInput"));
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_SlateInput);
|
|
|
|
FSlateApplication& SlateApp = FSlateApplication::Get();
|
|
SlateApp.PollGameDeviceState();
|
|
// Gives widgets a chance to process any accumulated input
|
|
SlateApp.FinishedInputThisFrame();
|
|
}
|
|
|
|
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.
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_WaitForMovieToFinish);
|
|
|
|
GetMoviePlayer()->WaitForMovieToFinish();
|
|
}
|
|
|
|
if (GShaderCompilingManager)
|
|
{
|
|
// Process any asynchronous shader compile results that are ready, limit execution time
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GShaderCompilingManager);
|
|
GShaderCompilingManager->ProcessAsyncResults(true, false);
|
|
}
|
|
|
|
if (GDistanceFieldAsyncQueue)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GDistanceFieldAsyncQueue);
|
|
GDistanceFieldAsyncQueue->ProcessAsyncTasks();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
FGraphEventRef ConcurrentTask;
|
|
const bool bDoConcurrentSlateTick = GEngine->ShouldDoAsyncEndOfFrameTasks();
|
|
|
|
if (bDoConcurrentSlateTick)
|
|
{
|
|
const float DeltaSeconds = FApp::GetDeltaTime();
|
|
UGameViewportClient* const GameViewport = GEngine->GameViewport;
|
|
ConcurrentTask = TGraphTask<FExecuteConcurrentWithSlateTickTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
|
|
[GameViewport, DeltaSeconds]()
|
|
{
|
|
if (CVarDoAsyncEndOfFrameTasksRandomize.GetValueOnAnyThread(true) > 0)
|
|
{
|
|
FPlatformProcess::Sleep(FMath::RandRange(0.0f, .003f)); // this shakes up the threading to find race conditions
|
|
}
|
|
if (GameViewport != nullptr)
|
|
{
|
|
UWorld* const World = GameViewport->GetWorld();
|
|
if (World && World->DemoNetDriver)
|
|
{
|
|
World->DemoNetDriver->TickFlushAsyncEndOfFrame(DeltaSeconds);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
#endif
|
|
|
|
if (FSlateApplication::IsInitialized() && !bIdleMode)
|
|
{
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_ProcessPlayerControllersSlateOperations);
|
|
check(!IsRunningDedicatedServer());
|
|
|
|
// Process slate operations accumulated in the world ticks.
|
|
ProcessLocalPlayerSlateOperations();
|
|
}
|
|
|
|
// Tick Slate application
|
|
FSlateApplication::Get().Tick();
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
if (ConcurrentTask.GetReference())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ConcurrentWithSlateTickTasks_Wait);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConcurrentTask);
|
|
ConcurrentTask = nullptr;
|
|
}
|
|
#endif
|
|
|
|
#if STATS
|
|
// Clear any stat group notifications we have pending just incase they weren't claimed during FSlateApplication::Get().Tick
|
|
extern CORE_API void ClearPendingStatGroups();
|
|
ClearPendingStatGroups();
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER( STAT_FEngineLoop_Tick_AutomationController );
|
|
static FName AutomationController( "AutomationController" );
|
|
//Check if module loaded to support the change to allow this to be hot compilable.
|
|
if (FModuleManager::Get().IsModuleLoaded( AutomationController ))
|
|
{
|
|
FModuleManager::GetModuleChecked<IAutomationControllerModule>( AutomationController ).Tick();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if WITH_ENGINE
|
|
#if WITH_AUTOMATION_WORKER
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationWorker);
|
|
//Check if module loaded to support the change to allow this to be hot compilable.
|
|
static const FName AutomationWorkerModuleName = TEXT("AutomationWorker");
|
|
if (FModuleManager::Get().IsModuleLoaded(AutomationWorkerModuleName))
|
|
{
|
|
FModuleManager::GetModuleChecked<IAutomationWorkerModule>(AutomationWorkerModuleName).Tick();
|
|
}
|
|
}
|
|
#endif
|
|
#endif //WITH_ENGINE
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RHITickTime);
|
|
RHITick( FApp::GetDeltaTime() ); // Update RHI.
|
|
}
|
|
|
|
// Increment global frame counter. Once for each engine tick.
|
|
GFrameCounter++;
|
|
|
|
// Disregard first few ticks for total tick time as it includes loading and such.
|
|
if( GFrameCounter > 6 )
|
|
{
|
|
TotalTickTime+=FApp::GetDeltaTime();
|
|
}
|
|
|
|
|
|
// Find the objects which need to be cleaned up the next frame.
|
|
FPendingCleanupObjects* PreviousPendingCleanupObjects = PendingCleanupObjects;
|
|
PendingCleanupObjects = GetPendingCleanupObjects();
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_FrameSyncTime );
|
|
// this could be perhaps moved down to get greater parallelizm
|
|
// Sync game and render thread. Either total sync or allowing one frame lag.
|
|
static FFrameEndSync FrameEndSync;
|
|
static auto CVarAllowOneFrameThreadLag = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag"));
|
|
FrameEndSync.Sync( CVarAllowOneFrameThreadLag->GetValueOnGameThread() != 0 );
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_DeferredTickTime );
|
|
// Delete the objects which were enqueued for deferred cleanup before the previous frame.
|
|
delete PreviousPendingCleanupObjects;
|
|
|
|
// Destroy all linkers pending delete
|
|
#if WITH_COREUOBJECT
|
|
DeleteLoaders();
|
|
#endif
|
|
|
|
FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
|
|
|
|
FThreadManager::Get().Tick();
|
|
|
|
GEngine->TickDeferredCommands();
|
|
}
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
EndFrame,
|
|
{
|
|
RHICmdList.EndFrame();
|
|
GPU_STATS_ENDFRAME(RHICmdList);
|
|
RHICmdList.PopEvent();
|
|
});
|
|
|
|
// Set CPU utilization stats.
|
|
const FCPUTime CPUTime = FPlatformTime::GetCPUTime();
|
|
SET_FLOAT_STAT( STAT_CPUTimePct, CPUTime.CPUTimePct );
|
|
SET_FLOAT_STAT( STAT_CPUTimePctRelative, CPUTime.CPUTimePctRelative );
|
|
|
|
// Set the UObject count stat
|
|
#if UE_GC_TRACK_OBJ_AVAILABLE
|
|
SET_DWORD_STAT(STAT_Hash_NumObjects, GUObjectArray.GetObjectArrayNumMinusAvailable());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void FEngineLoop::ClearPendingCleanupObjects()
|
|
{
|
|
delete PendingCleanupObjects;
|
|
PendingCleanupObjects = nullptr;
|
|
}
|
|
|
|
#endif // WITH_ENGINE
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogTimestamp(
|
|
TEXT("log.Timestamp"),
|
|
1,
|
|
TEXT("Defines if time is included in each line in the log file and in what form. Layout: [time][frame mod 1000]\n")
|
|
TEXT(" 0 = Do not display log timestamps\n")
|
|
TEXT(" 1 = Log time stamps in UTC and Frametime (default) e.g. [2015.11.25-21.28.50:803][376]\n")
|
|
TEXT(" 2 = Log timestamps in seconds elapsed since GStartTime e.g. [0130.29][420]"),
|
|
ECVF_Default);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLogCategory(
|
|
TEXT("log.Category"),
|
|
1,
|
|
TEXT("Defines if the categoy is included in each line in the log file and in what form.\n")
|
|
TEXT(" 0 = Do not log category\n")
|
|
TEXT(" 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;
|
|
}
|
|
}
|
|
|
|
{
|
|
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( "SinceStart" ))
|
|
{
|
|
CVarLogTimestamp->Set(2, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
// Assume this is a bool for backward compatibility
|
|
else if (FCString::ToBool( *LogTimes ))
|
|
{
|
|
CVarLogTimestamp->Set(1, ECVF_SetBySystemSettingsIni);
|
|
}
|
|
}
|
|
|
|
if (FParse::Param( FCommandLine::Get(), TEXT( "LOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set(1, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "NOLOGTIMES" ) ))
|
|
{
|
|
CVarLogTimestamp->Set(0, ECVF_SetByCommandline);
|
|
}
|
|
else if (FParse::Param( FCommandLine::Get(), TEXT( "LOGTIMESINCESTART" ) ))
|
|
{
|
|
CVarLogTimestamp->Set(2, ECVF_SetByCommandline);
|
|
}
|
|
}
|
|
|
|
|
|
/* FEngineLoop static interface
|
|
*****************************************************************************/
|
|
|
|
bool FEngineLoop::AppInit( )
|
|
{
|
|
// Output devices.
|
|
GError = FPlatformOutputDevices::GetError();
|
|
GWarn = FPlatformOutputDevices::GetWarn();
|
|
|
|
BeginInitTextLocalization();
|
|
|
|
// Avoiding potential exploits by not exposing command line overrides in the shipping games.
|
|
#if !UE_BUILD_SHIPPING && WITH_EDITORONLY_DATA
|
|
// 8192 is the maximum length of the command line on Windows XP.
|
|
TCHAR CmdLineEnv[8192];
|
|
|
|
// Retrieve additional command line arguments from environment variable.
|
|
FPlatformMisc::GetEnvironmentVariable(TEXT("UE-CmdLineArgs"), CmdLineEnv,ARRAY_COUNT(CmdLineEnv));
|
|
|
|
// Manually nullptr terminate just in case. The nullptr string is returned above in the error case so
|
|
// we don't have to worry about that.
|
|
CmdLineEnv[ARRAY_COUNT(CmdLineEnv)-1] = 0;
|
|
FString Env = FString(CmdLineEnv).Trim();
|
|
|
|
if (Env.Len())
|
|
{
|
|
// Append the command line environment after inserting a space as we can't set it in the
|
|
// environment. Note that any code accessing GCmdLine before appInit obviously won't
|
|
// respect the command line environment additions.
|
|
FCommandLine::Append(TEXT(" -EnvAfterHere "));
|
|
FCommandLine::Append(CmdLineEnv);
|
|
}
|
|
#endif
|
|
|
|
// Error history.
|
|
FCString::Strcpy(GErrorHist, TEXT("Fatal error!" LINE_TERMINATOR LINE_TERMINATOR));
|
|
|
|
// Platform specific pre-init.
|
|
FPlatformMisc::PlatformPreInit();
|
|
|
|
// Keep track of start time.
|
|
GSystemStartTime = FDateTime::Now().ToString();
|
|
|
|
// Switch into executable's directory.
|
|
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
|
|
|
|
// Now finish initializing the file manager after the command line is set up
|
|
IFileManager::Get().ProcessCommandLineOptions();
|
|
|
|
FPageAllocator::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;
|
|
}
|
|
|
|
// If "-WaitForDebugger" was specified, halt startup and wait for a debugger to attach before continuing
|
|
if( FParse::Param( FCommandLine::Get(), TEXT( "WaitForDebugger" ) ) )
|
|
{
|
|
while( !FPlatformMisc::IsDebuggerPresent() )
|
|
{
|
|
FPlatformProcess::Sleep( 0.1f );
|
|
}
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
#if PLATFORM_WINDOWS
|
|
|
|
// make sure that the log directory exists
|
|
IFileManager::Get().MakeDirectory( *FPaths::GameLogDir() );
|
|
|
|
// 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::GameLogDir(), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString())));
|
|
#endif
|
|
|
|
// Init logging to disk
|
|
FPlatformOutputDevices::SetupOutputDevices();
|
|
|
|
// init config system
|
|
FConfigCacheIni::InitializeConfigSystem();
|
|
|
|
// 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 && FPaths::IsProjectFilePathSet() && IPluginManager::Get().AreRequiredPluginsAvailable())
|
|
{
|
|
const FProjectDescriptor* CurrentProject = IProjectManager::Get().GetCurrentProject();
|
|
if(CurrentProject != nullptr && CurrentProject->Modules.Num() > 0)
|
|
{
|
|
bool bNeedCompile = false;
|
|
GConfig->GetBool(TEXT("/Script/UnrealEd.EditorLoadingSavingSettings"), TEXT("bForceCompilationAtStartup"), bNeedCompile, GEditorPerProjectIni);
|
|
if(FParse::Param(FCommandLine::Get(), TEXT("SKIPCOMPILE")) || FParse::Param(FCommandLine::Get(), TEXT("MULTIPROCESS")))
|
|
{
|
|
bNeedCompile = false;
|
|
}
|
|
if(!bNeedCompile)
|
|
{
|
|
// Check if any of the project or plugin modules are out of date, and the user wants to compile them.
|
|
TArray<FString> IncompatibleFiles;
|
|
IProjectManager::Get().CheckModuleCompatibility(IncompatibleFiles);
|
|
IPluginManager::Get().CheckModuleCompatibility(IncompatibleFiles);
|
|
|
|
if (IncompatibleFiles.Num() > 0)
|
|
{
|
|
// Log the modules which need to be rebuilt
|
|
FString ModulesList = TEXT("The following modules are missing or built with a different engine version:\n\n");
|
|
for (int Idx = 0; Idx < IncompatibleFiles.Num(); Idx++)
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT("Incompatible or missing module: %s"), *IncompatibleFiles[Idx]);
|
|
ModulesList += IncompatibleFiles[Idx] + TEXT("\n");
|
|
}
|
|
ModulesList += TEXT("\nWould you like to rebuild them now?");
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Ask whether to compile before continuing
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *ModulesList, *FString::Printf(TEXT("Missing %s Modules"), FApp::GetGameName())) == EAppReturnType::No)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bNeedCompile = true;
|
|
}
|
|
}
|
|
|
|
if(bNeedCompile)
|
|
{
|
|
// Try to compile it
|
|
FFeedbackContext *Context = (FFeedbackContext*)FDesktopPlatformModule::Get()->GetNativeFeedbackContext();
|
|
Context->BeginSlowTask(FText::FromString(TEXT("Starting build...")), true, true);
|
|
bool bCompileResult = FDesktopPlatformModule::Get()->CompileGameProject(FPaths::RootDir(), FPaths::GetProjectFilePath(), Context);
|
|
Context->EndSlowTask();
|
|
|
|
// Get a list of modules which are still incompatible
|
|
TArray<FString> StillIncompatibleFiles;
|
|
IProjectManager::Get().CheckModuleCompatibility(StillIncompatibleFiles);
|
|
IPluginManager::Get().CheckModuleCompatibility(StillIncompatibleFiles);
|
|
|
|
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::GetGameName()), TEXT("Error"));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// NOTE: This is the earliest place to init the online subsystems (via plugins)
|
|
// Code needs GConfigFile to be valid
|
|
// Must be after FThreadStats::StartThread();
|
|
// Must be before Render/RHI subsystem D3DCreate()
|
|
// For platform services that need D3D hooks like Steam
|
|
|
|
// Load "pre-init" plugin modules
|
|
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostConfigInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Register the callback that allows the text localization manager to load data for plugins
|
|
FTextLocalizationManager::Get().GatherAdditionalLocResPathsCallback.AddLambda([](TArray<FString>& OutLocResPaths)
|
|
{
|
|
IPluginManager::Get().GetLocalizationPathsForEnabledPlugins(OutLocResPaths);
|
|
});
|
|
|
|
PreInitHMDDevice();
|
|
|
|
// Put the command line and config info into the suppression system
|
|
FLogSuppressionInterface::Get().ProcessConfigAndCommandLine();
|
|
|
|
// after the above has run we now have the REQUIRED set of engine .INIs (all of the other .INIs)
|
|
// that are gotten from .h files' config() are not requires and are dynamically loaded when the .u files are loaded
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Prompt the user for remote debugging?
|
|
bool bPromptForRemoteDebug = false;
|
|
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugging"), bPromptForRemoteDebug, GEngineIni);
|
|
bool bPromptForRemoteDebugOnEnsure = false;
|
|
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugOnEnsure"), bPromptForRemoteDebugOnEnsure, GEngineIni);
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUG")))
|
|
{
|
|
bPromptForRemoteDebug = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUGENSURE")))
|
|
{
|
|
bPromptForRemoteDebug = true;
|
|
bPromptForRemoteDebugOnEnsure = true;
|
|
}
|
|
|
|
FPlatformMisc::SetShouldPromptForRemoteDebugging(bPromptForRemoteDebug);
|
|
FPlatformMisc::SetShouldPromptForRemoteDebugOnEnsure(bPromptForRemoteDebugOnEnsure);
|
|
|
|
// Feedback context.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("WARNINGSASERRORS")))
|
|
{
|
|
GWarn->TreatWarningsAsErrors = true;
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("SILENT")))
|
|
{
|
|
GIsSilent = true;
|
|
}
|
|
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
// Show log if wanted.
|
|
if (GLogConsole && FParse::Param(FCommandLine::Get(), TEXT("LOG")))
|
|
{
|
|
GLogConsole->Show(true);
|
|
}
|
|
|
|
//// Command line.
|
|
UE_LOG(LogInit, Log, TEXT("Build: %s"), FApp::GetBuildVersion());
|
|
UE_LOG(LogInit, Log, TEXT("Engine Version: %s"), *FEngineVersion::Current().ToString());
|
|
UE_LOG(LogInit, Log, TEXT("Compatible Engine Version: %s"), *FEngineVersion::CompatibleWith().ToString());
|
|
UE_LOG(LogInit, Log, TEXT("Net CL: %u"), FNetworkVersion::GetNetworkCompatibleChangelist());
|
|
FDevVersionRegistration::DumpVersionsToLog();
|
|
|
|
#if PLATFORM_64BITS
|
|
UE_LOG(LogInit, Log, TEXT("Compiled (64-bit): %s %s"), ANSI_TO_TCHAR(__DATE__), ANSI_TO_TCHAR(__TIME__));
|
|
#else
|
|
UE_LOG(LogInit, Log, TEXT("Compiled (32-bit): %s %s"), ANSI_TO_TCHAR(__DATE__), ANSI_TO_TCHAR(__TIME__));
|
|
#endif
|
|
|
|
// Print compiler version info
|
|
#if defined(__clang__)
|
|
UE_LOG(LogInit, Log, TEXT("Compiled with Clang: %s"), ANSI_TO_TCHAR( __clang_version__ ) );
|
|
#elif defined(__INTEL_COMPILER)
|
|
UE_LOG(LogInit, Log, TEXT("Compiled with ICL: %d"), __INTEL_COMPILER);
|
|
#elif defined( _MSC_VER )
|
|
#ifndef __INTELLISENSE__ // Intellisense compiler doesn't support _MSC_FULL_VER
|
|
{
|
|
const FString VisualCPPVersion( FString::Printf( TEXT( "%d" ), _MSC_FULL_VER ) );
|
|
const FString VisualCPPRevisionNumber( FString::Printf( TEXT( "%02d" ), _MSC_BUILD ) );
|
|
UE_LOG(LogInit, Log, TEXT("Compiled with Visual C++: %s.%s.%s.%s"),
|
|
*VisualCPPVersion.Mid( 0, 2 ), // Major version
|
|
*VisualCPPVersion.Mid( 2, 2 ), // Minor version
|
|
*VisualCPPVersion.Mid( 4 ), // Build version
|
|
*VisualCPPRevisionNumber // Revision number
|
|
);
|
|
}
|
|
#endif
|
|
#else
|
|
UE_LOG(LogInit, Log, TEXT("Compiled with unrecognized C++ compiler") );
|
|
#endif
|
|
|
|
UE_LOG(LogInit, Log, TEXT("Build Configuration: %s"), EBuildConfigurations::ToString(FApp::GetBuildConfiguration()));
|
|
UE_LOG(LogInit, Log, TEXT("Branch Name: %s"), *FApp::GetBranchName() );
|
|
UE_LOG(LogInit, Log, TEXT("Command line: %s"), FCommandLine::GetForLogging() );
|
|
UE_LOG(LogInit, Log, TEXT("Base directory: %s"), FPlatformProcess::BaseDir() );
|
|
//UE_LOG(LogInit, Log, TEXT("Character set: %s"), sizeof(TCHAR)==1 ? TEXT("ANSI") : TEXT("Unicode") );
|
|
UE_LOG(LogInit, Log, TEXT("Installed Engine Build: %d"), FApp::IsEngineInstalled() ? 1 : 0);
|
|
|
|
// if a logging build, clear out old log files
|
|
#if !NO_LOGGING
|
|
FMaintenance::DeleteOldLogs();
|
|
#endif
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
FApp::InitializeSession();
|
|
#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);
|
|
|
|
// Init other systems.
|
|
FCoreDelegates::OnInit.Broadcast();
|
|
return true;
|
|
}
|
|
|
|
|
|
void FEngineLoop::AppPreExit( )
|
|
{
|
|
UE_LOG(LogExit, Log, TEXT("Preparing to exit.") );
|
|
|
|
FCoreDelegates::OnPreExit.Broadcast();
|
|
|
|
MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); );
|
|
|
|
#if WITH_ENGINE
|
|
if (FString(FCommandLine::Get()).Contains(TEXT("CreatePak")) && GetDerivedDataCache())
|
|
{
|
|
// if we are creating a Pak, we need to make sure everything is done and written before we exit
|
|
UE_LOG(LogInit, Display, TEXT("Closing DDC Pak File."));
|
|
GetDerivedDataCacheRef().WaitForQuiescence(true);
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
FRemoteConfig::Flush();
|
|
#endif
|
|
|
|
FCoreDelegates::OnExit.Broadcast();
|
|
|
|
// Clean up the thread pool
|
|
if (GThreadPool != nullptr)
|
|
{
|
|
GThreadPool->Destroy();
|
|
}
|
|
#if USE_NEW_ASYNC_IO
|
|
if (GIOThreadPool != nullptr)
|
|
{
|
|
GIOThreadPool->Destroy();
|
|
}
|
|
#endif // USE_NEW_ASYNC_IO
|
|
|
|
#if WITH_ENGINE
|
|
if ( GShaderCompilingManager )
|
|
{
|
|
GShaderCompilingManager->Shutdown();
|
|
|
|
delete GShaderCompilingManager;
|
|
GShaderCompilingManager = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void FEngineLoop::AppExit( )
|
|
{
|
|
UE_LOG(LogExit, Log, TEXT("Exiting."));
|
|
|
|
FPlatformMisc::PlatformTearDown();
|
|
|
|
if (GConfig)
|
|
{
|
|
GConfig->Exit();
|
|
delete GConfig;
|
|
GConfig = nullptr;
|
|
}
|
|
|
|
if( GLog )
|
|
{
|
|
GLog->TearDown();
|
|
}
|
|
|
|
FInternationalization::TearDown();
|
|
}
|
|
|
|
|
|
void FEngineLoop::PreInitHMDDevice()
|
|
{
|
|
#if WITH_ENGINE && !UE_SERVER
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("nohmd")) && !FParse::Param(FCommandLine::Get(), TEXT("emulatestereo")))
|
|
{
|
|
// Get a list of modules that implement this feature
|
|
FName Type = IHeadMountedDisplayModule::GetModularFeatureName();
|
|
IModularFeatures& ModularFeatures = IModularFeatures::Get();
|
|
TArray<IHeadMountedDisplayModule*> HMDModules = ModularFeatures.GetModularFeatureImplementations<IHeadMountedDisplayModule>(Type);
|
|
|
|
// Iterate over modules, calling PreInit
|
|
for (auto HMDModuleIt = HMDModules.CreateIterator(); HMDModuleIt; ++HMDModuleIt)
|
|
{
|
|
IHeadMountedDisplayModule* HMDModule = *HMDModuleIt;
|
|
|
|
if (!HMDModule->PreInit())
|
|
{
|
|
// Unregister modules which fail PreInit
|
|
ModularFeatures.UnregisterModularFeature(Type, HMDModule);
|
|
}
|
|
}
|
|
}
|
|
#endif // #if WITH_ENGINE && !UE_SERVER
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|