概述
呜哇哈哈哈哈哈哈哈哈哈哈哈哈哈哈
整理知识体系
源码版本5.3
插件与模块
基本使用
创建插件
编辑->插件->添加,创建一个Blank插件
看 .uplugin
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "TestBlank",
"Description": "",
"Category": "Other",
"CreatedBy": "cwl",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "TestBlank",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}
这个 Type 定义在 ModuleDescriptor.h
/**
* Environment that can load a module.
*/
namespace EHostType
{
enum Type
{
// Loads on all targets, except programs.
Runtime,
// Loads on all targets, except programs and the editor running commandlets.
RuntimeNoCommandlet,
// Loads on all targets, including supported programs.
RuntimeAndProgram,
// Loads only in cooked games.
CookedOnly,
// Only loads in uncooked games.
UncookedOnly,
// Deprecated due to ambiguities. Only loads in editor and program targets, but loads in any editor mode (eg. -game, -server).
// Use UncookedOnly for the same behavior (eg. for editor blueprint nodes needed in uncooked games), or DeveloperTool for modules
// that can also be loaded in cooked games but should not be shipped (eg. debugging utilities).
Developer,
// Loads on any targets where bBuildDeveloperTools is enabled.
DeveloperTool,
// Loads only when the editor is starting up.
Editor,
// Loads only when the editor is starting up, but not in commandlet mode.
EditorNoCommandlet,
// Loads only on editor and program targets
EditorAndProgram,
// Only loads on program targets.
Program,
// Loads on all targets except dedicated clients.
ServerOnly,
// Loads on all targets except dedicated servers.
ClientOnly,
// Loads in editor and client but not in commandlets.
ClientOnlyNoCommandlet,
//~ NOTE: If you add a new value, make sure to update the ToString() method below!
Max
};
}
这个 LoadingPhase 是加载阶段。同一个文件
/**
* Phase at which this module should be loaded during startup.
*/
namespace ELoadingPhase
{
enum Type
{
/** As soon as possible - in other words, uplugin files are loadable from a pak file (as well as right after PlatformFile is set up in case pak files aren't used) Used for plugins needed to read files (compression formats, etc) */
EarliestPossible,
/** Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks */
PostConfigInit,
/** The first screen to be rendered after system splash screen */
PostSplashScreen,
/** Loaded before coreUObject for setting up manual loading screens, used for our chunk patching system */
PreEarlyLoadingScreen,
/** 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
};
模块在这个函数
void UEditorEngine::Init(IEngineLoop* InEngineLoop)
也就是 UEditorEngine 的 Init 阶段加载。
FScopedSlowTask ModuleSlowTask((float)UE_ARRAY_COUNT(ModuleNames));
for (const TCHAR* ModuleName : ModuleNames)
{
ModuleSlowTask.EnterProgressFrame(1);
FModuleManager::Get().LoadModule(ModuleName);
}
上面有一个超长数组
看插件的 build.cs 可以配置一些模块依赖和搜索路径
如何包含这个插件,uproject 写
"Plugins": [
{
"Name": "TestBlank",
"Enabled": true
}
]
build.cs 写
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"TestBlank"
// ... add private dependencies that you statically link with here ...
}
);
项目目录下的多模块
时代进步了。。。
插件目录下多模块
以前是创建一个空插件T, 把Source下的内容,粘到 TestBlank 的 Source下。然后删除插件T
build.cs uplugin看着改。
现在是rider在Source右键创建module。效果一样
在uplugin会帮我们写上
"Modules": [
{
"Name": "TestBlank",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "T",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "TR",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
不编译模块
build.cs里面
bUsePrecompiled = true;
达到隐藏头文件的目的。
IMPLEMENT_MODULE(FTRModule, TR)
IS_MONOLITHIC 是一个预处理器宏,用于确定当前的编译配置是否为单体(monolithic)模式。 在单体模式下,所有的代码(包括引擎代码和游戏代码)都被编译到一个单独的可执行文件中。这种模式通常用于最终的产品发布,因为它可以提供更好的性能和更小的二进制文件大小。 相反,如果 IS_MONOLITHIC 为 false,则表示编译配置为模块化(modular)模式。在模块化模式下,代码被编译成多个动态链接库(DLLs),每个模块一个。这种模式通常用于开发过程中,因为它支持热重载(hot-reloading),即在不重启应用程序的情况下重新编译和加载代码。
所以直接看这个
#if IS_MONOLITHIC
// 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( TEXT(#ModuleName) ); \
/* Forced reference to this function is added by the linker to check that each module uses IMPLEMENT_MODULE */ \
extern "C" void IMPLEMENT_MODULE_##ModuleName() { } \
PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)
#else
static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( TEXT(#ModuleName) ); \
声明了一个全局static对象,是 FStaticallyLinkedModuleRegistrant 这个类,名字叫xx
他的构造函数,会这样子注册
/**
* Utility class for registering modules that are statically linked.
*/
template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant
{
public:
/**
* Explicit constructor that registers a statically linked module
*/
FStaticallyLinkedModuleRegistrant( FLazyName InModuleName )
{
// Create a delegate to our InitializeModule method
FModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw(
this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );
// Register this module
FModuleManager::Get().RegisterStaticallyLinkedModule(
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( )
{
return new ModuleClass();
}
};
绑定了一个代理,
注册 RegisterStaticallyLinkedModule,存到ModuleManager的一个 tarray 里面
/**
* Registers an initializer for a module that is statically linked.
*
* @param InModuleName The name of this module.
* @param InInitializerDelegate The delegate that will be called to initialize an instance of this module.
*/
void RegisterStaticallyLinkedModule( const FLazyName InModuleName, const FInitializeStaticallyLinkedModule& InInitializerDelegate )
{
PendingStaticallyLinkedModuleInitializers.Emplace( InModuleName, InInitializerDelegate );
}
我们找 PendingStaticallyLinkedModuleInitializers 哪里使用的,可以找到这个调用链。
IModuleInterface FModuleManager::LoadModule(const FName InModuleName, ELoadModuleFlags InLoadModuleFlags)
IModuleInterface FModuleManager::LoadModuleWithFailureReason(const FName InModuleName, EModuleLoadResult& OutFailureReason, ELoadModuleFlags InLoadModuleFlags)
void FModuleManager::ProcessPendingStaticallyLinkedModuleInitializers() const
源头 LoadModule 的调用就比较多了,我们可以得出结论。这个接口可以根据模块名字来加载模块。
IModuleInterface* FModuleManager::LoadModule(const FName InModuleName, ELoadModuleFlags InLoadModuleFlags)
{
LLM_SCOPE_BYNAME(TEXT("Modules"));
// We allow an already loaded module to be returned in other threads to simplify
// parallel processing scenarios but they must have been loaded from the main thread beforehand.
if(!IsInGameThread())
{
return GetModule(InModuleName);
}
EModuleLoadResult FailureReason;
IModuleInterface* Result = LoadModuleWithFailureReason(InModuleName, FailureReason, InLoadModuleFlags);
// This should return a valid pointer only if and only if the module is loaded
checkSlow((Result != nullptr) == IsModuleLoaded(InModuleName));
return Result;
}
非游戏线程走GetModule, 这里面大概是上锁,去一个对象Map里面找,主线程已经创建好的模块对象,没找到就是没有了。
/** Map of all modules. Maps the case-insensitive module name to information about that module, loaded or not. */
FModuleMap Modules;
游戏线程就走 LoadModuleWithFailureReason,看下这个函数
// Do fast check for existing module, this is the most common case
ModuleInfoPtr FoundModulePtr = FindModule(InModuleName);
if (FoundModulePtr.IsValid())
{
LoadedModule = FoundModulePtr->Module.Get();
if (LoadedModule)
{
// note: this function does not check (bIsReady || IsInGameThread()) the way GetModule() does
// that looks like a bug if called from off-game-thread
return LoadedModule;
}
}
开头是找,已经创建过了就直接返回
没找到就 AddModule,再 FindModule
if (!FoundModulePtr.IsValid())
{
// Update our set of known modules, in case we don't already know about this module
AddModule(InModuleName);
// Ptr will always be valid at this point
FoundModulePtr = FindModule(InModuleName);
// NOTE: Module is now findable , calls to Find or Load Module will find this module pointer
// but it's not initialized yet (bIsReady is false so Get from other threads will fail)
// this AddModule must be done before the module is initialized
// because StartupModule may call functions that Find/Load on this module
// and they should get back this pointer, even though it is not finished initializing yet
}
这里的AddModule 只是把模块信息
ModuleInfoRef ModuleInfo(new FModuleInfo());
加入到模块的一个Map里面
void FModuleManager::AddModule(const FName InModuleName)
{
// Do we already know about this module? If not, we'll create information for this module now.
if (!((ensureMsgf(InModuleName != NAME_None, TEXT("FModuleManager::AddModule() was called with an invalid module name (empty string or 'None'.) This is not allowed.")) &&
!Modules.Contains(InModuleName))))
{
return;
}
ModuleInfoRef ModuleInfo(new FModuleInfo());
#if !IS_MONOLITHIC
RefreshModuleFilenameFromManifestImpl(InModuleName, ModuleInfo.Get());
#endif // !IS_MONOLITHIC
// Make sure module info is added to known modules and proper delegates are fired on exit.
FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
}
这个模块信息代码很短
里面的 TUniquePtr
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())
{
// 。。。
ModuleInfo->Module->StartupModule();
// 。。。
}
}
创建module并startup
最后的返回值是 这个 IModuleInterface*
FInitializeStaticallyLinkedModule* 这个结构我们刚刚看到过
template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant
的构造函数里面
最后是个朴实无华的new
IModuleInterface* InitializeModule( )
{
return new ModuleClass();
}
模块初始化
位于 bool FEngineLoop::AppInit( ) 中
// Load "asap" plugin modules
IPluginManager& PluginManager = IPluginManager::Get();
IProjectManager& ProjectManager = IProjectManager::Get();
if (!ProjectManager.LoadModulesForProject(ELoadingPhase::EarliestPossible) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::EarliestPossible))
{
return false;
}
LoadModulesForProject
bool FProjectManager::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 )
{
// 。。
break;
}
}
FMessageDialog::Open(EAppMsgType::Ok, FailureMessage);
bSuccess = false;
}
}
OnLoadingPhaseCompleteEvent.Broadcast(LoadingPhase, bSuccess);
return bSuccess;
}
FModuleDescriptor::LoadModulesForPhase(LoadingPhase, CurrentProject->Modules, ModuleLoadFailures);
void FModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase, const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors)
{
FScopedSlowTask SlowTask((float)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);
}
}
}
}
}
按阶段加载模块,便利模块,看是不是当前阶段。然后 LoadModuleWithFailureReason,这个函数我们上面解释过
LoadModulesForEnabledPlugins
bool FPluginManager::LoadModulesForEnabledPlugins( const ELoadingPhase::Type LoadingPhase )
{
// Figure out which plugins are enabled
bool bSuccess = true;
if (!ConfigureEnabledPlugins())
{
bSuccess = false;
}
else
{
FScopedSlowTask SlowTask((float)AllPlugins.Num());
LLM_SCOPE_BYNAME(TEXT("Modules"));
// Load plugins!
for (const FDiscoveredPluginMap::ElementType& PluginPair : AllPlugins)
{
const TSharedRef<FPlugin>& Plugin = DiscoveredPluginMapUtils::ResolvePluginFromMapVal(PluginPair.Value);
SlowTask.EnterProgressFrame(1);
if (Plugin->bEnabled && !Plugin->Descriptor.bExplicitlyLoaded)
{
if (!TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase))
{
bSuccess = false;
break;
}
}
}
// This is a workaround for crashes when delaying the loading of binaries when using pak files/iostore. Should be removed
if (bPreloadedBinaries && LoadingPhase == ELoadingPhase::PostConfigInit)
{
UE_SCOPED_ENGINE_ACTIVITY("Preloading all plugin binaries");
for (const FDiscoveredPluginMap::ElementType& PluginPair : AllPlugins)
{
for (auto& ModuleName : DiscoveredPluginMapUtils::ResolvePluginFromMapVal(PluginPair.Value)->Descriptor.Modules)
{
if (ModuleName.IsCompiledInCurrentConfiguration())
{
FModuleManager::Get().LoadModuleBinaryOnly(ModuleName.Name);
}
}
}
}
}
// Some phases such as ELoadingPhase::PreEarlyLoadingScreen are potentially called multiple times,
// but we do not return to an earlier phase after calling LoadModulesForEnabledPlugins on a later phase
UE_CLOG(LastCompletedLoadingPhase != ELoadingPhase::None && LastCompletedLoadingPhase > LoadingPhase,
LogPluginManager, Error, TEXT("LoadModulesForEnabledPlugins called on phase %d after already being called on later phase %d."),
static_cast<int32>(LoadingPhase), static_cast<int32>(LastCompletedLoadingPhase));
// We send the broadcast event each time, even if this function is called multiple times with the same phase
LastCompletedLoadingPhase = LoadingPhase;
LoadingPhaseCompleteEvent.Broadcast(LoadingPhase, bSuccess);
return bSuccess;
}
TryLoadModulesForPlugin