Engine Source Analysis: Module Loading and Startup
引擎源码分析:模块的加载和启动
Posted onEdited onViews: Symbols count in article: 7.8kReading time ≈20 mins.
UE is a modular architecture, where Engine/Game Project/StandaloneApplication/Plugins are all Modules (Unreal Engine API Reference lists the Modules provided by the Engine). This article analyzes how UE’s Modules are loaded and started via FModuleManager::LoadModule using the code from FModuleManager.
Forward
This article references UE code from version 4.21.
Note: The IS_MONOLITHIC mentioned in this article refers to the LinkType in the user’s *.target.cs, which can be specified using TargetLinkType.Monolithic.
The meaning of TargetLinkType.Monolithic (Monolithic mode) is that all the code is placed into a single executable file, and during compilation, it uses methods such as implementation inclusion or static linking, without relying on other Modules’ DLLs.
FDefaultGameModuleImpl is a class that inherits from FDefaultModuleImpl and indirectly from IModuleInterface, without overriding any of IModuleInterface‘s functions, serving merely as a layer of encapsulated default implementation (game projects do not need to drive startup through Module’s StartupModule). You can pass your own class inheriting from IModuleInterface into the Module, overriding its interfaces to perform specific actions when the Module is loaded (the interfaces of IModuleInterface are listed later).
The IMPLEMENT_PRIMARY_GAME_MODULE macro first gets replaced with IMPLEMENT_GAME_MODULE, which then continues to replace with IMPLEMENT_MODULE. In the current engine implementation, these three macros have no distinction:
IMPLEMENT_PRIMARY_GAME_MODULE
IMPLEMENT_GAME_MODULE
IMPLEMENT_MODULE
Generally, what is commonly used in plugins is IMPLEMENT_MODULE (this macro is defined in Runtime\Core\Public\Modules\ModuleManger.h):
// If we're linking monolithically we assume all modules are linked in with the main binary. #define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \ /** Global registrant object for this module when linked statically */ \ static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( #ModuleName ); \ /** Implement an empty function so that if this module is built as a statically linked lib, */ \ /** static initialization for this lib can be forced by referencing this symbol */ \ void EmptyLinkFunctionForStaticInitialization##ModuleName(){} \ PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)
#else
#define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \ \ /**/ \ /* InitializeModule function, called by module manager after this module's DLL has been loaded */ \ /**/ \ /* @return Returns an instance of this module */ \ /**/ \ extern "C" DLLEXPORT IModuleInterface* InitializeModule() \ { \ return new ModuleImplClass(); \ } \ PER_MODULE_BOILERPLATE \ PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)
#endif//IS_MONOLITHIC
This macro defines and exports a generic module interface for external control of this Module’s startup/shutdown or other functionalities. This macro is essential for UE’s module mechanism to function.
IS_MONOLITHIC
When the LinkType in the compilation target’s target.cs is TargetLinkType.Monolithic, the logic used is IS_MONOLITHIC. In this mode, the IMPLEMENT_MODULE macro constructs a static object of FStaticallyLinkedModuleRegistrant<ModuleImplClass>, which in its constructor calls FModuleManager::RegisterStaticallyLinkedModule to add it to a TMap object (FModuleManager::StaticallyLinkedModuleInitializers) for subsequent LoadModule lookups:
/** * Explicit constructor that registers a statically linked module */ FStaticallyLinkedModuleRegistrant( const ANSICHAR* InModuleName ) { // Create a delegate to our InitializeModule method FModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw( this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );
// Register this module FModuleManager::Get().RegisterStaticallyLinkedModule( FName( InModuleName ), // Module name InitializerDelegate ); // Initializer delegate } /** * Creates and initializes this statically linked module. * * The module manager calls this function through the delegate that was created * in the @see FStaticallyLinkedModuleRegistrant constructor. * * @return A pointer to a new instance of the module. */ IModuleInterface* InitializeModule( ) { returnnewModuleClass(); } };
private: /** Map of module names to a delegate that can initialize each respective statically linked module */ typedef TMap< FName, FInitializeStaticallyLinkedModule > FStaticallyLinkedModuleInitializerMap; FStaticallyLinkedModuleInitializerMap StaticallyLinkedModuleInitializers; // ... };
In the context of IS_MONOLITHIC, the function FModuleManager::LoadModuleWithFailureReason (via FModuleManager::LoadModule) forwards to the TMap ModuleName-InitializerDelegate (FStaticallyLinkedModuleRegistrant::InitializeModule) to obtain the IModuleInterface of the specified module.
// Make sure this isn't a module that we had previously loaded, and then unloaded at shutdown time. // // If this assert goes off, your trying to load a module during the shutdown phase that was already // cleaned up. The easiest way to fix this is to change your code to query for an already-loaded // module instead of trying to load it directly. checkf((!ModuleInfo->bWasUnloadedAtShutdown), TEXT("Attempted to load module '%s' that was already unloaded at shutdown. FModuleManager::LoadModule() was called to load a module that was previously loaded, and was unloaded at shutdown time. If this assert goes off, your trying to load a module during the shutdown phase that was already cleaned up. The easiest way to fix this is to change your code to query for an already-loaded module instead of trying to load it directly."), *InModuleName.ToString());
// Check if we're statically linked with the module. Those modules register with the module manager using a static variable, // so hopefully we already know about the name of the module and how to initialize it. const FInitializeStaticallyLinkedModule* ModuleInitializerPtr = StaticallyLinkedModuleInitializers.Find(InModuleName); if (ModuleInitializerPtr != nullptr) { const FInitializeStaticallyLinkedModule& ModuleInitializer(*ModuleInitializerPtr);
// Initialize the module! ModuleInfo->Module = TUniquePtr<IModuleInterface>(ModuleInitializer.Execute());
if (ModuleInfo->Module.IsValid()) { // Startup the module ModuleInfo->Module->StartupModule(); // The module might try to load other dependent modules in StartupModule. In this case, we want those modules shut down AFTER this one because we may still depend on the module at shutdown. ModuleInfo->LoadOrder = FModuleInfo::CurrentLoadOrder++;
// Module was started successfully! Fire callbacks. ModulesChangedEvent.Broadcast(InModuleName, EModuleChangeReason::ModuleLoaded);
// Set the return parameter LoadedModule = ModuleInfo->Module.Get(); } else { UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function failed (returned nullptr.)"), *InModuleName.ToString()); OutFailureReason = EModuleLoadResult::FailedToInitialize; } } #if IS_MONOLITHIC else { // Monolithic builds that do not have the initializer were *not found* during the build step, so return FileNotFound // (FileNotFound is an acceptable error in some case - ie loading a content only project) UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Module '%s' not found - its StaticallyLinkedModuleInitializers function is null."), *InModuleName.ToString()); OutFailureReason = EModuleLoadResult::FileNotFound; } #endif // something.... };
!IS_MONOLITHIC
In the case of !IS_MONOLITHIC, it exports a symbol IModuleInterface* InitializeModule() (when LinkType is not specified as Monolithic in the target.cs - each Module is a separate linked library (lib/dll)). When using FModuleManager::LoadModuleWithFailureReason to load a specified Module, it first retrieves the Module’s linked library (DLL) path through FModuleManager::FindModulePaths: It will search through:
if (bCanUseCache) { // Try to use cache first if (const FString* ModulePathPtr = ModulePathsCache->Find(NamePattern)) { OutModulePaths.Add(FName(NamePattern), *ModulePathPtr); return; }
// Wildcard for all items if (FCString::Strcmp(NamePattern, TEXT("*")) == 0) { OutModulePaths = ModulePathsCache.GetValue(); return; } // Wildcard search if (FCString::Strchr(NamePattern, TEXT('*')) || FCString::Strchr(NamePattern, TEXT('?'))) { bool bFoundItems = false; FString NamePatternString(NamePattern); for (const TPair<FName, FString>& CacheIt : ModulePathsCache.GetValue()) { if (CacheIt.Key.ToString().MatchesWildcard(NamePatternString)) { OutModulePaths.Add(CacheIt.Key, *CacheIt.Value); bFoundItems = true; } }
if (bFoundItems) { return; } } }
// Search through the engine directory FindModulePathsInDirectory(FPlatformProcess::GetModulesDirectory(), false, NamePattern, OutModulePaths);
// Search any engine directories for (int Idx = 0; Idx < EngineBinariesDirectories.Num(); Idx++) { FindModulePathsInDirectory(EngineBinariesDirectories[Idx], false, NamePattern, OutModulePaths); }
// Search any game directories for (int Idx = 0; Idx < GameBinariesDirectories.Num(); Idx++) { FindModulePathsInDirectory(GameBinariesDirectories[Idx], true, NamePattern, OutModulePaths); } }
voidFModuleManager::FindModulePathsInDirectory(const FString& InDirectoryName, bool bIsGameDirectory, const TCHAR* NamePattern, TMap<FName, FString> &OutModulePaths)const { // Figure out the BuildId if it's not already set. if (!BuildId.IsSet()) { FString FileName = FModuleManifest::GetFileName(FPlatformProcess::GetModulesDirectory(), false);
FModuleManifest Manifest; if (!FModuleManifest::TryRead(FileName, Manifest)) { UE_LOG(LogModuleManager, Fatal, TEXT("Unable to read module manifest from '%s'. Module manifests are generated at build time, and must be present to locate modules at runtime."), *FileName) }
BuildId = Manifest.BuildId; }
// Find all the directories to search through, including the base directory TArray<FString> SearchDirectoryNames; IFileManager::Get().FindFilesRecursive(SearchDirectoryNames, *InDirectoryName, TEXT("*"), false, true); SearchDirectoryNames.Insert(InDirectoryName, 0);
// Enumerate the modules in each directory for(const FString& SearchDirectoryName: SearchDirectoryNames) { FModuleManifest Manifest; if (FModuleManifest::TryRead(FModuleManifest::GetFileName(SearchDirectoryName, bIsGameDirectory), Manifest) && Manifest.BuildId == BuildId.GetValue()) { for (const TPair<FString, FString>& Pair : Manifest.ModuleNameToFileName) { if (Pair.Key.MatchesWildcard(NamePattern)) { OutModulePaths.Add(FName(*Pair.Key), *FPaths::Combine(*SearchDirectoryName, *Pair.Value)); } } } } } #endif
After obtaining the linked library’s path, FPlatformProcess::GetDllExport is used to get the function pointer for IModuleInterface::InitializeModule in the DLL, followed by calling IModuleInterface::StartupModule to start this module.
// FModuleManager::LoadModuleWithFailureReason (Runtime\Core\Private\Modules\ModuleManager.cpp) IModuleInterface* FModuleManager::LoadModuleWithFailureReason(const FName InModuleName, EModuleLoadResult& OutFailureReason) { // something.... // Make sure that any UObjects that need to be registered were already processed before we go and // load another module. We just do this so that we can easily tell whether UObjects are present // in the module being loaded. if (bCanProcessNewlyLoadedObjects) { ProcessLoadedObjectsCallback.Broadcast(); }
// Determine which file to load for this module. const FString ModuleFileToLoad = FPaths::ConvertRelativePathToFull(ModuleInfo->Filename);
// Clear the handle and set it again below if the module is successfully loaded ModuleInfo->Handle = nullptr;
// Skip this check if file manager has not yet been initialized if (FPaths::FileExists(ModuleFileToLoad)) { ModuleInfo->Handle = FPlatformProcess::GetDllHandle(*ModuleFileToLoad); if (ModuleInfo->Handle != nullptr) { // First things first. If the loaded DLL has UObjects in it, then their generated code's // static initialization will have run during the DLL loading phase, and we'll need to // go in and make sure those new UObject classes are properly registered. { // Sometimes modules are loaded before even the UObject systems are ready. We need to assume // these modules aren't using UObjects. if (bCanProcessNewlyLoadedObjects) { // OK, we've verified that loading the module caused new UObject classes to be // registered, so we'll treat this module as a module with UObjects in it. ProcessLoadedObjectsCallback.Broadcast(); } }
// Find our "InitializeModule" global function, which must exist for all module DLLs FInitializeModuleFunctionPtr InitializeModuleFunctionPtr = (FInitializeModuleFunctionPtr)FPlatformProcess::GetDllExport(ModuleInfo->Handle, TEXT("InitializeModule")); if (InitializeModuleFunctionPtr != nullptr) { if ( ModuleInfo->Module.IsValid() ) { // Assign the already loaded module into the return value, otherwise the return value gives the impression the module failed load! LoadedModule = ModuleInfo->Module.Get(); } else { // Initialize the module! ModuleInfo->Module = TUniquePtr<IModuleInterface>(InitializeModuleFunctionPtr());
if ( ModuleInfo->Module.IsValid() ) { // Startup the module ModuleInfo->Module->StartupModule(); // The module might try to load other dependent modules in StartupModule. In this case, we want those modules shut down AFTER this one because we may still depend on the module at shutdown. ModuleInfo->LoadOrder = FModuleInfo::CurrentLoadOrder++;
// Module was started successfully! Fire callbacks. ModulesChangedEvent.Broadcast(InModuleName, EModuleChangeReason::ModuleLoaded);
// Set the return parameter LoadedModule = ModuleInfo->Module.Get(); } else { UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function failed (returned nullptr.)"), *ModuleFileToLoad);
FPlatformProcess::FreeDllHandle(ModuleInfo->Handle); ModuleInfo->Handle = nullptr; OutFailureReason = EModuleLoadResult::FailedToInitialize; } } } else { UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function was not found."), *ModuleFileToLoad);
FPlatformProcess::FreeDllHandle(ModuleInfo->Handle); ModuleInfo->Handle = nullptr; OutFailureReason = EModuleLoadResult::FailedToInitialize; } } else { UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because the file couldn't be loaded by the OS."), *ModuleFileToLoad); OutFailureReason = EModuleLoadResult::CouldNotBeLoadedByOS; } } else { UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because the file '%s' was not found."), *InModuleName.ToString(), *ModuleFileToLoad); OutFailureReason = EModuleLoadResult::FileNotFound; } // something.... }
IModuleInterface
Regardless of whether it’s through IS_MONOLITHIC or !IS_MONOLITHIC, the outcome is the IModuleInterface of the Module (Runtime\Core\Public\Modules\ModuleInterface.h), which declares the common Module interface:
classIModuleInterface { public: // Note: Even though this is an interface class we need a virtual destructor here because modules are deleted via a pointer to this interface virtual ~IModuleInterface(){}
// Called right after the module DLL has been loaded and the module object has been created // Load dependent modules here, and they will be guaranteed to be available during ShutdownModule. ie: // FModuleManager::Get().LoadModuleChecked(TEXT("HTTP")); virtualvoidStartupModule(){}
// Called before the module has been unloaded virtualvoidPreUnloadCallback(){}
// Called after the module has been reloaded
virtualvoidPostLoadCallback(){}
// Called before the module is unloaded, right before the module object is destroyed. // During normal shutdown, this is called in reverse order that modules finish StartupModule(). // This means that, as long as a module references dependent modules in its StartupModule(), it // can safely reference those dependencies in ShutdownModule() as well. virtualvoidShutdownModule(){}
// Override this to set whether your module is allowed to be unloaded on the fly // @return Whether the module supports shutdown separate from the rest of the engine. virtualboolSupportsDynamicReloading(){returntrue;}
// Override this to set whether your module would like cleanup on application shutdown // @return Whether the module supports shutdown on application exit virtualboolSupportsAutomaticShutdown(){returntrue;}
// Returns true if this module hosts gameplay code // @return True for "gameplay modules", or false for engine code modules, plugins, etc. virtualboolIsGameModule()const{returnfalse;} };
Load Modules when the Engine Launch
As mentioned earlier, UE is a modular architecture, and the engine’s implementation relies on various modules combined to drive it. Therefore, when the engine starts, various corresponding modules will be initiated. The entry point for engine startup is in the Launch module, which is forwarded from the main function of each platform to GuardedMain (Engine\Source\Runtime\Launch\Private\Launch.cpp). In GuardedMain, GEngineLoop.PreInit(CmdLine) and GEngineLoop.Init() are executed in sequence.
In FEngineLoop::PreInit, the engine loads the essential modules for startup by sequentially calling FEngineLoop::LoadCoreModules (LaunchEngineLoop.cpp#L1480) and FEngineLoop::LoadPreInitModules (LaunchEngineLoop.cpp#L1572). The loading order of the modules is as follows:
// LaunchEngineLoop.cpp FEngineLoop::PreInit() { // ... // L1480 // 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.")); return1; } // ...
// L1572 LoadPreInitModules(); // ...
// L2015 // note: Since 4.20 add ELoadingPhase::PreEarlyLoadingScreen Support. // Load up all modules that need to hook into the loading screen if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen)) { return1; } // ... // L2202 if ( !LoadStartupCoreModules() ) { // At least one startup module failed to load, return 1 to indicate an error return1; } // ...
// L2211 // Load up all modules that need to hook into the loading screen if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreLoadingScreen)) { return1; } // ...
// L2310 if ( !LoadStartupModules() ) { // At least one startup module failed to load, return 1 to indicate an error return1; } // ...
// L2457 // Load all the post-engine init modules ensure(IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit)); ensure(IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit)); // ... // L3044 // Load all the post-engine init modules if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit)) { GIsRequestingExit = true; return1; } // ...
// LaunchEngineLoop.cpp#L2679 boolFEngineLoop::LoadCoreModules() { // Always attempt to load CoreUObject. It requires additional pre-init which is called from its module's StartupModule method. #if WITH_COREUOBJECT return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr; #else returntrue; #endif }
#if !UE_SERVER if (!IsRunningDedicatedServer() ) { if (!GUsingNullRHI) { // This needs to be loaded before InitializeShaderTypes is called FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer"); } } #endif
// 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")); FModuleManager::Get().LoadModule(TEXT("AnimationModifiers")); #endif }
// FPlatformApplicationMisc::LoadPreInitModules voidFWindowsPlatformApplicationMisc::LoadPreInitModules() { // D3D11 is not supported on WinXP, so in this case we use the OpenGL RHI if(FWindowsPlatformMisc::VerifyWindowsVersion(6, 0)) { //#todo-rco: Only try on Win10 constbool bForceD3D12 = FParse::Param(FCommandLine::Get(), TEXT("d3d12")) || FParse::Param(FCommandLine::Get(), TEXT("dx12")); if (bForceD3D12) { FModuleManager::Get().LoadModule(TEXT("D3D12RHI")); } FModuleManager::Get().LoadModule(TEXT("D3D11RHI")); } FModuleManager::Get().LoadModule(TEXT("OpenGLDrv")); }
// initialize messaging SlowTask.EnterProgressFrame(10); if (FPlatformProcess::SupportsMultithreading()) { FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging"); }
// Init Scene Reconstruction support #if !UE_SERVER if (!IsRunningDedicatedServer()) { FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh"); } #endif
#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 }
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"));
// Load the StringTableEditor module to register its asset actions FModuleManager::Get().LoadModule("StringTableEditor");
if( !IsRunningDedicatedServer() ) { // VREditor needs to be loaded in non-server editor builds early, so engine content Blueprints can be loaded during DDC generation FModuleManager::Get().LoadModule(TEXT("VREditor")); } // -----------------------------------------------------
// 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")); }
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)) { returnfalse; }
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)) { returnfalse; }
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)) { returnfalse; }
returntrue; }
From the code above, it’s clear which modules the engine’s PreInit starts and the modules for the started plugins (determined by the plugin’s LoadingPhase).
Load Plugin Modules
In the previously organized code, it can be seen that the project’s plugin module is also loaded in FEngineLoop::PreInit. Those who have written UE plugins know that the UE plugin configuration (.uplugin) has two options: Type and LoadingPhase:
The Type and LoadingPhase in the Modules section determine whether the module of the plugin is loaded at engine startup and when it is loaded. Type is a required parameter, while LoadingPhase is optional, defaulting to Default.
Type: Determines whether the module is loaded in different runtime environments.
LoadingPhase: Determines when the module is loaded.
Plugin:Type (Required)
Sets the type of Module. Valid options are Runtime, RuntimeNoCommandlet, Developer, Editor, EditorNoCommandlet, and Program. This type determines which types of applications this Plugin’s Module is suitable for loading in. For example, some plugins may include modules that are only designed to be loaded when the editor is running. Runtime modules will be loaded in all cases, even in shipped games. Developer modules will only be loaded in development runtime or editor builds, but never in shipping builds. Editor modules will only be loaded when the editor is starting up. Your Plugin can use a combination of modules of different types.
The selectable options for Type are as follows (read from json in FModuleDescriptor::Read):
namespace EHostType { enumType { // Any target using the UE4 runtime Runtime, // Any target except for commandlet RuntimeNoCommandlet, // Any target or program RuntimeAndProgram, // Loaded only in cooked builds CookedOnly, // Loaded only when the engine has support for developer tools enabled Developer, // Loaded only by the editor Editor, // Loaded only by the editor, except when running commandlets EditorNoCommandlet, // Loaded only by programs Program, // Loaded only by servers ServerOnly, // Loaded only by clients ClientOnly, // NOTE: If you add a new value, make sure to update the ToString() method below!
Max }; // ... }
Plugin:LoadingPhase (Optional)
.uplugin Module LoadingPhase Descriptors.
If specified, controls when the plugin is loaded at start-up. This is an advanced option that should not normally be required. The valid options are Default (which is used when no LoadingPhase is specified), PreDefault, and PostConfigInit. PostConfigInit enables the module to be loaded before the engine has finished starting up key subsystems. PreDefault loads just before the normal phase. Typically, this is only needed if you expect game modules to depend directly on content within your plugin, or types declared within the plugin’s code.
If the LoadingPhase option is not specified in the .uplugin, it defaults to Default. The optional options for LoadingPhase are as follows:
namespace ELoadingPhase { enumType { /** Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks */ PostConfigInit,
/** Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers */ PreLoadingScreen,
/** Right before the default phase */ PreDefault,
/** Loaded at the default loading point during startup (during engine init, after game modules are loaded.) */ Default,
/** Right after the default phase */ PostDefault,
/** After the engine has been initialized */ PostEngineInit,
/** Do not automatically load this module */ None,
// NOTE: If you add a new value, make sure to update the ToString() method below! Max }; }
FProjectManager::LoadModulesForProject
In FEngineLoop::PreInit, the specified LoadingPhase plugin modules are loaded via the call to IProjectManager::Get().LoadModulesForProject:
Inside, the module loading is forwarded to FModuleDescriptor::LoadModulesForPhase and error checking is performed on the results (the compilation failures of plugins usually indicate issues at this step):
boolFProjectManager::LoadModulesForProject(const ELoadingPhase::Type LoadingPhase) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Game Modules"), STAT_GameModule, STATGROUP_LoadTime);
bool bSuccess = true;
if (CurrentProject.IsValid()) { TMap<FName, EModuleLoadResult> ModuleLoadFailures; FModuleDescriptor::LoadModulesForPhase(LoadingPhase, CurrentProject->Modules, ModuleLoadFailures);
if (ModuleLoadFailures.Num() > 0) { FText FailureMessage; for (auto FailureIt = ModuleLoadFailures.CreateConstIterator(); FailureIt; ++FailureIt) { const EModuleLoadResult FailureReason = FailureIt.Value();
if (FailureReason != EModuleLoadResult::Success) { const FText TextModuleName = FText::FromName(FailureIt.Key());
if (FailureReason == EModuleLoadResult::FileNotFound) { FailureMessage = FText::Format(LOCTEXT("PrimaryGameModuleNotFound", "The game module '{0}' could not be found. Please ensure that this module exists and that it is compiled."), TextModuleName); } elseif (FailureReason == EModuleLoadResult::FileIncompatible) { FailureMessage = FText::Format(LOCTEXT("PrimaryGameModuleIncompatible", "The game module '{0}' does not appear to be up to date. This may happen after updating the engine. Please recompile this module and try again."), TextModuleName); } elseif (FailureReason == EModuleLoadResult::FailedToInitialize) { FailureMessage = FText::Format(LOCTEXT("PrimaryGameModuleFailedToInitialize", "The game module '{0}' could not be successfully initialized after it was loaded."), TextModuleName); } elseif (FailureReason == EModuleLoadResult::CouldNotBeLoadedByOS) { FailureMessage = FText::Format(LOCTEXT("PrimaryGameModuleCouldntBeLoaded", "The game module '{0}' could not be loaded. There may be an operating system error or the module may not be properly set up."), TextModuleName); } else { ensure(0); // If this goes off, the error handling code should be updated for the new enum values! FailureMessage = FText::Format(LOCTEXT("PrimaryGameModuleGenericLoadFailure", "The game module '{0}' failed to load for an unspecified reason. Please report this error."), TextModuleName); }
// Runtime\Project\Private\ModuleDescriptor.cpp // LoadingPhase - is want load Module LoadingPhase Descriptor // Modules - is current project all modules(CurrentProject->Modules) // ModuleLoadErrors - is module-moduleLoadError mapping voidFModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase, const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors) { FScopedSlowTask SlowTask(Modules.Num()); for (int Idx = 0; Idx < Modules.Num(); Idx++) { SlowTask.EnterProgressFrame(1); const FModuleDescriptor& Descriptor = Modules[Idx];
// Don't need to do anything if this module is already loaded if (!FModuleManager::Get().IsModuleLoaded(Descriptor.Name)) { if (LoadingPhase == Descriptor.LoadingPhase && Descriptor.IsLoadedInCurrentConfiguration()) { // @todo plugin: DLL search problems. Plugins that statically depend on other modules within this plugin may not be found? Need to test this.
// NOTE: Loading this module may cause other modules to become loaded, both in the engine or game, or other modules // that are part of this project or plugin. That's totally fine. EModuleLoadResult FailureReason; IModuleInterface* ModuleInterface = FModuleManager::Get().LoadModuleWithFailureReason(Descriptor.Name, FailureReason); if (ModuleInterface == nullptr) { // The module failed to load. Note this in the ModuleLoadErrors list. ModuleLoadErrors.Add(Descriptor.Name, FailureReason); } } } } }
The implementation of this function is to iterate over all modules of the current project. When the LoadingPhase of the Module matches the passed LoadingPhase parameter and the Type of the Module meets the current runtime environment (judged by FModuleDescriptor::IsLoadedInCurrentConfiguration), the Module is loaded.### FModuleDescriptor::IsLoadedInCurrentConfiguration FModuleDescriptor::IsLoadedInCurrentConfiguration‘s role is to determine whether the current module’s Type matches the current runtime environment, and it returns a bool to decide whether to load the Module.
boolFModuleDescriptor::IsLoadedInCurrentConfiguration()const { // Check that the module is built for this configuration if(!IsCompiledInCurrentConfiguration()) { returnfalse; }
// Check that the runtime environment allows it to be loaded switch (Type) { case EHostType::RuntimeAndProgram: #if (WITH_ENGINE || WITH_PLUGIN_SUPPORT) returntrue; #endif break;