Irrlicht引擎分析记录1

发布于 2009-12-11  226 次阅读


1.1   hello world入手

本打算使用rational rose进行分析,irr含有一些全局函数,无法直接使用,还是改用word为主线进行分析,rose作为制图的辅助工具。计划先从所有范例开始遍历重要的元素。

首先每个main中的第一个对象是个IrrlichtDevice接口的对象,这个对象通过一个全局函数createDevice创建。从手册中可以看到这个函数通过一个枚举参数来选择设备类型,支持d3d8/9 opengl 软件渲染器等。后面参数设置分辨率,位深,不再赘述。最后一个设置用户自定义的事件处理器,是在这个引擎中看到的第一个可以进行针对事件来costomize的地方。用一个OnEvent函数来设置所有的event handler,事件以几个枚举来进行罗列。

Irr引擎没有使用复杂的COM,但是有引用计数的机制,采用IRefcount来作为接口,即IrrlichtDevice即是一类实例,其接口是

IReferenceCounted

使用grab()增加 drop()减少其计数

即引用计数的对象从创建时1grab 到多一个地方引用多一个grab 每次用完都需要记的drop 所有用到的地方都drop的时候自动销毁.目前为止还没有看到irr有其他的资源管理机制,貌似Sample中都是使用引用计数的机制来管理动态对象(new出来的对象)的生存期。

 

嗯,IrrlichtDevice像是多啦A梦的四维空间袋,每集的搞笑桥段都从这个袋子里出现,也终止于这个袋子。在hello world sample中可以看到这个device对象随后通过一系列get获取其他基础设施:

IVideoDriver* driver = device->getVideoDriver();

ISceneManager* smgr = device->getSceneManager();

IGUIEnvironment* guienv = device->getGUIEnvironment();

 

 

从这里就可以看出,IDevice实际上相当于GOF中的工厂模式接口,这个接口是一切的“始作俑者”,有点类似MFC中的theApp

实现方式是通过给CreateDevice传递基本的参数,自动创建出符合系统环境和配置的底层驱动程序。这里驱动程序的概念不同于硬件驱动,而是软件工程范畴内的底层实现。

几步跟踪到达createDeviceEx这个实际创建设备的函数中,通过一个结构体的引用参数   params传递了前面设定的分辨率 窗口大小 全屏 颜色bit数等动态的环境信息,这些都是可以加装个配置文件或者在启动前通过对话框,控制台修改的。另外一半静态的环境信息来源于irrCompileConfig.h以及编译器配置的宏。这里就要引入配置文件了,在linux下开发的软件经常会有这样的1-n.h配置文件,其实也不足为奇了。

 

irrCompileConfig.h是鬼火引擎重要的配置文件,这个文件中大量使用#ifdef在编译阶段提供部分与硬件关联比较大的环境信息,以及可选的模块,比如各种格式图形文件读写的模块,这些内容在程序执行中一般也没有必要改变。

 

然后我们看这段代码:

 

 extern "C" IRRLICHT_API IrrlichtDevice* IRRCALLCONV createDeviceEx(const SIrrlichtCreationParameters& params)

 {

 

           IrrlichtDevice* dev = 0;

 

#ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_

           if (params.DeviceType == EIDT_WIN32 || (!dev && params.DeviceType == EIDT_BEST))

                    dev = new CIrrDeviceWin32(params);

#endif

 

#ifdef _IRR_COMPILE_WITH_OSX_DEVICE_

           if (params.DeviceType == EIDT_OSX || (!dev && params.DeviceType == EIDT_BEST))

                    dev = new CIrrDeviceMacOSX(params);

#endif

 

#ifdef _IRR_COMPILE_WITH_WINDOWS_CE_DEVICE_

           if (params.DeviceType == EIDT_WINCE || (!dev && params.DeviceType == EIDT_BEST))

                    dev = new CIrrDeviceWinCE(params);

#endif

。。。 。。。

 

这里可以看到CIrrDeviceWin32  CIrrDeviceMacOSX CIrrDeviceWinCE等针对不同平台的实现的设备(Device),这就是鬼火引擎实现跨系统的“交通要道”。各个平台下所需的其他基础环境都随这些类的构建而构建,之后使用IDevice*接口来调用这些“一线骨干”的get*系列获取的其他驱动,ui,场景管理器等骨干对象。继续跟踪进CIrrDeviceWin32  的构造函数,去熟悉win32的朋友可以一眼看出是创建win32窗口的代码 随后是创建drivers以及GUI和场景管理器。

 

 

好了,有了以上信息,我们可以在rational rose加以验证和更形象化地记忆了,这里使用一个类图表达引擎的抽象层次,使用时序图来表达重要对象的创建过程(以Win32vc编译器为例):

 

鬼火引擎跨平台体系相关类的Uml模型:

 

 

 

类型关系静态结构图:

 

附注:在构筑模型的时候一开始惊奇地发觉CIrrDeviceWin32 等和IrrlichtDevice没有直接的继承关系,直到加入CIrrDeviceStub之后所有的继承关系才自动呈现在图上,再次感叹 rational rose的强大 更明确了这些类型的关系。

浏览了一下CIrrDeviceStub的内容,实现了部分的接口,仍然是个虚类,可以说是个存放一些各平台公用数据的中间层,文件系统和日志系统的载入和清理也在这个类的构造和析构中完成。stub这个词的意思我觉得最合适的解释是与平台直接相关的类型根部,有了这个结构整个引擎对具体平台的依赖就可以被“连根拔除”了。

另外一个可能的意义是相对于具体的设备实现,这一层提供了用户接口传递来的参数和信息的存根,因为可以看到params那个参数结构体被整个存到了它的成员变量里。

另外这次在rose上逆向irr1.6工程没有一个错误提示,果然无码无本质啊~

 

stub类型的进一步思考:

我们都知道对于接口类为了排除多余的依赖性和隔离内部实现,一般是不应该有成员变量而只有函数的(在COM相关书籍和EffectiveC++中进一步讨论了这个主题),接口更主要的作用是强调系统调用界面的统一,方便系统的移植。然而在具体实施的时候,同一接口的不同实现往往需要有一些共用的数据,这里的stub中间层正是充当了这样的一种作用。我不清楚这个算不算好的风格,但是它的一个显而易见的好处是:stub中的共享的数据和默认方法实现可以减少重复代码,方便修改和维护。



点击数:15


吟游赛博空间 谱写01诗篇