UE DynamicSubsystem详解

顾名思义,其为动态的Subsystem,会被动态对待。这里的动态特指随着模块的加载释放来创建和销毁。

为什么要如此设计?

首先要先理解UE的模块(Module)机制:

  • 一个uproject项目或uplugin插件可以包含多个Module。
  • 每个Module有自己的Build.cs。
  • 每个Module可以被编译成dll。
  • Module之间可以相互引用。

因此一个Module可能会有多个依赖的其他Modle,我们姑且称被依赖的Module为DependencyModules

假设

  1. 我有一个插件(Plugin)名为MyPlugin.uplugin,一个项目名为Hello.uproject。
  2. Hello.uproject想使用MyPlugin.uplugin插件。
  3. MyPlugin.uplugin里面依次定义了UMyPluginEngineSubsystem,UMyPluginEditorSubsystem和UMyPluginGameInstanceSubsystem。

我们可能有两种引用Module的情况,一种为显式添加Plugin / DependencyModules(即事先配置好),另一种为动态加载Plugin / DependencyModules。

显示添加Plugin / DependencyModules

配置Plugin / DependencyModules

  • 在Hello项目的插件设置上开启MyPlugin。
  • 或在Hello.uproject文件中手动添加 "Plugins": [{"Name": "MyPlugin","Enabled": true}]
  • 或在Build.cs中添加 PublicDependencyModuleNames.Add("MyOtherModule")

这样当Hello模块启动时,引擎会自动加载其依赖模块(MyPlugin)。UMyEngineSubsystem和UMyPluginSubsystem在编辑器启动时就会创建并Initialize。

Initialize函数的部分实现(点击展开/折叠代码块)

引擎除了加载了自定义的DynamicSubsystem,也加载了引擎自身实现的DynamicSubsystem,这些DynamicSubsystem被存储在DynamicSystemModuleMap(UE5源码中为GlobalDynamicSystemModuleMap)中,其中最重要的就是UnrealEd模块

UnrealEd模块本身定义了几个Subsystem:

  • AssetEditorSubsystem:资产编辑器子系统。
  • BrushEditingSubsystem
  • ImportSubsystem:用于在编辑器中导入资源的子系统,包含用hooking导入的实用函数和回调。
  • LayersSubsystem
  • PanelExtensionSubsystem:用于在编辑器中创建可扩展面板的子系统。

那MyPlugin里的Subsystem什么时候创建呢?

其实在第一次初始化时,会调用FSubsystemModuleWatcher的InitializeModuleWatcher函数。

InitializeModuleWatcher函数的部分实现(点击展开/折叠代码块)

在上述代码块中,首先是遍历了当前进程里的UDynamicSubsystem子类,并按照模块划分存储进DynamicSystemModuleMap。这样之后就知道当加载或销毁某个模块时应该加载和销毁哪些Subsystem。

其次是OnModuleChange事件的注册,这样就可以能够在模块加载/卸载的时候加载/卸载其包含的DynamicSubsystem

OnModuleChange函数的实现(点击展开/折叠代码块)

这其中的关键就是SubsystemCollections(在UE5的源码中为GlobalSubsystemCollections)是一个静态变量,其引用了整个进程中所有定义的FSubsystemCollection的数量。这么写其实是为了做一个保险,保证每个类型的SubsystemCollections都能正确的创建多个。

总结

整个流程其实就是在MyPlugin模块被加载的时候,会自动触发OnModulesChanged事件,从而被自动的创建出内部的UMypluginEngineSubsystem和UMyEngineSubsystem。

动态加载Plugin / DependencyModules

在UE中可以使用 FModuleManager::Get().LoadModule(TEXT("MyOtherModule")) 动态加载Module,如果这个模块里有定义DynamicSubsystem其会被创建出来。

对于Plugin必须要在MyPlugin.uplugin里面加上 "ExplicitlyLoaded": true ,这一句很重要,表明后续要显示的动态加载该插件。

根据路径动态加载Plugin(点击展开/折叠代码块)

在加载Plugin / DependencyModules后,其包含的UMyPluginEngineSubsystem和UMyPluginEditorSubsystem就会在这个时候被创建出来。

总结

MyPlugin里的DynamiacSubsystem对象虽然都是靠OnModulesChanged事件来创建和销毁自己,但是根据项目配置模块引用的不同,时机也可以不同。

DynamicSubsystem会根据Module的加载和释放来创建和销毁,这就是动态的含义。

动态加载MyPlugin里的UMyPluginGameInstanceSubsystem可以正常工作么?

只有MyPlugin已经被加载进来后,再点击Play,这个时候就可以正确的使用MyPlugin中的非DunamicSubsystem了。

为什么只有UEngineSubsystem和UEditorSubsystem才是UDynamicSubsystem?

从生命周期来考虑,只有UEngineSubsystem和UeditorSubsystem的生命周期是跟编辑器的进程绑定在一起的,且对于另外三个:UGameInstanceSubsystem,UWorldSubsystem和ULocalPlayerSubsystem其本身就是随着Play和Stop来反反复复的创建和销毁,本身已经足够动态了。

《InsideUE4》GamePlay架构(十一)Subsystems:https://zhuanlan.zhihu.com/p/158717151

发表回复