模块化游戏引擎:DLL 循环依赖
Modular game engine: DLL circular dependencies
我想创建一个游戏引擎作为培训和投资组合项目,模块化方法听起来很有希望,但我在模块设计方面遇到了一些问题。
首先,我想创建低级模块,如渲染、应用程序、实用程序等,然后在 Terrain 等高级模块中使用它们。
所以依赖关系看起来像这样 Game<-Engine<-Terrain<-Rendering.
我想创建多个渲染 "sub modules",例如 Rendering.Direct3D11 和 Rendering.OpenGL。那就是我会有循环依赖的地方。子模块会使用 Rendering 接口,Rendering 需要管理子模块,对吧?
游戏<-引擎<-地形<-渲染<-->渲染.Direct3D11
我可能会创建一个像 RenderingInterfaces 这样的模块并打破循环依赖,但这似乎是一个棘手的解决方法。我打算多次使用 "sub module design" ,例如:
游戏<-引擎<-应用程序<-->Application.Windows
子模块设计丑吗?有没有办法使用没有循环依赖的子模块设计?
您的设计中应该不需要反向依赖。
这都是关于接口的。您的渲染模块需要本机渲染 API(子模块,用您的话来说),但它不应该关心它是 OpenGL 还是 Direct3D11。 API 子模块只需要公开一个公共的 API ;像CreatePrimitiveFromResource()
, RenderPrimitive()
...这些子模块不应该知道上层,它们只是暴露它们共同的API.
换句话说,唯一"dependencies"需要的是渲染模块依赖于渲染子模块(使用公共接口),而渲染子模块不依赖于任何东西(在你的引擎),它们只是公开了一个通用接口。
简单的例子:
我们有一个呈现整数的呈现模块"IntRenderer"。它的工作是将整数转换为字符并打印出来。现在我们想要子模块 "IntRenderer.Console" 和 "IntRenderer.Window",以在控制台或 window.
中打印
有了它,我们定义了我们的接口:子模块必须是导出函数的 DLL void print( const char * );
。
整个描述就是我们的界面;它描述了我们所有的 int 渲染器子模块必须具有的常见 public 面孔。在编程上,您可以说接口只是函数定义,但这只是一个术语问题。
现在每个子模块都可以实现接口:
// IntRenderer.Console
DLLEXPORT void print( const char *str ) {
printf(str);
}
// IntRenderer.Window
DLLEXPORT void print( const char *str ) {
AddTextToMyWindow(str);
}
这样,int 渲染器就可以只使用导入子模块,并使用 printf(myFormattedInt);
,而不管子模块。
您显然可以根据需要定义接口,如果需要,可以使用 C++ 多态性。
示例:子模块 X 必须是导出函数 CreateRenderer()
的 DLL,该函数 returns 和 class 继承 class Renderer
,并实现其所有虚拟函数。
你可以抽象地解决这个问题。假设您有三个动态库:Game.dll
、Renderer.dll
、SubRenderer.dll
.
渲染器界面可能如下所示(简化):
// Renderer.h
class SubRenderer
{
public:
virtual ~SubRenderer() {}
virtual void render() = 0;
};
class API Renderer
{
public:
explicit Renderer(SubRenderer* sub_renderer);
void render();
private:
SubRenderer* sub_renderer;
};
您可以将其粘贴在 Renderer.h
或类似的内容中,并且 Renderer 构造函数和 render
方法可以在 Renderer.cpp
中实现,您将其包含在输出 [= 的项目中17=].
现在 SubRenderer.dll
,你可能有这样的功能:
// SubRenderer.h
class SubRenderer;
API SubRenderer* create_opengl_renderer();
可以在SubRenderer.cpp
中实现,即compiled/linked输出`SubRenderer.dll。它可能看起来像这样:
// SubRenderer.cpp
#include "SubRenderer.h"
#include <Renderer.h>
class OpenGlRenderer: public SubRenderer
{
public:
virtual void render() override {...}
};
SubRenderer* create_opengl_renderer()
{
return new OpenGlRenderer;
}
最后但同样重要的是,在 Game.dll 中的某些源文件中,您可以在 Game.cpp
:
中执行类似的操作
// Game.cpp
#include <Renderer.h>
#include <SubRenderer.h>
int main()
{
SubRenderer* opengl_renderer = create_opengl_renderer();
Renderer renderer(opengl_renderer);
renderer.render(); // render a frame
...
delete opengl_renderer;
}
...当然希望有符合RAII的更安全的设计。
对于这种系统,您有这些 header 依赖项:
`Game.cpp->Renderer.h`
`Game.cpp->SubRenderer.h`
`SubRenderer.cpp->Renderer.h`
在模块依赖方面:
`Game.dll->Renderer.dll`
`Game.dll->SubRenderer.dll`
就是这样 -- 任何地方都没有循环依赖。 Game.dll
依赖于Renderer.dll
和SubRenderer.dll
,但是Renderer.dll
和SubRenderer.dll
是完全独立的。
这是可行的,因为这个 Renderer
可以使用给定其虚拟接口的 SubRenderer
而无需确切知道它是什么(因此不需要依赖于 'sub-renderer' 的具体类型)。
您可以将 Renderer.h
放在可以从所有三个项目集中访问的位置,这些项目具有共同的包含路径(例如:在 SDK
目录中)。无需复制。
我想创建一个游戏引擎作为培训和投资组合项目,模块化方法听起来很有希望,但我在模块设计方面遇到了一些问题。
首先,我想创建低级模块,如渲染、应用程序、实用程序等,然后在 Terrain 等高级模块中使用它们。 所以依赖关系看起来像这样 Game<-Engine<-Terrain<-Rendering.
我想创建多个渲染 "sub modules",例如 Rendering.Direct3D11 和 Rendering.OpenGL。那就是我会有循环依赖的地方。子模块会使用 Rendering 接口,Rendering 需要管理子模块,对吧? 游戏<-引擎<-地形<-渲染<-->渲染.Direct3D11
我可能会创建一个像 RenderingInterfaces 这样的模块并打破循环依赖,但这似乎是一个棘手的解决方法。我打算多次使用 "sub module design" ,例如: 游戏<-引擎<-应用程序<-->Application.Windows
子模块设计丑吗?有没有办法使用没有循环依赖的子模块设计?
您的设计中应该不需要反向依赖。
这都是关于接口的。您的渲染模块需要本机渲染 API(子模块,用您的话来说),但它不应该关心它是 OpenGL 还是 Direct3D11。 API 子模块只需要公开一个公共的 API ;像CreatePrimitiveFromResource()
, RenderPrimitive()
...这些子模块不应该知道上层,它们只是暴露它们共同的API.
换句话说,唯一"dependencies"需要的是渲染模块依赖于渲染子模块(使用公共接口),而渲染子模块不依赖于任何东西(在你的引擎),它们只是公开了一个通用接口。
简单的例子:
我们有一个呈现整数的呈现模块"IntRenderer"。它的工作是将整数转换为字符并打印出来。现在我们想要子模块 "IntRenderer.Console" 和 "IntRenderer.Window",以在控制台或 window.
中打印有了它,我们定义了我们的接口:子模块必须是导出函数的 DLL void print( const char * );
。
整个描述就是我们的界面;它描述了我们所有的 int 渲染器子模块必须具有的常见 public 面孔。在编程上,您可以说接口只是函数定义,但这只是一个术语问题。
现在每个子模块都可以实现接口:
// IntRenderer.Console
DLLEXPORT void print( const char *str ) {
printf(str);
}
// IntRenderer.Window
DLLEXPORT void print( const char *str ) {
AddTextToMyWindow(str);
}
这样,int 渲染器就可以只使用导入子模块,并使用 printf(myFormattedInt);
,而不管子模块。
您显然可以根据需要定义接口,如果需要,可以使用 C++ 多态性。
示例:子模块 X 必须是导出函数 CreateRenderer()
的 DLL,该函数 returns 和 class 继承 class Renderer
,并实现其所有虚拟函数。
你可以抽象地解决这个问题。假设您有三个动态库:Game.dll
、Renderer.dll
、SubRenderer.dll
.
渲染器界面可能如下所示(简化):
// Renderer.h
class SubRenderer
{
public:
virtual ~SubRenderer() {}
virtual void render() = 0;
};
class API Renderer
{
public:
explicit Renderer(SubRenderer* sub_renderer);
void render();
private:
SubRenderer* sub_renderer;
};
您可以将其粘贴在 Renderer.h
或类似的内容中,并且 Renderer 构造函数和 render
方法可以在 Renderer.cpp
中实现,您将其包含在输出 [= 的项目中17=].
现在 SubRenderer.dll
,你可能有这样的功能:
// SubRenderer.h
class SubRenderer;
API SubRenderer* create_opengl_renderer();
可以在SubRenderer.cpp
中实现,即compiled/linked输出`SubRenderer.dll。它可能看起来像这样:
// SubRenderer.cpp
#include "SubRenderer.h"
#include <Renderer.h>
class OpenGlRenderer: public SubRenderer
{
public:
virtual void render() override {...}
};
SubRenderer* create_opengl_renderer()
{
return new OpenGlRenderer;
}
最后但同样重要的是,在 Game.dll 中的某些源文件中,您可以在 Game.cpp
:
// Game.cpp
#include <Renderer.h>
#include <SubRenderer.h>
int main()
{
SubRenderer* opengl_renderer = create_opengl_renderer();
Renderer renderer(opengl_renderer);
renderer.render(); // render a frame
...
delete opengl_renderer;
}
...当然希望有符合RAII的更安全的设计。
对于这种系统,您有这些 header 依赖项:
`Game.cpp->Renderer.h`
`Game.cpp->SubRenderer.h`
`SubRenderer.cpp->Renderer.h`
在模块依赖方面:
`Game.dll->Renderer.dll`
`Game.dll->SubRenderer.dll`
就是这样 -- 任何地方都没有循环依赖。 Game.dll
依赖于Renderer.dll
和SubRenderer.dll
,但是Renderer.dll
和SubRenderer.dll
是完全独立的。
这是可行的,因为这个 Renderer
可以使用给定其虚拟接口的 SubRenderer
而无需确切知道它是什么(因此不需要依赖于 'sub-renderer' 的具体类型)。
您可以将 Renderer.h
放在可以从所有三个项目集中访问的位置,这些项目具有共同的包含路径(例如:在 SDK
目录中)。无需复制。