模块化游戏引擎:DLL循环依赖

我想创建一个游戏引擎作为培训和组合项目,模块化方法听起来很有前途,但我在模块设计方面存在一些问题。

首先,我想创建低级模块,如渲染,应用程序,实用程序等,并将其用于高级模块(如Terrain)。 所以依赖会看起来像这个游戏<-Engine <-Terrain <-Rendering。

我想要创建像Rendering.Direct3D11和Rendering.OpenGL这样的多个渲染“子模块”。 这就是我会有循环依赖的地方。 子模块会使用Rendering和Rendering的接口来管理子模块,对吧? 游戏< - 发动机<-Terrain <-Rendering < - > Rendering.Direct3D11

我大概可以创建一个像RenderingInterfaces这样的模块,并打破循环依赖,但这看起来像是一个拙劣的解决方法。 我打算多次使用“子模块设计”,比如:Game <-Engine <-Application < - > Application.Windows

子模块设计是否丑陋? 有没有办法使用没有循环依赖的子模块设计?


你可以抽象地解决这个问题。 假设您有三个dylib: Game.dllRenderer.dllSubRenderer.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或类似的东西,和渲染器的构造和render方法可以实现Renderer.cpp您包括输出项目Renderer.dll

现在在SubRenderer.dll ,你可能有这样的功能:

// SubRenderer.h
class SubRenderer;
API SubRenderer* create_opengl_renderer();

这可以在SubRenderer.cpp中实现,它被编译/链接以输出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的更安全的设计。

有了这种系统,你有这些头依赖关系:

`Game.cpp->Renderer.h`
`Game.cpp->SubRenderer.h`
`SubRenderer.cpp->Renderer.h`

就模块依赖性而言:

`Game.dll->Renderer.dll`
`Game.dll->SubRenderer.dll`

就是这样 - 在任何地方都没有循环依赖。 Game.dll依赖于Renderer.dllSubRenderer.dll ,但Renderer.dllSubRenderer.dll完全相互独立。

这是有效的,因为这个Renderer可以使用一个SubRenderer给予它的虚拟接口,而不知道它是什么(因此不需要依赖于'sub-renderer'的具体类型)。

你可以把Renderer.h放在一个可以从所有三个项目集中访问的地方,并且有一个公共的包含路径(例如:在SDK目录中)。 没有必要重复它。


在你的设计中不应该有任何需要逆向依赖的东西。

这是关于接口的。 你的渲染模块需要一个本地渲染API(根据你的条件,子模块),但它不应该在意它是OpenGL还是Direct3D11。 API子模块只需公开一个公共API; 像CreatePrimitiveFromResource()RenderPrimitive() ...这些子模块不应该知道上层,他们只是公开他们的通用API。

换句话说,唯一需要的“依赖”是渲染模块依赖于渲染子模块(使用通用接口),渲染子模块不依赖于任何东西(在引擎中),它们只是暴露一个通用的界面。


简单的例子:

我们有一个渲染整数的渲染模块“IntRenderer”。 它的工作是将整数转换成字符并打印出来。 现在我们想要在控制台或窗口中打印子模块“IntRenderer.Console”和“IntRenderer.Window”。

因此,我们定义了我们的接口:子模块必须是一个导出函数的DLL void print( const char * );
这整个描述是我们的接口; 它描述了我们所有int呈现器子模块必须具有的普通公众的面孔。 在编程上,你可以说接口只是函数定义,但这只是术语的问题。

现在每个子模块都可以实现这个接口:

// IntRenderer.Console
DLLEXPORT void print( const char *str ) {
    printf(str);
}

// IntRenderer.Window
DLLEXPORT void print( const char *str ) {
    AddTextToMyWindow(str);
}

这样,int渲染器就可以使用import一个子模块,并使用printf(myFormattedInt); ,而不管子模块。

你可以很明显地根据你的需要定义你的接口,如果你想用C ++多态。
示例:子模块X必须是导出函数CreateRenderer()的DLL,该函数返回继承类Renderer的类并实现其所有虚函数。

链接地址: http://www.djcxy.com/p/30693.html

上一篇: Modular game engine: DLL circular dependencies

下一篇: Modular android project