九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

微软内部都对HTML5和Silverlight的未来举棋不定?

    今天看到Irrlicht作者Ambiera推的一则有意思的消息,虽然其实与iPhone开发关系不大,但是因为牵涉到Windows Phone 7和HTML5,我还是大概看完了。

    消息原地址来自于:http://bit.ly/d3wEqD

     大概的含义是某人刚到MS总部一周,发现了MS内部在HTML5和SilverLight技术上的派系之争。并且,提到WPF已经死掉。

"Right now there’s a faction war inside Microsoft over HTML5 vs
Silverlight. oh and WPF is dead.. i mean..it kind of was..but now..
funeral."

 

文中还提到,HTML5是WPF的替代者,并且MS还是会对HTML执行它惯用的"拥抱并且扩展"(Embrace and extend")战略,然后分支HTML5,用本地化的Windows API来特殊化Windows下的JS/HTML5。

“HTML5 is the replacement for WPF.. IE team want to fork the HTML5 spec by bolting on custom windows APi’s via JS/HTML5”


从这个观点来看,SilverLight会边缘化,但是同时也看到了新的信息,因为新的Windows Phone 7中,普通app的开发平台都是SilverLight了,可见MS并不希望SilverLight边缘化。


这个其实并不是我最感兴趣的,最有意思的是,Hadi Hariri在他的博客上发表了一篇反SilverLight的文章。“WTF you got against Silverlight?!”

见:http://tinyurl.com/3x7r8p8

文中提到了很多有趣的观点,文后的评论质量更加是非常高,很多评论的信息量已经足够独立成文了,想到国内的"顶顶族"和“家具党”,我真是感到了国际差距。。。。。

 

文章和评论可以看出,国外对MS那种老是完全放弃老技术对开发者的伤害还是很大的,虽然从技术革新来上看,这是技术上很大胆和进步。另外,国外也的确有人很注意跨平台(CP),谈及MS的时候,Linux和MacOS都有提及,甚至有人说在做网页开发的时候,公司里面几乎没有后人真的装Windows来开发,仅仅通过虚拟机装Windows XP用于测试IE。感觉国外也有人不希望与MS"绑定"在一起.

最激进的MS反对者提出,Windows Phone 7最终没有会用,就是因为开发者都不想光是为了Windows Phone 7的开发而迁移到SilverLight平台,所以会鲜有开发者支持,MS有这个功夫,不如多花点时间
把自己的其他东西弄好

当然,也有人支持MS的,
具体内容大家自己去看吧,其实最感叹的是国外这些哥们谈论完全不同技术问题,并且带有派系之针的时候纯粹是就技术论技术,并且一条一条论据摆出来分析,结合周围实际情况来讲,我最疑问的是,为啥这样讨论都没有带骂娘的人生攻击呢?。。。。。。。。

 

 

背景参考:在IE9中,MS会支持新的HTML5技术,这是与其SilverLight直接竞争的技术,同时,MS新推的Windows Phone 7中,普通app的开发平台是Silverlight。。。。。。

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记 完结篇(7) 渲染流程

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

   本文应该是该系列的最后一篇,看看一个物体在Orx中从载入到显示的完整流程。

   在Orx,渲染部分,一直与object纠缠在一起,一般不直接由外部使用。最主要的函数是2个:

orxDisplay_LoadBitmap

orxDisplay_BlitBitmap

假如还需要加一个,那就是

orxDisplay_ClearBitmap

事实是向作者讨教过显示一个位图最快的方式,大概是下面这个样子的:

void
 GameApp::Update(const
 orxCLOCK_INFO *_pstClockInfo, void
 *_pstContext) {

    //m_states.top()->Update(_pstClockInfo->fDT);

  orxDisplay_ClearBitmap(orxDisplay_GetScreenBitmap(), orx2RGBA(0
, 0
, 0
, 0
));

  orxDisplay_BlitBitmap(orxDisplay_GetScreenBitmap(), spstBitmap, 100.0f
, 000.0f
, orxDISPLAY_BLEND_MODE_ALPHA);

}

orxSTATUS GameApp::Init() {

  spstBitmap = orxDisplay_LoadBitmap("Dragon.png"
);

  //orxDisplay_SetDestinationBitmap(orxDisplay_GetScreenBitmap());

  orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);

  

  /*
 Registers our update callback
*/

  orxClock_Register(pstClock, sUpdate, orxNULL, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);

  return
 orxSTATUS_SUCCESS;

}

也就是说,事实上也可以在Orx不通过配置,直接通过API,调用上面的3个函数完成图形的绘制。

 

orxDisplay_LoadBitmap

图形信息载入后保存的结构是:
/*
* Internal bitmap structure

 */

struct
 __orxBITMAP_t

{

  GLuint                    uiTexture;

  orxBOOL                   bSmoothing;

  orxFLOAT                  fWidth, fHeight;

  orxU32                    u32RealWidth, u32RealHeight, u32Depth;

  orxFLOAT                  fRecRealWidth, fRecRealHeight;

  orxCOLOR                  stColor;

  orxAABOX                  stClip;

};

在图形的载入过程中,在Orx中使用了SOIL这个外部的库,用于支持多种图形格式,而又可以不直接与jpeg和png的那一堆解压库打交道,说实话,就我使用那些库的感觉来说,为了通用及功能强大,API还是太复杂了一些,而且文档并不详细,而是用SOIL,Orx仅仅用

  /* Loads image */

  pu8ImageData = SOIL_load_image(_zFilename, (int *)&uiWidth, (int *)&uiHeight, (int *)&uiBytesPerPixel, SOIL_LOAD_RGBA);

这么一句,就支持了多种图形文件。

在orxDisplay_LoadBitmap的函数调用中,对图形的宽和高还是进行了修正,代码如下:

      /* Gets its real size */

      uiRealWidth   = orxMath_GetNextPowerOfTwo(uiWidth);

      uiRealHeight  = orxMath_GetNextPowerOfTwo(uiHeight);

使得宽高都位置在2的幂上,以支持更多的显卡。

 

然后保存好图形的相关信息,手工调用OpenGL进行纹理绑定:

      /*
 Creates new texture
*/

      glGenTextures(1
, &pstResult->uiTexture);

      glASSERT();

      glBindTexture(GL_TEXTURE_2D, pstResult->uiTexture);

      glASSERT();

      glTexImage2D(GL_TEXTURE_2D, 0
, GL_RGBA, pstResult->u32RealWidth, pstResult->u32RealHeight, 0
, GL_RGBA, GL_UNSIGNED_BYTE, pu8ImageBuffer);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (pstResult->bSmoothing != orxFALSE) ? GL_LINEAR : GL_NEAREST);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (pstResult->bSmoothing != orxFALSE) ? GL_LINEAR : GL_NEAREST);

      glASSERT();

用下面的语句删除临时载入的资源:

    /* Deletes surface */

    SOIL_free_image_data(pu8ImageData);

 

最后图形的相关信息,包括通过OpenGL载入的纹理的句柄,都保存__orxBITMAP_t
结构中。

 

orxDisplay_ClearBitmap

主要就是如下几句:

    /*
 Clears the color buffer with given color
*/

    glClearColor((1.0f
 / 255.f
) * orxU2F(orxRGBA_R(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_G(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_B(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_A(_stColor)));

    glASSERT();

    glClear(GL_COLOR_BUFFER_BIT);

 

只要了解OpenGL,这个没有什么好说的。

 

 

orxDisplay_BlitBitmap

    此函数是渲染流程中最主要的函数,其中又调用了如下函数完成绘制:

orxDisplay_GLFW_DrawBitmap(_pstSrc, _eSmoothing, _eBlendMode);

 

 

orxDisplay_GLFW_DrawBitmap

orxDisplay_GLFW_DrawBitmap分两部分:

1。用orxDisplay_GLFW_PrepareBitmap指定当前的纹理,及通过当前渲染的配置,调整OpenGL渲染状态,比如是否抗锯齿,是否应用定点色等。

2。实际进行纹理的映射,然后最终调用

/* Draws arrays */

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

完成图形的绘制。

全系列小结

    到这里,全部的Orx源代码阅读就结束了。Orx算是一个相对较小,实现游戏相关概念不多,但是相对完整,并且有特色的的一个游戏引擎了。完整的看下来,会发现,在2D游戏引擎中,其实渲染的这一部分代码非常少,基本上也就是一个OpenGL使用纹理映射显示位图的翻版,(可参考这里
)主要的部分在于整个游戏引擎的组织。这一点上,作为完全使用C语言完成的Orx来说,代码的组织上还是非常有可借鉴之处的。每个单独的模块都是用几个关键的结构,构成一个局部的(单文件中)全局结构实现,然后仅对外开放必要的接口,基本上能做到仅仅看到结构,就能了解整个模块的大概实现,看起来非常的清晰。在部分模块的实现中,也借鉴的面向对象的实现方式,实现了可配置的简单的update及init,setup等操作。这些都是引擎中的亮点。

    假如说Orx有什么缺点的话,通过通读代码,我最大的感觉就是因为其实现的大量可配置的特点,给引擎带来的太多额外的负担,比如update,init,setup等模块的实现,几乎都是从这个目的出发的,是引擎变得复杂了很多,而且,因为这个关系,引擎内部之间的模块设计,从物理设计上来看,太多的交错,离正交太远。

    这些可能是从一开始作者对Orx的数据驱动的理念是相关的,在作者看来,外部使用者甚至就不应该直接使用其更多的API,而是直接仅仅使用配置。而从我个人的感觉,已经在推广Orx的过程中一些中国使用者的反馈来看,在没有合适编辑的情况下,config的配置负担在Orx比编写和修改代码的负担要重更多,而且总体结合起来的易用性及开发的速度上面并不比一个不需要配置驱动但是API设计合理的传统2D引擎要强。而且,在使用上并没有获得更好的易用性,在实现上由于一开始的理念存在,也使得实现更加复杂。个人感觉有点得不偿失。Orx的设计是超前,但是目前来看,传统之所以成为传统,还有有其道理所在。

    个人认为,Orx可改进的方向有几个:

其一,Orx的底层就是应该通过传统的手法去构建,不要弄的配置那么深入,各个模块需要那么灵活的配置,实现上的负担可以减轻很多。并且,这一部分的API设计也是可以对外的。也有文档指明各个API的搭配使用,外部使用者可以在完全不使用配置的情况下,将Orx作为一个普通的不是数据驱动的引擎来使用。虽然这个比较不符合作者的初衷。

其二,配置驱动的设计完全构建在上面那一层传统的不是数据驱动的那一层游戏引擎上,只要下层的API设计合理,这完全可以做到。对于普通使用者来说,学习曲线也可以更加平缓,在学习和了解了底层的API使用后,配置仅仅是用于加速自己设计的实现,而不是仅仅知道配置,一旦某个地方的配置出现隐晦的错误就一筹莫展(这个问题我自己和很多中国用户都出现过),因为了解底层的API 的使用,出现类似问题,通过在上层的调试,(即读取配置使用底层接口这一层)就能很容易的发现。而且,配置的使用不应该成为强制性的,这仅仅是使用者提高开发速度的一种方式,假如使用者认为底层的API已经足够使用,完全可以不用配置层。前面这两条说简单点就是将数据驱动与传统的接口相分离,而不是真的作为引擎的强制项。

其三,配置其实不能完全等同与一般意义的数据,需要手工编写的格式严格的配置,会给使用者带来很大的负担,我有时甚至觉得写代码比写配置更加容易,起码出错了更加方便调试。这些问题就需要能够保证生成正确配置的工具来解决了。作为了游戏引擎,Orx在这方面还比较欠缺,因为作者“憎恨”写UI的特点,估计这也不能靠作者来完成了,需要Orx的社区力量来完成。没有工具生成配置的配置驱动,根本就称不上真正的数据驱动,不过是将写代码的负担移到写配置而已。。。。。。。。。。。。。。。。。。

以上纯属个人看法,而历史证明,由于懂得东西不是很多,所以我的个人看法很多是不正确的,比如前面我提到的Orx的list实现很奇怪的问题
。。。。。。。。。。。。。。。。。。所以请读者不要轻信上述问题,那仅仅是一家之言。

 

原创文章作者保留版权 转载请注明原作者 并给出链接





write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(6) C语言实现的面向对象

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    这一节,看的是我感觉Orx实现的最为"有技术"的部分了,C语言实现面向的技术,在SDL的源码
中看到过一种方法,Orx实现的是另外一种,相对来说可以支撑更加复杂和灵活的结构。

在SDL中,用一个结构中的函数指针的替换来实现了面向对象的效果。在Orx中则是一种类似大量需要最基础基类的面向对象语言一样,提供了所有对象的基类(结构)orxSTRUCTURE,其他结构需要"继承"(其实在Orx是包含orxSTRUCTURE)自这个结构,然后在使用时,可以在获取orxSTRUCTURE指针的情况下获得真正的"子类"对象指针,并且使用。虽然说是一种模拟面向对象的技术,其实也有些像handle的技术。

    在Orx中大量的结构都是在这个框架之下的。

下面是Structure的ID列表,下面这些都是属于Orx“面向对象体系”的一部分。

/*
* Structure IDs

 */

typedef
 enum
 __orxSTRUCTURE_ID_t

{

  /*
 *** Following structures can be linked to objects ***
*/

  orxSTRUCTURE_ID_ANIMPOINTER = 0
,

  orxSTRUCTURE_ID_BODY,

  orxSTRUCTURE_ID_CLOCK,

  orxSTRUCTURE_ID_FRAME,

  orxSTRUCTURE_ID_FXPOINTER,

  orxSTRUCTURE_ID_GRAPHIC,

  orxSTRUCTURE_ID_SHADERPOINTER,

  orxSTRUCTURE_ID_SOUNDPOINTER,

  orxSTRUCTURE_ID_SPAWNER,

  orxSTRUCTURE_ID_LINKABLE_NUMBER,

  /*
 *** Below this point, structures can not be linked to objects ***
*/

  orxSTRUCTURE_ID_ANIM = orxSTRUCTURE_ID_LINKABLE_NUMBER,

  orxSTRUCTURE_ID_ANIMSET,

  orxSTRUCTURE_ID_CAMERA,

  orxSTRUCTURE_ID_FONT,

  orxSTRUCTURE_ID_FX,

  orxSTRUCTURE_ID_OBJECT,

  orxSTRUCTURE_ID_SHADER,

  orxSTRUCTURE_ID_SOUND,

  orxSTRUCTURE_ID_TEXT,

  orxSTRUCTURE_ID_TEXTURE,

  orxSTRUCTURE_ID_VIEWPORT,

  orxSTRUCTURE_ID_NUMBER,

  orxSTRUCTURE_ID_NONE = orxENUM_NONE

} orxSTRUCTURE_ID;

这里的对象与前面讲到的
过的module其实是有些重叠的。但是总体上,module更加偏向于实现的概念,这里的对象更加倾向于游戏中实际对应的对象,也就是更加高层一些。

共有结构:

orxSTRUCTURE这个最基础基类的结构非常简单:

/*
* Public structure (Must be first derived structure member!)

 */

typedef
 struct
 __orxSTRUCTURE_t

{

  orxSTRUCTURE_ID eID;            /*
*< Structure ID : 4
*/

  orxU32          u32RefCounter;  /*
*< Reference counter : 8
*/

  orxU32          u32Flags;       /*
*< Flags : 12
*/

  orxHANDLE       hStorageNode;   /*
*< Internal storage node handle : 16
*/

} orxSTRUCTURE;

一个ID,一个引用计数,一个标志量,一个存储节点的HANDLE。

Handle:

typedef void *                  orxHANDLE;

在C中也就用做于任何类型的指针。。。。。。。。。

在同样的头文件中,还有下面这个type枚举:

/*
* Structure storage types

 */

typedef
 enum
 __orxSTRUCTURE_STORAGE_TYPE_t

{

  orxSTRUCTURE_STORAGE_TYPE_LINKLIST = 0
,

  orxSTRUCTURE_STORAGE_TYPE_TREE,

  orxSTRUCTURE_STORAGE_TYPE_NUMBER,

  orxSTRUCTURE_STORAGE_TYPE_NONE = orxENUM_NONE,

} orxSTRUCTURE_STORAGE_TYPE;

但是基础结构中没有表示类型的变量,而handle到底表示什么类型的变量,就是这里面的几种了,list或者tree了,这个问题见下面的实现结构部分.

这里还看不出太多的东西,下面看看内部的实现结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/


/*
* Internal storage structure

 */

typedef
 struct
 __orxSTRUCTURE_STORAGE_t

{

  orxSTRUCTURE_STORAGE_TYPE eType;            /*
*< Storage type : 4
*/

  orxBANK                  *pstNodeBank;      /*
*< Associated node bank : 8
*/

  orxBANK                  *pstStructureBank; /*
*< Associated structure bank : 12
*/

  union

  {

    orxLINKLIST             stLinkList;       /*
*< Linklist : 24
*/

    orxTREE                 stTree;           /*
*< Tree : 20
*/

  };                                          /*
*< Storage union : 24
*/

} orxSTRUCTURE_STORAGE;

/*
* Internal registration info

 */

typedef
 struct
 __orxSTRUCTURE_REGISTER_INFO_t

{

  orxSTRUCTURE_STORAGE_TYPE     eStorageType; /*
*< Structure storage type : 4
*/

  orxU32                        u32Size;      /*
*< Structure storage size : 8
*/

  orxMEMORY_TYPE                eMemoryType;  /*
*< Structure storage memory type : 12
*/

  orxSTRUCTURE_UPDATE_FUNCTION  pfnUpdate;    /*
*< Structure update callbacks : 16
*/

} orxSTRUCTURE_REGISTER_INFO;

/*
* Internal storage node

 */

typedef
 struct
 __orxSTRUCTURE_STORAGE_NODE_t

{

  union

  {

    orxLINKLIST_NODE stLinkListNode;          /*
*< Linklist node : 12
*/

    orxTREE_NODE stTreeNode;                  /*
*< Tree node : 16
*/

  };                                          /*
*< Storage node union : 16
*/

  orxSTRUCTURE *pstStructure;                 /*
*< Pointer to structure : 20
*/

  orxSTRUCTURE_STORAGE_TYPE eType;            /*
*< Storage type : 24
*/

} orxSTRUCTURE_STORAGE_NODE;

/*
* Static structure

 */

typedef
 struct
 __orxSTRUCTURE_STATIC_t

{

  orxSTRUCTURE_STORAGE        astStorage[orxSTRUCTURE_ID_NUMBER]; /*
*< Structure banks
*/

  orxSTRUCTURE_REGISTER_INFO  astInfo[orxSTRUCTURE_ID_NUMBER];    /*
*< Structure info
*/

  orxU32                      u32Flags;                           /*
*< Control flags
*/

} orxSTRUCTURE_STATIC;

/*
**************************************************************************

 * Static variables                                                        *

 **************************************************************************
*/

/*
* static data

 */

static
 orxSTRUCTURE_STATIC sstStructure;

这里的结构就没有以前那么清晰了,以前是看到结构大概就知道实现的。
orxSTRUCTURE_STATIC
的orxSTRUCTURE_STORAGE        astStorage[orxSTRUCTURE_ID_NUMBER];

用于为每个structure结构提供bank,bank还分成两种,

一种是pstNodeBank表示的节点的bank。一种是pstStructureBank是结构本身的bank。

此结构的

  union

  {

    orxLINKLIST             stLinkList;       /**< Linklist : 24 */

    orxTREE                 stTree;           /**< Tree : 20 */

  };                                          /**< Storage union : 24 */

部分很明显与

orxSTRUCTURE_STORAGE_NODE结构对应,表示的是一个orxSTRUCTURE_STORAGE_NODE结构结构的list或者tree。因为根据Orx中list/tree的使用方法。(比较奇怪,见以前的文章

orxSTRUCTURE_STORAGE_NODE结构的第一个成员是

  union

  {

    orxLINKLIST_NODE stLinkListNode;          /**< Linklist node : 12 */

    orxTREE_NODE stTreeNode;                  /**< Tree node : 16 */

  };

正好符合要求。

然后这个节点结构的内容还包括:

  orxSTRUCTURE *pstStructure;                 /**< Pointer to structure : 20 */

  orxSTRUCTURE_STORAGE_TYPE eType;            /**< Storage type : 24 */

难道是每个结构bank分配的内存指针最后都保存在这里面吗?

假如真是这样,那与

orxSTRUCTURE_STATIC
结构成员变量orxSTRUCTURE_REGISTER_INFO不是重复吗?

因为这个结构也包含:

  orxSTRUCTURE_STORAGE_TYPE     eStorageType; /*
*< Structure storage type : 4
*/

  orxU32                        u32Size;      /*
*< Structure storage size : 8
*/

 

这个问题暂时留着,等下面看流程的时候再来验证。

 

然后orxSTRUCTURE_REGISTER_INFO这个注册信息结构中还有个update函数的指针,很突出,类型是:


/*
* Structure update callback function type

 */

typedef
 orxSTATUS (orxFASTCALL *orxSTRUCTURE_UPDATE_FUNCTION)(orxSTRUCTURE *_pstStructure, const
 orxSTRUCTURE *_pstCaller, const
 orxCLOCK_INFO *_pstClockInfo);

 

    一个结构注册后,每次还需要进行update?

    哈哈,到这里,已经解答了我的疑惑,既然module与Structure都是用于总体的管理整个Orx的结构的,为啥还需要两个这样的组织管理方式?而且很多类还是属于module,structure两个组织结构管理的?而且,上面看到Structure的列表也说了,Structure更加倾向于游戏中具体的概念,module倾向于底层实现。到了这里,再加上Orx一贯的一切自动化的思路,Structure的核心作用就很明显了!那就是管理所有需要对象的创建及update!没错,与module管理所有module的依赖,初始化,退出一样,Structure主要就是管理创建新对象及Update,这个在Orx这个非典型的游戏引擎中非常重要。在大部分游戏引擎中,我们需要手动的写主循环,然后控制主循环,并将update从主循环中一直进行下去,同时手动创建对象,但是Orx是配置驱动的,我们通过配置创建了对象,创建了一堆的东西,然后就都不管了,全丢给Orx了,Orx就是通过Structure这样的结构来统一管理的,也就是说,Structure是Orx运转的基石。同时Structure与module的更明显不同也显现出来了,Structure管理的是对象,一个Structure的"子类"可以有很多个创建出来的对象,module是管理模块,每个模块就是唯一的一个全局对象,所以需要统一的进行初始化及退出处理。

    知道了这个以后,再回头来看看Structure列表,什么感觉?很熟悉啊,原来都是config中能够配置自动创建的对象!

    这也是为什么Orx会费很大精力将所有配置能够创建的对象集中在这个Structure体系中管理了,无论其对象最终是什么,在Orx中都需要对通过配置其进行创建,update,所以提炼出了这个最终的基类,并且对所有对象进行统一的管理。

 

同时,上面遗留的问题,也有了一些思路了,既然是用于update的,那么分配出来的对象不一定就一定马上update,所有才会有orxSTRUCTURE_REGISTER_INFO与orxSTRUCTURE_STORAGE中的重复,很明显,Orx是在某个结构REGISTER(注册后)才开始update的。

 

基本的思路已经清晰了,下面通过流程来验证一下:

因为viewport总是需要创建的,从它入手:

首先看viewport的结构:


/*
* Viewport structure

 */

struct
 __orxVIEWPORT_t

{

  orxSTRUCTURE      stStructure;              /*
*< Public structure, first structure member : 16
*/

  orxFLOAT          fX;                       /*
*< X position (top left corner) : 20
*/

  orxFLOAT          fY;                       /*
*< Y position (top left corner) : 24
*/

  orxFLOAT          fWidth;                   /*
*< Width : 28
*/

  orxFLOAT          fHeight;                  /*
*< Height : 32
*/

  orxCOLOR          stBackgroundColor;        /*
*< Background color : 48
*/

  orxCAMERA        *pstCamera;                /*
*< Associated camera : 52
*/

  orxTEXTURE       *pstTexture;               /*
*< Associated texture : 56
*/

  orxSHADERPOINTER *pstShaderPointer;         /*
*< Shader pointer : 60
*/

};

符合前面
Structure
注释中说明的要求,也就是第一个结构是orxSTRUCTURE
类型的成员变量。我发现Orx最喜欢利用这样的技巧,list的node也是,tree的node也是,可能iarwain最喜欢这样使用C语言吧,不过的确很有用,此时__orxVIEWPORT_t
结构对象的指针与其第一个变量stStructure
的指针位置是完全一样的,也就是说,此指针可以直接作为一个orxSTRUCTURE
来使用,使用的时候,通过orxSTRUCTURE
类型中保留的type信息,又能还原整个对象的信息,保存的时候无论多少类型这样的对象的确只需要都保存orxSTRUCTURE
结构指针就行了。

 

 

orxViewport_Create

函数中,调用orxStructure_Create并加上自己的STRUCTURE_ID来创建了viewport。

  /* Creates viewport */

  pstViewport = orxVIEWPORT(orxStructure_Create(orxSTRUCTURE_ID_VIEWPORT));

 

这个函数虽然只有一句,但是包含的信息很多,orxStructure_Create函数就像前面说的那样,利用Structure的Bank和node的bank分别为Structure和node分配缓存。分配后直接添加在orxSTRUCTURE_STORAGE的list/tree中。

前面的orxVIEWPORT类似强转,但是其实不是,因为不会如此将一个指针直接转成一个结构(非指针)了。

#define orxVIEWPORT(STRUCTURE)      orxSTRUCTURE_GET_POINTER(STRUCTURE, VIEWPORT)

#define orxSTRUCTURE_GET_POINTER(STRUCTURE, TYPE) ((orx##TYPE *)_orxStructure_GetPointer(STRUCTURE, orxSTRUCTURE_ID_##TYPE))

这里的关键是另一个函数:


/*
* Gets structure pointer / debug mode

 * @param[in]   _pStructure    Concerned structure

 * @param[in]   _eStructureID   ID to test the structure against

 * @return      Valid orxSTRUCTURE, orxNULL otherwise

 */

static
 orxINLINE orxSTRUCTURE *_orxStructure_GetPointer(const
 void
 *_pStructure, orxSTRUCTURE_ID _eStructureID)

{

  orxSTRUCTURE *pstResult;

  /*
 Updates result
*/

  pstResult = ((_pStructure != orxNULL) && (((orxSTRUCTURE *)_pStructure)->eID ^ orxSTRUCTURE_MAGIC_TAG_ACTIVE) == _eStructureID) ? (orxSTRUCTURE *)_pStructure : (orxSTRUCTURE *)orxNULL;

  /*
 Done!
*/

  return
 pstResult;

}

传进一个orxSTRUCTURE的指针(orxStructure_Create的返回值),然后传出一个新的orxSTRUCTURE结构指针,再进行强转,感觉很怪异。

因为事实上,就上面的分析,只需要提供一个orxSTRUCTURE结构的指针指向的的确是一个"子类"对象的内存,是可以直接强转使用的。

所以这里的确怪异。

orxSTRUCTURE_MAGIC_TAG_ACTIVE这个怪异的宏定义入手:

/*
* Structure magic number

 */

#ifdef __orxDEBUG__

  #define orxSTRUCTURE_MAGIC_TAG_ACTIVE  
0xDEFACED0

#else

  #define orxSTRUCTURE_MAGIC_TAG_ACTIVE  
0x00000000

#endif

可见是为了debug准备的。并且意图是在debug的时候检验结构的eID成员变量,debug时发现此eID实际指向的是一段未分配内存(野指针时),能够返回空。

那这个0xDEFACED0

是怎么来的呢?magic嘛。。。。我也不知道,可能是作者在debug的时候特意打上去的标记,因为与主流程无关,这里不深究了。

另外,对于viewport来说,我发现在Orx中其并没有update函数,所以在一个真正的viewport创建出来的时候就先通过

eResult = orxSTRUCTURE_REGISTER(VIEWPORT, orxSTRUCTURE_STORAGE_TYPE_LINKLIST, orxMEMORY_TYPE_MAIN, orxNULL);

注册了,最后的注册的update函数是orxNULL,应该就表示不需要update了吧。

下面看一个需要update的,比如animpointer。

orxObject_UpdateAll

函数中

会遍历所有的object:

 /* For all objects */

  for(pstObject = orxOBJECT(orxStructure_GetFirst(orxSTRUCTURE_ID_OBJECT));

      pstObject != orxNULL;

      pstObject = orxOBJECT(orxStructure_GetNext(pstObject)))

并且,还会:

          if
(pstObject->astStructure[i].pstStructure != orxNULL)

          {

            /*
 Updates it
*/

            if
(orxStructure_Update(pstObject->astStructure[i].pstStructure, pstObject, pstClockInfo) == orxSTATUS_FAILURE)

            {

              /*
 Logs message
*/

              orxDEBUG_PRINT(orxDEBUG_LEVEL_OBJECT, "Failed to update structure #
%ld
 for object <
%s
>."
, i, orxObject_GetName(pstObject));

            }

          }

 

注意,这里的结构的update是pstObject
调用的。也就是说,前面配置及自动化的那么多的部分,事实上并不是直接由Orx底层的clock直接调用的,而是由object来驱动的。

这里通过遍历Structure结构中的update函数,完成了每个object里面需要update的部分的update,因为Orx的配置几乎是以object为基础的,所以这样设计非常合理。

同时,这里也真实的再现了面向对象的基础需求之一,对不同的对象使用相同的接口,并且不关心具体是哪个对象。。。。。。。这里object就不关心内部那一个Structure指针的数组变量中每个变量具体存储的真实Structure类型了,只需要用同一的update回调即可。。。。。

 

到了这里,Orx的主要组织结构module和Structure都看过了,底层的基础结构和模块也看过了。整体的Orx主干已经清晰了,具体的每个Structure,module是怎么实现的,暂时就不看了,需要的时候看一看就很清楚了。

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(5) core部分(config,event)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    有的引擎的源代码是越看越吸引人的,越看越让人惊呼设计之精巧,实现之完美的,很遗憾的,Orx的源码不是属于那一类,使用的感觉也不像那一类,反而越看越觉得其设计思想上与主流思想格格不入。。。。。在上一节
中list,tree的设计中,这种感觉达到了顶峰。。。。。但是,对于一个这样跨平台的引擎,我还是会看下去,也许看完美的作品,我只能惊叹,看不完美的作品,我却可以了解到更多值得改进的地方吧。在看Orx的源代码的过程中,起码我是对C语言怎么来组织一个相对规模较大的工程有了一些了解,对于常年使用C++的我,这方面知识其实还是相对欠缺。

Core部分主要包含5个部分,Clock,Config,Event,Locale,System。其中Clock,Config和Event比较在Orx中占很重要的地位,会看的详细点,其他略看。另外,因为Clock依赖于structure部分,会等到看完structure以后再看。

System部分主要是用于获取系统事件,以及delay的,太过简单了,就这一句话了。
Locale虽然有些意思,但是算不是Orx的核心模块,对于对整个Orx的理解没有帮助,这个留在自己需要实现多语言的时候再回来参考吧。

Event

    Event在Orx也算是比较核心的内容了,贯穿整个系统,很多功能的使用,比如物理的碰撞处理等都是通过事件响应来完成的。
先看结构:
外部结构:

/*
* Public event structure

 */

typedef
 struct
 __orxEVENT_t
{
  orxEVENT_TYPE     eType;                            /*
*< Event type : 4
*/

  orxENUM           eID;                              /*
*< Event ID : 8
*/

  orxHANDLE         hSender;                          /*
*< Sender handle : 12
*/

  orxHANDLE         hRecipient;                       /*
*< Recipient handle : 16
*/

  void
             *pstPayload;                       /*
*< Event payload : 20
*/

} orxEVENT;

内部结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

/*
* Static structure

 */

typedef
 struct
 __orxEVENT_STATIC_t
{
  orxU32        u32Flags;                             /*
*< Control flags
*/

  orxHASHTABLE *pstHandlerTable;                      /*
*< Handler table
*/

} orxEVENT_STATIC;

/*
**************************************************************************

 * Module global variable                                                  *

 **************************************************************************
*/

static
 orxEVENT_STATIC sstEvent;

看了结构已经了解的差不多了,内部实现以Orx自己的hashtable为核心,实现快速的查找及事件的发送,外部以orxEVENT
结构的eType
,eID区分事件,pstPayload
用于事件实际结构的存储。
着重看两个实现:
添加事件关注的
orxSTATUS orxFASTCALL orxEvent_AddHandler(orxEVENT_TYPE _eEventType, orxEVENT_HANDLER _pfnEventHandler):



  /*
 No bank yet?
*/

  if
(pstBank == orxNULL)
  {
    /*
 Creates it
*/

    pstBank = orxBank_Create(orxEVENT_KU32_HANDLER_BANK_SIZE, sizeof
(orxEVENT_HANDLER), orxBANK_KU32_FLAG_NONE, orxMEMORY_TYPE_MAIN);

    /*
 Valid?
*/

    if
(pstBank != orxNULL)
    {
      /*
 Tries to add it to the table
*/

      if
(orxHashTable_Add(sstEvent.pstHandlerTable, _eEventType, pstBank) == orxSTATUS_FAILURE)
      {
        /*
 Deletes bank
*/

        orxBank_Delete(pstBank);
        pstBank = orxNULL;
      }
    }
  }




从以上代码我惊讶的发现,hashTable保存的不是一个个Handler的值,而直接是每个eventType对应的一个bank。bank的知识参考以前的文章

然后直接从bank中分配内存并赋值:

  /*
 Valid?
*/

  if
(pstBank != orxNULL)
  {
    orxEVENT_HANDLER *ppfnHandler;

    /*
 Creates a new handler slot
*/

    ppfnHandler = (orxEVENT_HANDLER *)orxBank_Allocate(pstBank);

    /*
 Valid?
*/

    if
(ppfnHandler != orxNULL)
    {
      /*
 Updates it
*/

      *ppfnHandler = _pfnEventHandler;

      /*
 Updates result
*/

      eResult = orxSTATUS_SUCCESS;
    }
  }
我开始很奇怪这种使用方式,直接就对分配的内存赋值
然后就不管了。后来想了想,因为bank毕竟是Orx自己的内存管理模块,这里将来对eventHandler的使用也肯定是遍历,那么Orx中就直接省去了对bank分配内存指针的保存,将来直接遍历bank了。。。。。起码现在是这样猜测的。

看Send部分后一定见分晓。
由于Send函数的源代码较短,全部贴出来了:
orxSTATUS orxFASTCALL orxEvent_Send(const
 orxEVENT *_pstEvent)
{
  orxBANK  *pstBank;
  orxSTATUS eResult = orxSTATUS_SUCCESS;

  /*
 Checks
*/

  orxASSERT(orxFLAG_TEST(sstEvent.u32Flags, orxEVENT_KU32_STATIC_FLAG_READY));
  orxASSERT(_pstEvent != orxNULL);

  /*
 Gets corresponding bank
*/

  pstBank = (orxBANK *)orxHashTable_Get(sstEvent.pstHandlerTable, _pstEvent->eType);

  /*
 Valid?
*/

  if
(pstBank != orxNULL)
  {
    orxEVENT_HANDLER *ppfnHandler;

    /*
 For all handler
*/

    for
(ppfnHandler = (orxEVENT_HANDLER *)orxBank_GetNext(pstBank, orxNULL);
        ppfnHandler != orxNULL;
        ppfnHandler = (orxEVENT_HANDLER *)orxBank_GetNext(pstBank, ppfnHandler))
    {
      /*
 Calls handler
*/

      if
((*ppfnHandler)(_pstEvent) == orxSTATUS_FAILURE)
      {
        /*
 Updates result
*/

        eResult = orxSTATUS_FAILURE;

        break
;
      }
    }
  }

  /*
 Done!
*/

  return
 eResult;
}


果然如我在Add Handler中看到Orx的操作后的猜测一样,获取bank,然后遍历bank.................虽然这样的确也能够实现功能,虽然这样可以省去一个容器变量用于存储handler,但是个人还是感觉这样的用法类似hack。。。。想象一下,你使用Win32 API的时候,直接遍历内存链是什么效果。。。。。。或者,bank在设计时就考虑了这样的用法吧。。。。即使如此,还是将bank一物两用了,又作为内存分配的缓存容器,同时还是个实际数据的缓存容器。。。。。

Config

对于以Config为驱动的Orx的来说,这应该算是最最核心的模块了,假如说Orx哪个部分我是最希望抽出来以后独立使用的话,估计也就是这个部分了。Orx的Config部分,不仅仅实现了一个普通Windows INI所需要的简单的那一部分,包括section和key=value的读取,还包括section的继承,key的引用,value的随机,vector value的读取等强大功能,很多功能甚至凌驾于Json等通用配置。当时,这样强大的config不是没有代价的,换来的是较为庞大的配置代码。光是orxConfig.c一个文件,代码行数就超过了4K。

还是先看结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

/*
* Config value type enum

 */

typedef
 enum
 __orxCONFIG_VALUE_TYPE_t
{
  orxCONFIG_VALUE_TYPE_STRING = 0
,
  orxCONFIG_VALUE_TYPE_FLOAT,
  orxCONFIG_VALUE_TYPE_S32,
  orxCONFIG_VALUE_TYPE_U32,
  orxCONFIG_VALUE_TYPE_BOOL,
  orxCONFIG_VALUE_TYPE_VECTOR,

  orxCONFIG_VALUE_TYPE_NUMBER,

  orxCONFIG_VALUE_TYPE_NONE = orxENUM_NONE

} orxCONFIG_VALUE_TYPE;

/*
* Config value structure

 */

typedef
 struct
 __orxCONFIG_VALUE_t
{
  orxSTRING             zValue;             /*
*< Literal value : 4
*/

  orxCONFIG_VALUE_TYPE  eType;              /*
*< Value type : 8
*/

  orxU16                u16Flags;           /*
*< Status flags : 10
*/

  orxU8                 u8ListCounter;      /*
*< List counter : 11
*/

  orxU8                 u8CacheIndex;       /*
*< Cache index : 12
*/

  union

  {
    orxVECTOR           vValue;             /*
*< Vector value : 24
*/

    orxBOOL             bValue;             /*
*< Bool value : 16
*/

    orxFLOAT            fValue;             /*
*< Float value : 16
*/

    orxU32              u32Value;           /*
*< U32 value : 16
*/

    orxS32              s32Value;           /*
*< S32 value : 16
*/

  };                                        /*
*< Union value : 24
*/

  union

  {
    orxVECTOR           vAltValue;          /*
*< Alternate vector value : 36
*/

    orxBOOL             bAltValue;          /*
*< Alternate bool value : 28
*/

    orxFLOAT            fAltValue;          /*
*< Alternate float value : 28
*/

    orxU32              u32AltValue;        /*
*< Alternate U32 value : 28
*/

    orxS32              s32AltValue;        /*
*< Alternate S32 value : 28
*/

  };                                        /*
*< Union value : 36
*/

} orxCONFIG_VALUE;

/*
* Config entry structure

 */

typedef
 struct
 __orxCONFIG_ENTRY_t
{
  orxLINKLIST_NODE  stNode;                 /*
*< List node : 12
*/

  orxSTRING         zKey;                   /*
*< Entry key : 16
*/

  orxU32            u32ID;                  /*
*< Key ID (CRC) : 20
*/

  orxCONFIG_VALUE   stValue;                /*
*< Entry value : 56
*/

  orxPAD(56
)

} orxCONFIG_ENTRY;

/*
* Config section structure

 */

typedef
 struct
 __orxCONFIG_SECTION_t
{
  orxLINKLIST_NODE  stNode;                 /*
*< List node : 12
*/

  orxBANK          *pstEntryBank;           /*
*< Entry bank : 16
*/

  orxSTRING         zName;                  /*
*< Section name : 20
*/

  orxU32            u32ID;                  /*
*< Section ID (CRC) : 24
*/

  orxU32            u32ParentID;            /*
*< Parent ID (CRC) : 28
*/

  orxS32            s32ProtectionCounter;   /*
*< Protection counter : 32
*/

  orxLINKLIST       stEntryList;            /*
*< Entry list : 44
*/

  orxPAD(44
)

} orxCONFIG_SECTION;

/*
* Config stack entry structure

 */

typedef
 struct
 __orxCONFIG_STACK_ENTRY_t
{
  orxLINKLIST_NODE    stNode;               /*
*< Linklist node : 12
*/

  orxCONFIG_SECTION  *pstSection;           /*
*< Section : 16
*/

} orxCONFIG_STACK_ENTRY;

/*
* Static structure

 */

typedef
 struct
 __orxCONFIG_STATIC_t
{
  orxBANK            *pstSectionBank;       /*
*< Section bank
*/

  orxCONFIG_SECTION  *pstCurrentSection;    /*
*< Current working section
*/

  orxBANK            *pstHistoryBank;       /*
*< History bank
*/

  orxBANK            *pstStackBank;         /*
*< Stack bank
*/

  orxLINKLIST         stStackList;          /*
*< Stack list
*/

  orxU32              u32Flags;             /*
*< Control flags
*/

  orxU32              u32LoadCounter;       /*
*< Load counter
*/

  orxSTRING           zEncryptionKey;       /*
*< Encryption key
*/

  orxU32              u32EncryptionKeySize; /*
*< Encryption key size
*/

  orxCHAR            *pcEncryptionChar;     /*
*< Current encryption char
*/

  orxLINKLIST         stSectionList;        /*
*< Section list
*/

  orxHASHTABLE       *pstSectionTable;      /*
*< Section table
*/

  orxCHAR             zBaseFile[orxCONFIG_KU32_BASE_FILENAME_LENGTH]; /*
*< Base file name
*/

} orxCONFIG_STATIC;

/*
**************************************************************************

 * Static variables                                                        *

 **************************************************************************
*/

/*
* static data

 */

static
 orxCONFIG_STATIC sstConfig;

还是那句话,看结构比看流程实在是更加有用,对于Orx来说,说了它再多的不是,它每个模块的结构之清晰,(虽然每个模块之间关系有点乱)真是很多软件设计根本没法比的。

 

所有的配置由sstConfig存储。orxHASHTABLE       *pstSectionTable; 用于快速的查找section,完整的section信息保存在stSectionList
中。

每个orxCONFIG_SECTION
结构本身又包含stEntryList
表示的list,存储的是orxCONFIG_ENTRY
结构,表示一组key=value对。zKey
自然表示完整的key,u32ID
表示CRC过的key。value保存在orxCONFIG_VALUE结构的
成员变量stValue
中。

orxCONFIG_VALUE
就是很多时候能在C/C++语言中看到的万能结构,用于保存一个可能为各种类型的变量。

 

zValue用于保存原始的value字符串,然后eType表示类型,具体的value由此type决定,保存在下面两个union中。

出去一些辅助成员,(比如stack,CurrentSection
等用于暂时缓存现在config操作的成员变量),主要的config变量是多层容器结构。

由config保存着section容器列表,每个section容器又包含entry容器列表,entry容器保存key=value对。

其实看到结构已经对Orx的Config了解的很清楚了,但是回头想想,对于类似的需求,真的也不会以其他方式实现,还不就是上层的东西包含一个下层东西的列表啊。

 

另外,section结构中是包含一个u32ParentID变量的,用于表示继承自哪个父section。

没有在结构中看到Orx拥有的引用,随机等内容,说明这些都是都是在解析config的时候搞定的。

下面只看一个函数orxConfig_Load,搞定这一个,基本其他也不用看了。

 

主要解析config的过程是下列这样一个for循环结构


    for
(u32Size = orxFile_Read(acBuffer, sizeof
(orxCHAR), orxCONFIG_KU32_BUFFER_SIZE, pstFile), u32Offset = 0
, bFirstTime = orxTRUE;
        u32Size > 0
;
        u32Size = orxFile_Read(acBuffer + u32Offset, sizeof
(orxCHAR), orxCONFIG_KU32_BUFFER_SIZE - u32Offset, pstFile) + u32Offset, bFirstTime = orxFALSE)
    {

.............

 

很夸张的for运用方式。。。。。。。。但是理解上没有什么问题,也就是遍历读取文件到一个叫acBuffer的buffer中。

然后遍历buffer中的每个字符开始解析:

每一行的解析直到这个时候为止:

        /*
 Comment character or EOL out of block mode or block character in block mode?
*/

        if
((((*pc == orxCONFIG_KC_COMMENT)
          || (*pc == orxCHAR_CR)
          || (*pc == orxCHAR_LF))
         && (bBlockMode == orxFALSE))
        || ((bBlockMode != orxFALSE)
         && (*pc == orxCONFIG_KC_BLOCK)))
        {

............

上面也就是找到了行结尾了。

当发现当前行有section的时候(通过[]来判断)

用 orxConfig_SelectSection(pcLineStart + 1);来完成section的创建及选择(假如已经创建了的话)

这样的意义在于无论多少个section,最后都会拼接成一个,并且实现Orx配置中后面出现的配置会覆盖前面的配置的特性。

static orxINLINE orxCONFIG_SECTION *orxConfig_CreateSection(const orxSTRING _zSectionName, orxU32 _u32SectionID, orxU32 _u32ParentID)
中有两句关键的代码:

          orxLinkList_AddEnd(&(sstConfig.stSectionList), &(pstSection->stNode));

          /* Adds it to table */
          orxHashTable_Add(sstConfig.pstSectionTable, _u32SectionID, pstSection);

以此完成section的添加,如看结构的时候分析的一样,分别添加进list和hashTable中。

 

 

而当发现当前行有key和value的时候:

if((pcKeyEnd != orxNULL) && (pcValueStart != orxNULL))

处理一下数据,通过orxConfig_AddEntry函数,添加一个入口。

 

AddEntry中会复制key和value到config中,

其中orxConfig_ComputeWorkingValue函数中处理了value的引用,继承,随机等问题。当然,其实也没有什么技术含量,实际也就是对value进行类似上面的字符解析而已。

然后用以下语句解答了结构中的分析。

orxLinkList_AddEnd(&(sstConfig.pstCurrentSection->stEntryList), &(pstEntry->stNode));

以上代码对当前选中的section的EntryList中添加了新的entry节点,也就是表示当前的key=value对。

以上代码基本已经完成了从section列表的创建到entry列表创建。

 

 

小结

其实,开始的时候我以为中间包含的字符解析,文件包含,继承,引用等内容,还有些看头,后来看了才发现,由于都用了很特殊的字符来表示,实际也就是找到相关的字符,然后处理的过程,没有啥技术含量。。。。。。。。。大失所望。。。。。。不过回头来想想,Martin Fowler说,傻子都会写让计算机理解的代码;而优秀程序员写的是人能看懂的代码。从这个角度看,config写的是很成功的了。。。。。只是抱着商业大片的人结果看的是平淡无奇的文艺片,可能有些失望吧。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

一些CRC算法的分析 用于 确认Orx HashTable的Bug

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

上文
我阅读了Orx自己实现的一套hashTable,经我过的初步分析,一个CRC算法作为key是有可能冲突的,但是并没有验证,作为bug提交的话,有些不够完整,所以,我写个测试程序真实的验证一下这个CRC算法,同时,也验证一下自己的分析是否正确。简而言之,就是验证此CRC算法在实际使用中,到底有无冲突的可能。

 

原理很简单,就是生成一堆字符串,然后传进此CRC算法,然后比较CRC后的值有无重复。此时我真希望可以使用Python,但是想想有写将接口使用Python API让Python使用的功夫,我都已经将测试代码写完了,于是作罢,还是老老实实的用C++吧。

 

先将整个Orx的Crc算法抽出来,如下:


#include


typedef
 unsigned
  long
          orxU32;
typedef
 char
                    orxCHAR;
#define orxCHAR_NULL              
'0'

#define orxASSERT assert

#define orxSTRING               orxCHAR *

#define orxNULL               (
0
)

static
 const
 orxU32 sau32CRCTable[256
] =
{
  0x00000000
, 0x04C11DB7
, 0x09823B6E
, 0x0D4326D9
, 0x130476DC
, 0x17C56B6B
, 0x1A864DB2
, 0x1E475005
,
  0x2608EDB8
, 0x22C9F00F
, 0x2F8AD6D6
, 0x2B4BCB61
, 0x350C9B64
, 0x31CD86D3
, 0x3C8EA00A
, 0x384FBDBD
,
  0x4C11DB70
, 0x48D0C6C7
, 0x4593E01E
, 0x4152FDA9
, 0x5F15ADAC
, 0x5BD4B01B
, 0x569796C2
, 0x52568B75
,
  0x6A1936C8
, 0x6ED82B7F
, 0x639B0DA6
, 0x675A1011
, 0x791D4014
, 0x7DDC5DA3
, 0x709F7B7A
, 0x745E66CD
,
  0x9823B6E0
, 0x9CE2AB57
, 0x91A18D8E
, 0x95609039
, 0x8B27C03C
, 0x8FE6DD8B
, 0x82A5FB52
, 0x8664E6E5
,
  0xBE2B5B58
, 0xBAEA46EF
, 0xB7A96036
, 0xB3687D81
, 0xAD2F2D84
, 0xA9EE3033
, 0xA4AD16EA
, 0xA06C0B5D
,
  0xD4326D90
, 0xD0F37027
, 0xDDB056FE
, 0xD9714B49
, 0xC7361B4C
, 0xC3F706FB
, 0xCEB42022
, 0xCA753D95
,
  0xF23A8028
, 0xF6FB9D9F
, 0xFBB8BB46
, 0xFF79A6F1
, 0xE13EF6F4
, 0xE5FFEB43
, 0xE8BCCD9A
, 0xEC7DD02D
,
  0x34867077
, 0x30476DC0
, 0x3D044B19
, 0x39C556AE
, 0x278206AB
, 0x23431B1C
, 0x2E003DC5
, 0x2AC12072
,
  0x128E9DCF
, 0x164F8078
, 0x1B0CA6A1
, 0x1FCDBB16
, 0x018AEB13
, 0x054BF6A4
, 0x0808D07D
, 0x0CC9CDCA
,
  0x7897AB07
, 0x7C56B6B0
, 0x71159069
, 0x75D48DDE
, 0x6B93DDDB
, 0x6F52C06C
, 0x6211E6B5
, 0x66D0FB02
,
  0x5E9F46BF
, 0x5A5E5B08
, 0x571D7DD1
, 0x53DC6066
, 0x4D9B3063
, 0x495A2DD4
, 0x44190B0D
, 0x40D816BA
,
  0xACA5C697
, 0xA864DB20
, 0xA527FDF9
, 0xA1E6E04E
, 0xBFA1B04B
, 0xBB60ADFC
, 0xB6238B25
, 0xB2E29692
,
  0x8AAD2B2F
, 0x8E6C3698
, 0x832F1041
, 0x87EE0DF6
, 0x99A95DF3
, 0x9D684044
, 0x902B669D
, 0x94EA7B2A
,
  0xE0B41DE7
, 0xE4750050
, 0xE9362689
, 0xEDF73B3E
, 0xF3B06B3B
, 0xF771768C
, 0xFA325055
, 0xFEF34DE2
,
  0xC6BCF05F
, 0xC27DEDE8
, 0xCF3ECB31
, 0xCBFFD686
, 0xD5B88683
, 0xD1799B34
, 0xDC3ABDED
, 0xD8FBA05A
,
  0x690CE0EE
, 0x6DCDFD59
, 0x608EDB80
, 0x644FC637
, 0x7A089632
, 0x7EC98B85
, 0x738AAD5C
, 0x774BB0EB
,
  0x4F040D56
, 0x4BC510E1
, 0x46863638
, 0x42472B8F
, 0x5C007B8A
, 0x58C1663D
, 0x558240E4
, 0x51435D53
,
  0x251D3B9E
, 0x21DC2629
, 0x2C9F00F0
, 0x285E1D47
, 0x36194D42
, 0x32D850F5
, 0x3F9B762C
, 0x3B5A6B9B
,
  0x0315D626
, 0x07D4CB91
, 0x0A97ED48
, 0x0E56F0FF
, 0x1011A0FA
, 0x14D0BD4D
, 0x19939B94
, 0x1D528623
,
  0xF12F560E
, 0xF5EE4BB9
, 0xF8AD6D60
, 0xFC6C70D7
, 0xE22B20D2
, 0xE6EA3D65
, 0xEBA91BBC
, 0xEF68060B
,
  0xD727BBB6
, 0xD3E6A601
, 0xDEA580D8
, 0xDA649D6F
, 0xC423CD6A
, 0xC0E2D0DD
, 0xCDA1F604
, 0xC960EBB3
,
  0xBD3E8D7E
, 0xB9FF90C9
, 0xB4BCB610
, 0xB07DABA7
, 0xAE3AFBA2
, 0xAAFBE615
, 0xA7B8C0CC
, 0xA379DD7B
,
  0x9B3660C6
, 0x9FF77D71
, 0x92B45BA8
, 0x9675461F
, 0x8832161A
, 0x8CF30BAD
, 0x81B02D74
, 0x857130C3
,
  0x5D8A9099
, 0x594B8D2E
, 0x5408ABF7
, 0x50C9B640
, 0x4E8EE645
, 0x4A4FFBF2
, 0x470CDD2B
, 0x43CDC09C
,
  0x7B827D21
, 0x7F436096
, 0x7200464F
, 0x76C15BF8
, 0x68860BFD
, 0x6C47164A
, 0x61043093
, 0x65C52D24
,
  0x119B4BE9
, 0x155A565E
, 0x18197087
, 0x1CD86D30
, 0x029F3D35
, 0x065E2082
, 0x0B1D065B
, 0x0FDC1BEC
,
  0x3793A651
, 0x3352BBE6
, 0x3E119D3F
, 0x3AD08088
, 0x2497D08D
, 0x2056CD3A
, 0x2D15EBE3
, 0x29D4F654
,
  0xC5A92679
, 0xC1683BCE
, 0xCC2B1D17
, 0xC8EA00A0
, 0xD6AD50A5
, 0xD26C4D12
, 0xDF2F6BCB
, 0xDBEE767C
,
  0xE3A1CBC1
, 0xE760D676
, 0xEA23F0AF
, 0xEEE2ED18
, 0xF0A5BD1D
, 0xF464A0AA
, 0xF9278673
, 0xFDE69BC4
,
  0x89B8FD09
, 0x8D79E0BE
, 0x803AC667
, 0x84FBDBD0
, 0x9ABC8BD5
, 0x9E7D9662
, 0x933EB0BB
, 0x97FFAD0C
,
  0xAFB010B1
, 0xAB710D06
, 0xA6322BDF
, 0xA2F33668
, 0xBCB4666D
, 0xB8757BDA
, 0xB5365D03
, 0xB1F740B4

};

orxU32 orxString_ContinueCRC(const
 orxSTRING _zString, orxU32 _u32CRC)
{
  register
 orxU32          u32CRC;
  register
 const
 orxCHAR  *pc;

  /*
 Checks
*/

  orxASSERT(_zString != orxNULL);

  /*
 Inits CRC
*/

  u32CRC = _u32CRC ^ 0xFFFFFFFFL
;

  /*
 For the whole string
*/

  for
(pc = _zString; *pc != orxCHAR_NULL; pc++)
  {
    /*
 Computes the CRC
*/

    u32CRC = sau32CRCTable[(u32CRC ^ *pc) & 0xFF
] ^ (u32CRC >> 8
);
  }

  /*
 Done!
*/

  return
(u32CRC ^ 0xFFFFFFFFL
);
}

 

由于在Orx中使用的时候,第2参数一直为0,所以函数实际可以改成下面这样

orxU32 orxString_ContinueCRC(const
 orxSTRING _zString)
{
  register
 orxU32          u32CRC;
  register
 const
 orxCHAR  *pc;

  /*
 Checks
*/

  orxASSERT(_zString != orxNULL);

  /*
 Inits CRC
*/

  u32CRC = 0
 ^ 0xFFFFFFFFL
;

  /*
 For the whole string
*/

  for
(pc = _zString; *pc != orxCHAR_NULL; pc++)
  {
    /*
 Computes the CRC
*/

    u32CRC = sau32CRCTable[(u32CRC ^ *pc) & 0xFF
] ^ (u32CRC >> 8
);
  }

  /*
 Done!
*/

  return
(u32CRC ^ 0xFFFFFFFFL
);
}

 

作为测试结果,我希望检验是否会在string较短时就发生冲突,并且,真的冲突的话,输出冲突的字符串。这里暂时仅测试ASCII的字符,按照Orx的实际使用,UTF8字符的冲突的可能性只会比这个高,不会比这个低。

突然觉得想出一个这样的测试算法也挺有意思的。

作为较短的函数,暂时将字符串长度定在20以下。

第一步:生成测试字符串:

这里为了简化,就全生成同样长度的小写字符串了,其实本来想遍历生成的,后来觉得太麻烦,想了想,其实没有必要,只要能找到冲突就行,不一定要遍历。


char
 *rand_str(char
 *str,const
 int
 len)
{
  char
 *c = str;
  for
(int
 i=0
; i
    *c='a'
+rand()%26
;
  }

  *c='0'
;
  return
 str;
}

用这个函数来生成随机指定长度的字符串。

 

我然后用下列函数来检验:


typedef
 hash_map<unsigned
 long
, list > resultType;
resultType result;
void
 testIt(char
* testString) {
  unsigned
 long
 crcValue = orxString_ContinueCRC(testString);

  resultType::iterator clashIt = result.find(crcValue);
  if
(clashIt != result.end()) {
    // have a clash

    list &clashList = (clashIt->second);
    clashList.push_back(testString);

    printf("Clash strings: "
);
    for
 (list::iterator it = clashList.begin();
      it != clashList.end();
      ++it) {
        printf("
%s
t
"
, it->c_str());
    }
    printf("
n
"
);
    return
;
  }

  list newVaule;
  newVaule.push_back(testString);
  result.insert(make_pair(crcValue, newVaule));
}

原理上也没有什么好说的了,就是一个hash_map<unsigned
 long
, list >

的变量,用于来保存目前测试过的所有字符串,key是CRC计算后的值,list用于存储字符串。这种用法消耗内存非常多。。。。。。。

 

驱动代码如下,需要增加测试数量的,改NumOftest是改测试字符串的长度,i的最大值是测试的次数。


int
 _tmain(int
 argc, _TCHAR* argv[])
{
  srand(time(NULL
));
  const
 int
 NumOftest = 11
;
  char
 test[NumOftest + 1
];
  for
(int
 i = 0
; i < 200000
; ++i) {
    rand_str(test, NumOftest);
    testIt(test);
  }

  system("PAUSE"
);

    return
 0
;
}

事实上, 经过测试,在上面这个级别我就测试出了4组冲突:

Clash strings: rdvohjmmqco      dzfmlbvurvy
Clash strings: ezmchjdxzvc      heugmqpnwhr
Clash strings: xuosifsuqnz      hdnklsfofvo
Clash strings: cxezqfgwkdy      hjccdfyaamc

 

20W的级别,长度为十的字符串。。。。。。。。。。。。这不是现实中可能出现的组合,出现这样的情况,Orx中的hashTable必然就会出现问题。。。。。

另外,这也说明了出现问题的概率不是小到宇宙毁灭的几率那样,谁也不能保证在Orx使用一个256长度的hashTable的时候会不会出现问题。。。。。。。。。。。。。。。。事实上,即使为了效率,也还是参考参考MPQ中暴雪对CRC的使用,或者是如一般std扩展中hashmap的使用吧。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(4) 用C实现的基本容器(List,HashTable,Tree)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

C语言中没有标准的容器,所以为了跨平台,Orx中自己实现了一套。

在Orx中作者分别实现了HashTable,List,Tree3个容器,虽然没有实现一个自己的String,但是为orxSTRING(实际为char*的typedef)实现了一堆的辅助函数。

下面分别来看看,为了不使本文成为一个类似algorithm in C的讲算法的文章,这里只看看使用和各容器的特性^^

List

以前有人说过,几乎每个程序员在职业生涯都会自己实现一个List,不知道是不是真这样,虽然我的确实现过。。。。。。。。。。
先看结构:

/*
* Node list structure

 */

typedef
 struct
 __orxLINKLIST_NODE_t
{
  struct
 __orxLINKLIST_NODE_t *pstNext;         /*
*< Next node pointer : 4
*/

  struct
 __orxLINKLIST_NODE_t *pstPrevious;     /*
*< Previous node pointer : 8
*/

  struct
 __orxLINKLIST_t      *pstList;         /*
*< Associated list pointer : 12
*/

} orxLINKLIST_NODE;

/*
* List structure

 */

typedef
 struct
 __orxLINKLIST_t
{
  orxLINKLIST_NODE *pstFirst;                   /*
*< First node pointer : 4
*/

  orxLINKLIST_NODE *pstLast;                    /*
*< Last node pointer : 8
*/

  orxU32            u32Counter;                 /*
*< Node counter : 12
*/

} orxLINKLIST;


从结构看,我感觉实现的有些特殊,我以前看到的一般的list实现(自然是指C语言中的)一般用一个结构就解决一切了,都是用list的node本身(一般是头结点)来表示一个list,Orx这里额外特别的抽象出了一个List结构,然后node还反过来有指针指向list,有点奇怪的是,没有看到数据存在哪里。。。。。。。。。。。然后,突然发现,我没有看到list的Create函数。。。。。。。。看了一下Orx对List的使用,原来都是直接使用orxLINKLIST
。。。。。。。。。。。。
再然后,没有地方存储数据(比如类似pData的指针)的疑惑也解开了。。。。。。所有使用这个list的地方都用一个以orxLINKLIST_NODE
为第一个成员变量的结构,然后node的指针,就相当于整个结构的指针了。。。。。。。。。。。。。
比如下面这个config的结构:
typedef struct __orxCONFIG_SECTION_t
{
  orxLINKLIST_NODE  stNode;                 /**< List node : 12 */
  orxBANK          *pstEntryBank;           /**< Entry bank : 16 */
  orxSTRING         zName;                  /**< Section name : 20 */
  orxU32            u32ID;                  /**< Section ID (CRC) : 24 */
  orxU32            u32ParentID;            /**< Parent ID (CRC) : 28 */
  orxS32            s32ProtectionCounter;   /**< Protection counter : 32 */
  orxLINKLIST       stEntryList;            /**< Entry list : 44 */

  orxPAD(44)

} orxCONFIG_SECTION;

谜底解开了,总的来说,这个List很诡异。。。。。。。。。。。。

HashTable

一个HashTable容器拥有O(1)的查找效率,被誉为20世纪最重要的编程领域发明之一。。。。。(不记得在哪看到的了)
先看看结构:
#define orxHASHTABLE_KU32_INDEX_SIZE                          256
/*
* Hash table cell definition.
*/

typedef
 struct
 __orxHASHTABLE_CELL_t
{
  orxU32                        u32Key;                       /*
*< Key element of a hash table : 4
*/

  void
                         *pData;                        /*
*< Address of data : 8
*/

  struct
 __orxHASHTABLE_CELL_t *pstNext;                      /*
*< Next cell with the same index : 12
*/

  orxPAD(12
)
} orxHASHTABLE_CELL;

/*
* Hash Table
*/

struct
 __orxHASHTABLE_t
{
  orxHASHTABLE_CELL  *apstCell[orxHASHTABLE_KU32_INDEX_SIZE]; /*
*< Hash table
*/

  orxBANK            *pstBank;                                /*
*< Bank where are stored cells
*/

  orxU32              u32Counter;                             /*
*< Hashtable item counter
*/

};


每个hashTable的数据自然是一个cell,存储在pData中,通过注释基本也能理解Orx的Hash table的结构了。
所有的数据通过apstCell[orxHASHTABLE_KU32_INDEX_SIZE];
数组来存储,并且通过某个较小的key(说明这个较小的key一定小于orxHASHTABLE_KU32_INDEX_SIZE
)来索引这个数据,假如有第一个较小的key冲突的情况,通过orxHASHTABLE_CELL
*pstNext
变量来保存成链状,并通过u32Key
这个32位的key来区别。这个结构与一般C++ STL扩展中的hashTable实现原理基本一样。(HashTable目前还不是C++标准库的内容,所以只在扩展库中存在)
谁说看结构比看流程清晰来着?说的实在是太对了。当结构是这样定的时候,流程还能玩出啥花来?

用下面这样的代码来测试一下,同时验证想法。
  orxHASHTABLE* hashTable = orxHashTable_Create(32
, orxHASHTABLE_KU32_FLAG_NONE, orxMEMORY_TYPE_MAIN);

  char
* firstKey = "FirstKey"
;
  char
* secondKey = "secondKey"
;
  char
* thirdKey = "thirdKey"
;
  orxHashTable_Add(hashTable, orxString_ToCRC(firstKey), firstKey);
  orxHashTable_Add(hashTable, orxString_ToCRC(secondKey), secondKey);
  orxHashTable_Set(hashTable, orxString_ToCRC(firstKey), secondKey);
  orxHashTable_Set(hashTable, orxString_ToCRC(thirdKey), thirdKey);
  char
* value = (char
*)orxHashTable_Get(hashTable, orxString_ToCRC(firstKey));
  orxHashTable_Delete(hashTable);

  printf("Value:
%s
"
, value);


解释:
orxHashTable_Create实际仅仅分配了内存而已。值得一提的仅仅是cell的内存使用上一节
提到的bank来分配的。
orxString_ToCRC用于将string进行CRC,没有啥好说的。只是不知道Orx的这个CRC效果怎么样,比如速度快不快,冲突几率高不高。
orxHashTable_Add中先判断是否有同样key的数据存在,存在的话会报错。
其中调用的下列函数非常关键,用于获取前面提到的较小的key,来合成数组的索引。原来就是取CRC出来的32位值的与orxHASHTABLE_KU32_INDEX_SIZE
匹配的低位而已。(虽然这里用取模会意思更加自然一些,但是一个取模操作比一个位操作慢的不是一点点)
static
 orxINLINE orxU32 orxHashTable_FindIndex(const
 orxHASHTABLE *_pstHashTable, orxU32 _u32Key)
{
  /*
 Checks
*/

  orxASSERT(_pstHashTable != orxNULL);

  /*
 Computes the hash index
*/

  return
(_u32Key & (orxHASHTABLE_KU32_INDEX_SIZE - 1
));
}

找到位置以后的操作就太自然了。

  /*
 Get the hash table index
*/

    u32Index = orxHashTable_FindIndex(_pstHashTable, _u32Key);

    /*
 Allocate a new cell if possible
*/

    pstCell = (orxHASHTABLE_CELL *)orxBank_Allocate(_pstHashTable->pstBank);

    /*
 If allocation succeed, insert the new cell
*/

    if
(pstCell != orxNULL)
    {
      /*
 Set datas
*/

      pstCell->u32Key = _u32Key;
      pstCell->pData  = _pData;
      pstCell->pstNext = _pstHashTable->apstCell[u32Index];

      /*
 Insert it
*/

      _pstHashTable->apstCell[u32Index] = pstCell;
      eStatus = orxSTATUS_SUCCESS;

      /*
 Updates counter
*/

      _pstHashTable->u32Counter++;
    }


由于iarwain的注释存在,我更加没有必要说太多了。值得一提的是可以看到这里面甚至没有判断这个数组索引所在的位置是否已经被占,然后进行冲突处理的操作。
原因在于先
pstCell->pstNext = _pstHashTable->apstCell[u32Index];

然后
_pstHashTable->apstCell[u32Index] = pstCell;

假如原来此位置为空,next也就是空了,假如原来此位置有原来的值(甚至可以是一个链),就将整个链接在现在新数据的后面。

然后看set函数:

  /*
 Traverse to find the key
*/

  pstCell = _pstHashTable->apstCell[u32Index];
  while
(pstCell != orxNULL && pstCell->u32Key != _u32Key)
  {
    /*
 Try with next cell
*/

    pstCell = pstCell->pstNext;
  }

  /*
 Cell found ?
*/

  if
(pstCell != orxNULL)
  {
    /*
 Set associated datas
*/

    pstCell->pData = _pData;
  }
  else

  {
    /*
 Allocate a new cell if possible
*/

    pstCell = (orxHASHTABLE_CELL *)orxBank_Allocate(_pstHashTable->pstBank);

    /*
 If allocation succeed, insert the new cell
*/

    if
(pstCell != orxNULL)
    {
      /*
 Set datas
*/

      pstCell->u32Key = _u32Key;
      pstCell->pData  = _pData;
      pstCell->pstNext = _pstHashTable->apstCell[u32Index];

      /*
 Insert it
*/

      _pstHashTable->apstCell[u32Index] = pstCell;
    }
  }

  /*
 Done!
*/

  return
 orxSTATUS_SUCCESS;


用于在此数组位置进行进一步的索引,找到32位key完全一致的值。
  while
(pstCell != orxNULL && pstCell->u32Key != _u32Key)
  {
    /*
 Try with next cell
*/

    pstCell = pstCell->pstNext;
  }



 /*
 Cell found ?
*/

  if
(pstCell != orxNULL)
  {
    /*
 Set associated datas
*/

    pstCell->pData = _pData;
  }
  else

  {


.....
}

则完美回答了我博客中以前评论中的踢出来的问题,当hashTable 进行Set的时候,假如原来这个Key不存在会进行什么操作,很明显,不存在就Add。

 

到了这里,Get,Delete函数我感觉已经没有太多讲的必要了,只要对hashTable的概念还算熟悉的,对Orx的hashtable应该有足够的了解了,无论是实现的原理还是效率。

 

我小结一下:

1、Orx的HashTable定死了apstCell[orxHASHTABLE_KU32_INDEX_SIZE]这个数组的大小,orxHASHTABLE_KU32_INDEX_SIZE
等于256,所以,很自然的,此hashTable

在数量较小的时候才能保持较高的效率,(即HashTable的理论值O(1))假如数量远远超过256,那么查询效率几乎接近线性效率O(n)。

2、Orx这里的索引完全依赖于其String的32位CRC计算,但是很遗憾的,CRC的计算时会有冲突的,也就是会出现两个不同String但是计算出同样CRC的情况,我没有仔细了解这个CRC的计算,但是将整个HashTable的基础建立在这个不稳定的基石上,总的来说已经说明了这个HashTable存在问题。。。。。。。。。。。。。

 

Orx的HashTableKey是建立在String上的,为了提高String的比较效率,所以对String进行了CRC,效率是高了,但是引发了第2点说的问题。

但是这里其实不是没有解决方案的。

1。高效的方案:如暴雪的MPQ那样,不以一个CRC来决定一切,额外用2个来做保险,也就是进行3次不同的CRC运算,同时比较3个CRC都一样时才能确定一个真正的数据位置。此方法比Orx的方法多2次CRC计算,并且多2次整数比较,但是出错几率会比Orx的低的多。(在实际中,就算到几十万这个等级,我也没有碰到过3个CRC同时冲突)

2。而作为对比,C++中实现的hashTable(可以参考VS或者boost的hashMap实现),由于模板的存在,可以使用任何东西作为key,同时,在第一次快速索引时,使用一个hash值,当hash值冲突后,完全使用原来的key来进行比较,虽然假如是字符串的话会稍微慢一点,但是起码能保证绝对无错。

 

 

Tree

还是先看结构:


/*
* Tree node structure

 */

typedef
 struct
 __orxTREE_NODE_t
{
  struct
 __orxTREE_NODE_t *pstParent;           /*
*< Parent node pointer : 4
*/

  struct
 __orxTREE_NODE_t *pstChild;            /*
*< First child node pointer : 8
*/

  struct
 __orxTREE_NODE_t *pstSibling;          /*
*< Next sibling node pointer : 12
*/

  struct
 __orxTREE_t      *pstTree;             /*
*< Associated tree pointer : 16
*/

} orxTREE_NODE;

/*
* Tree structure

 */

typedef
 struct
 __orxTREE_t
{
  orxTREE_NODE *pstRoot;                        /*
*< Root node pointer : 4
*/

  orxU32        u32Counter;                     /*
*< Node counter : 8
*/

} orxTREE;

 

看到结构的时候,从list的诡异中我诡异的理解了这个Tree的意思,也就是,这个tree的用法也和list类似了。。。。。。。。。。。。

我虽然以前学习数据结构的时候是以伪码和C++的实现为主,没有看过太多C语言实现的数据结构,但还真不是完全没有见过,我感觉Orx的实现是很诡异的。。。。。。使用方式也是在算不上正统,为了求证,以防止是因为自己的孤陋寡闻引起的笑话,特意求证glib实现。。。。。。

比如GList


typedef
 struct
 _GList GList;

struct
 _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

一看到结构。。。。。。。。。。。。我大呼。。。。。数据结构的书籍还是没有骗我的啊。。。。。。。。。。。。。。。。。诡异的还是Orx。。。。。。。。。哪有需要自己一定要用一个新结构来包含Node来用一个list/tree的话。。。。。我要保存一个整数怎么办?。。。。。。。。简单的说,Orx的这个容器实现的非主流,不推荐大家使用及学习。。。。我这里也仅仅为了了解Orx的源代码而看看吧。。。。。。。。

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

Cocos2D For IPhone中的慢动作

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    慢动作是游戏中常用的特效手法,感觉Cocos2D这样完善的引擎应该原生支持才对,我在网上搜索了一下,竟然没有搜到,(不知道英文是不是该搜索Slow Action,但是原来公司的项目都这样叫的)。
    于是自己实现了一下,在自己项目的最底层的update中,添加一个slowAction的scale值,设定每次将update的参数ts传进来后,立刻乘以此scale值。只要原来的代码都是时间相关的,可以看到慢动作效果了。刚开始都还好,但是Cocos2D的粒子效果不吃这套,由于粒子系统不受我控制,我没法强行改变其值。本来还想通过全局变量强行改cocos2D的源代码的,在粒子系统的step函数中下了断点,然后通过callback stack中看到了sharedDirector调用了sharedScheduler的tick来实现cocos2d内部的update,并且很惊奇的发现了CCScheduler的timeScale变量。

于是,本文真正的非废话就下面一句:
[[CCScheduler sharedScheduler] setTimeScale:倍率];

同时,这也得原来的代码都是时间相关才能实现的,假如都是按帧走的,那么就哭去吧。。。。。。。。。。。。。所以说,很久很久以前,大家就开始认为时间相关的代码优于帧相关的代码不是没有道理的。。。。。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(3) 内存管理

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

内存管理在任何希望达到高效的游戏引擎中都是基础,我以前恰好做过类似的工作,所以这里先看看Orx的实现,再谈谈自己以往做类似工作时的经验。

Memory模块

个人感觉这是非常没有必要成为一个模块的模块。因为实质上没有任何必要的全局变量需要初始化,退出的时候也没有任何需要的扫尾工作,仅仅是几个函数而已,为啥Orx中这些函数会集中在一起作为一个Orx的模块,除了出于逻辑上一致的考虑,我感觉纯粹属于过度设计。。。。。。。。起码从目前来看是这样。

extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Allocate(orxU32 _u32Size, orxMEMORY_TYPE _eMemType);
extern
 orxDLLAPI void
 orxFASTCALL       orxMemory_Free(void
 *_pMem);
extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Copy(void
 *_pDest, const
 void
 *_pSrc, orxU32 _u32Size);
extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Move(void
 *_pDest, void
 *_pSrc, orxU32 _u32Size);
extern
 orxDLLAPI orxU32 orxFASTCALL     orxMemory_Compare(const
 void
 *_pMem1, const
 void
 *_pMem2, orxU32 _u32Size);
extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Set(void
 *_pDest, orxU8 _u8Data, orxU32 _u32Size);
extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Zero(void
 *_pDest, orxU32 _u32Size);
extern
 orxDLLAPI void
 *orxFASTCALL      orxMemory_Reallocate(void
 *_pMem, orxU32 _u32Size);


除了allocate多了一个type,其余全部的有用接口其实都是C语言中对应接口的直接包装,都只需要一条C语句就能完成。

从内存的type上:
typedef enum __orxMEMORY_TYPE_t
{
  orxMEMORY_TYPE_MAIN = 0,              /**< Main memory type */

  orxMEMORY_TYPE_VIDEO,                 /**< Video memory type */
  orxMEMORY_TYPE_SPRITE,                /**< Sprite memory type */
  orxMEMORY_TYPE_BACKGROUND,            /**< Background memory type */
  orxMEMORY_TYPE_PALETTE,               /**< Palette memory type */

  orxMEMORY_TYPE_CONFIG,                /**< Config memory */
  orxMEMORY_TYPE_TEXT,                  /**< Text memory */

  orxMEMORY_TYPE_TEMP,                  /**< Temporary / scratch memory */

  orxMEMORY_TYPE_NUMBER,                /**< Number of memory type */

  orxMEMORY_TYPE_NONE = orxENUM_NONE    /**< Invalid memory type */

} orxMEMORY_TYPE;
可以看到作者的一些想法,但是事实上,物理上都没有区分的内存,(除了显存)无论怎么按类型分都是没有实际价值的。事实上,现在Orx也没有关心这个type。

void
 *orxFASTCALL orxMemory_Allocate(orxU32 _u32Size, orxMEMORY_TYPE _eMemType)
{
  /*
 Module initialized ?
*/

  orxASSERT((sstMemory.u32Flags & orxMEMORY_KU32_STATIC_FLAG_READY) == orxMEMORY_KU32_STATIC_FLAG_READY);

  /*
 Valid parameters ?
*/

  orxASSERT(_eMemType < orxMEMORY_TYPE_NUMBER);

  /*
 Returns system allocation function
*/

  return
((void
 *)malloc(_u32Size));
}


对C语言中标准的函数进行无意义的封装个人感觉除了影响效率,没有任何好处,这些C标准的东西本身也是跨平台的,封装它们干什么?从优化效率上考虑,稍微有些必要封装的也就是malloc和free两个而已。(realloc用的实在不多)除非Orx的作者看的更远,希望将来对包括内存Copy
,compare在内的内存操作函数都进行自己针对特定CPU/平台的汇编级优化。

bank模块

这是Orx用于优化性能实现的内存缓存的模块。
主要结构如下:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

typedef
 struct
 __orxBANK_SEGMENT_t
{
  orxU32                     *pu32CellAllocationMap; /*
*< List of bits that represents free and used elements in the segment
*/

  void
                       *pSegmentData;     /*
*< Pointer address on the head of the segment data cells
*/

  struct
 __orxBANK_SEGMENT_t *pstNext;          /*
*< Pointer on the next segment
*/

  orxU16                      u16NbFree;        /*
*< Number of free elements in the segment
*/

} orxBANK_SEGMENT;

struct
 __orxBANK_t
{
  orxBANK_SEGMENT  *pstFirstSegment;        /*
*< First segment used in the bank
*/

  orxU32            u32Flags;               /*
*< Flags set for the memory bank
*/

  orxMEMORY_TYPE    eMemType;               /*
*< Memory type that will be used by the memory allocation
*/

  orxU32            u32ElemSize;            /*
*< Size of a cell
*/

  orxU16            u16NbCellPerSegments;   /*
*< Number of cells per banks
*/

  orxU16            u16SizeSegmentBitField; /*
*< Number of u32 (4 bytes) to represent a segment
*/

  orxU32            u32Counter;             /*
*< Number of allocated cells
*/

};

typedef
 struct
 __orxBANK_STATIC_t
{
  orxU32 u32Flags;                      /*
*< Flags set by the memory module
*/

} orxBANK_STATIC;

/*
**************************************************************************

 * Module global variable                                                  *

 **************************************************************************
*/

static
 orxBANK_STATIC sstBank;

从结构上看,bank应该以__orxBANK_t
结构为主要结构,代表一个bank,每个bank中还可以包含一个orxBANK_SEGMENT
类型的列表。实际内存缓存在orxBANK_SEGMENT
类型对象的pSegmentData成员变量中。

主要对外接口有4个:

/*
* Creates a new bank in memory and returns a pointer on it

 * @param[in] _u16NbElem  Number of elements per segments

 * @param[in] _u32Size    Size of an element

 * @param[in] _u32Flags   Flags set for this bank

 * @param[in] _eMemType   Memory type where the datas will be allocated

 * @return  returns a pointer on the memory bank

 */

extern
 orxDLLAPI orxBANK *orxFASTCALL       orxBank_Create(orxU16 _u16NbElem, orxU32 _u32Size, orxU32 _u32Flags, orxMEMORY_TYPE _eMemType);

/*
* Frees a portion of memory allocated with orxMemory_Allocate

 * @param[in] _pstBank    Pointer on the memory bank allocated by orx

 */

extern
 orxDLLAPI void
 orxFASTCALL           orxBank_Delete(orxBANK *_pstBank);

/*
* Allocates a new cell from the bank

 * @param[in] _pstBank    Pointer on the memory bank to use

 * @return a new cell of memory (orxNULL if no allocation possible)

 */

extern
 orxDLLAPI void
 *orxFASTCALL          orxBank_Allocate(orxBANK *_pstBank);

/*
* Frees an allocated cell

 * @param[in] _pstBank    Bank of memory from where _pCell has been allocated

 * @param[in] _pCell      Pointer on the cell to free

 * @return a new cell of memory (orxNULL if no allocation possible)

 */

extern
 orxDLLAPI void
 orxFASTCALL           orxBank_Free(orxBANK *_pstBank, void
 *_pCell);

从流程上来看看:
先建立一个测试工程,在原来的Orx独立应用程序上改改,有下面的代码已经几乎可以做到走遍bank全部主要部分代码:

struct
 myStruct {
  int
 i;
};
orxSTATUS GameApp::Init() {
  orxBANK *bank = orxBank_Create(2
, sizeof
(myStruct), orxBANK_KU32_FLAG_NONE, orxMEMORY_TYPE_MAIN);
  myStruct *p1 = (myStruct *)orxBank_Allocate(bank);
  myStruct *p2 = (myStruct *)orxBank_Allocate(bank);
  myStruct *p3 = (myStruct *)orxBank_Allocate(bank);
  orxBank_Free(bank, p1);
  orxBank_Free(bank, p2);
  orxBank_Free(bank, p3);
  orxBank_Delete(bank);
    
    return
 orxSTATUS_SUCCESS;
}
orxBank_Create与orxBank_Delete是一对,用于创建删除一个bank对象。
然后可以通过orxBank_Allocate
orxBank_Free
来获取或者释放内存。使用还算是简单,对于完全不想自己写内存管理模块的,即使是自己的项目也可以使用这个内存管理的部分,因为这是Orx中最最底层的代码,没有依赖太多其他的东西,剥离出来也不是很难。




因为内存这一部分可以说是小技巧众多并且非常dirty的部分了,几乎都是一个一个字节相关的内容,我就是从编写内存管理模块和文件系统开始习惯看成篇的2进制内存/文件数据的。所以这里我也没有兴趣一句一句的去了解Orx的做法了,大概的了解一下。



操作流程部分:在用orxBank_Create
创建bank的时候,就会使用orxBank_SegmentCreate创建一个指定数量缓存的Segment,每个cell的大小当然就是第2个参数指定的。事实上,每一个Segment都是一个列表,保存着orxBank_Create
第一参数指定数量的cell。在默认情况下, 当一个Segment中的缓存都分配完了的时候,会自动分配新的Segment,新的Segment中的cell数量还是一样多,并且通过前一个Segment的pstNext
成员变量串起来。

内存实际分配结构:
每次分配Segment的时候实际分配的大小为:
  /* Compute the segment size */
  u32SegmentSize = sizeof(orxBANK_SEGMENT) +                                /* Size of the structure */
                   _pstBank->u16SizeSegmentBitField * sizeof(orxU32) +      /* Size of bitfields */
                   _pstBank->u16NbCellPerSegments * _pstBank->u32ElemSize;  /* Size of stored Data */

总体的使用:

  /*
 Allocate a new segent of memory
*/

  pstSegment = (orxBANK_SEGMENT *)orxMemory_Allocate(u32SegmentSize, _pstBank->eMemType);
  if
(pstSegment != orxNULL)
  {
    /*
 Set initial segment values
*/

    orxMemory_Zero(pstSegment, u32SegmentSize);
    pstSegment->pstNext               = orxNULL;
    pstSegment->u16NbFree             = _pstBank->u16NbCellPerSegments;
    pstSegment->pu32CellAllocationMap = (orxU32 *)(((orxU8 *)pstSegment) + sizeof
(orxBANK_SEGMENT));
    pstSegment->pSegmentData          = (void
 *)(((orxU8 *)pstSegment->pu32CellAllocationMap) + (_pstBank->u16SizeSegmentBitField * sizeof
(orxU32)));
  }


相当于将Segment自己的内存与其需要对外分配的内存一起分配,每次分配内存前面sizeof(orxBANK_SEGMENT)字节数的内存实际就做一个Segment结构对象使用,后面的_pstBank->u16SizeSegmentBitField * sizeof(orxU32)字节数内存作为一个标志数组使用,用于标志对应的某段内存是否已经分配,再后面的,才是实际会返回给用户使用的内存。

小结

Orx的内存管理模块对于习惯使用缓存并且非常需要使用缓存的地方还算是比较合适的,并且也还算容易使用。类似的技术也已经非常普遍了。
但是,个人感觉,类似的方式仅适用于的确已经常年习惯使用这种方式的人了,将这样添加缓存的功能完全的压到每个程序员头上,我感觉并不是很合适,最好的办法应该是在最底层透明的实现相关的功能,而不是这样将性能方面的要求强加给每个人。其次,对于已有项目,使用类似的技术需要对每个使用缓存的部分新加特别的代码,并不是很方便。还有,将缓存的代码遍布于项目中的每个角落,实在也不便于修改。在Orx中,memory,bank模块几乎被任何一个其他模块所依赖,很难想象需要改动的时候怎么办。

其实做的更加人性,更加好一点的办法也不难,说的简单点,在C语言下就是直接替换malloc,free的实现,在C++中就是重载new,delete操作符,直接通过内存的size来进行用户级别的缓存,而完全不考虑每次分配内存的用途(比如Orx中的内存type),实际的内存分配模块也的确不应该考虑和关心用户到底最后用此内存来做什么,给用户需要长度的内存就好了,那样做是加大了两方面的耦合。
基于size的缓存可以做的很简单,也可以很复杂,一般来说,对很小size的内存单独成链,为了限制链表的数量,可以取2次幂的方式。比如保存4,8,16,32,64等size的链表,每次分配这些大小的内存直接从链表中取,假如分配的大小在两个大小之间,则直接取一个更大的内存返回即可。比如用户分配9字节的内存,返回16给它,虽然造成了一定的内存浪费,但是考虑到效率和实现的问题,这并不是不可以接受。另外,对于C++来说,还可以重载标准库容器的分配器,上述方法和重载容器的分配器的方法可以参考一下SGI的STL实现。(在侯捷《STL源码剖析》中有详细描述)
其实,内存管理在C/C++中不仅仅出于效率方面的考虑,在大型项目中对于内存泄露的检测(特别是隐式的)这也是很重要的。
最后,其实,就我看到的资料都说明,现在C语言实现的自动垃圾回收效率已经非常高了,甚至已经比一般人写的手工内存管理效率还要高,如此说来,一个自动或半自动的垃圾回收机制也是个不错的选择。
总之,虽然我以前写服务器代码(常常需要以空间换时间)时写过很多类似Orx这种手工管理缓存的代码,但是,我感觉还是有很多更好的替代方案的。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(2) 基础模块与模块管理模块

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 上一节中对Orx的整体框架及结构进行了梳理,本节开始阅读Orx底层的各个模块。本节的主要内容是基础模块和用于管理模块的部分代码。

基础部分

orxDecl.h,orxType.h/orxType.c:

对于每个跨平台的库总会有些杂事需要处理,比如统一每个长度的变量命名,为各平台的换行符,路径分隔符进行统一命名,大小头等。这两个文件就是做类似的事情。列举一些特别典型的:

变量:

/*
 Windows
*/

#ifdef __orxWINDOWS__

  typedef
 void
 *                  orxHANDLE;

  typedef
 unsigned
  long
          orxU32;

  typedef
 unsigned
  short
         orxU16;

  typedef
 unsigned
  char
          orxU8;

  typedef
 signed
    long
          orxS32;

  typedef
 signed
    short
         orxS16;

  typedef
 signed
    char
          orxS8;

  typedef
 unsigned
  long
          orxBOOL;

  typedef
 float
                   orxFLOAT;

  typedef
 double
                  orxDOUBLE;

  typedef
 char
                    orxCHAR;
  #define orxSTRING               orxCHAR *

  typedef
 orxU32                  orxENUM;

  #define orx2F(V)                ((orxFLOAT)(V))

  #define orxENUM_NONE            
0xFFFFFFFFL

  /*
 Compiler specific
*/

  #ifdef __orxGCC__

    typedef
 unsigned
  long
 long
   orxU64;

    typedef
 signed
    long
 long
   orxS64;

  #endif
 /*
 __orxGCC__
*/

  #ifdef __orxMSVC__

    typedef
 unsigned
  __int64     orxU64;

    typedef
 signed
    __int64     orxS64;

  #endif
 /*
 __orxMSVC__
*/

#else
 /*
 __orxWINDOWS__
*/

  /*
 Linux / Mac
*/

  #if defined(__orxLINUX__) || defined(__orxMAC__) || defined(__orxGP2X__) || defined(__orxWII__) || defined(__orxIPHONE__)

    typedef
 void
 *                orxHANDLE;

    typedef
 unsigned
  long
 long
   orxU64;

    typedef
 unsigned
  long
        orxU32;

    typedef
 unsigned
  short
       orxU16;

    typedef
 unsigned
  char
        orxU8;

    typedef
 signed
    long
 long
   orxS64;

    typedef
 signed
    long
        orxS32;

    typedef
 signed
    short
       orxS16;

    typedef
 signed
    char
        orxS8;

    typedef
 unsigned
  long
        orxBOOL;

    typedef
 float
                 orxFLOAT;

    typedef
 double
                orxDOUBLE;

    typedef
 char
                  orxCHAR;
    #define orxSTRING             orxCHAR *

    typedef
 orxU32                orxENUM;

    #define orx2F(V)              ((orxFLOAT)(V))

    #define orxENUM_NONE          
0xFFFFFFFFL

  #endif
 /*
 __orxLINUX__ || __orxMAC__ || __orxGP2X__ || __orxWII__ || __orxIPHONE__
*/

#endif
 /*
 __orxWINDOWS__
*/


换行符:
#define orxCHAR_CR                
'/r'

#define orxCHAR_LF                
'/n'




#ifdef __orxWINDOWS__

  const
 orxCHAR    orxCHAR_EOL         = '/n'
;

  const
 orxSTRING  orxSTRING_EOL       = "
/r/n
"
;

#elif defined(__orxLINUX__) || defined(__orxGP2X__) || defined(__orxWII__) || defined(__orxIPHONE__)

  const
 orxCHAR    orxCHAR_EOL         = '/n'
;

  const
 orxSTRING  orxSTRING_EOL       = "
/n
"
;

#elif defined(__orxMAC__)

  const
 orxCHAR    orxCHAR_EOL         = '/r'
;

  const
 orxSTRING  orxSTRING_EOL       = "
/r
"
;

#endif


目录分割符:

/*
 *** Directory separators ***
*/

const
 orxCHAR     orxCHAR_DIRECTORY_SEPARATOR_WINDOWS    = '//'
;
const
 orxCHAR     orxCHAR_DIRECTORY_SEPARATOR_LINUX      = '/'
;
const
 orxSTRING   orxSTRING_DIRECTORY_SEPARATOR_WINDOWS  = "
//
"
;
const
 orxSTRING   orxSTRING_DIRECTORY_SEPARATOR_LINUX    = "/"
;

#ifdef __orxWINDOWS__

  const
 orxCHAR      orxCHAR_DIRECTORY_SEPARATOR         = '//'
;

  const
 orxSTRING    orxSTRING_DIRECTORY_SEPARATOR       = "
//
"
;

#else
 /*
 __orxWINDOWS__
*/

  /*
 Linux / Mac / GP2X / Wii
*/

  #if defined(__orxLINUX__) || defined(__orxMAC__) || defined(__orxGP2X__) || defined(__orxWII__) || defined(__orxIPHONE__)

    const
 orxCHAR    orxCHAR_DIRECTORY_SEPARATOR         = '/'
;

    const
 orxSTRING  orxSTRING_DIRECTORY_SEPARATOR       = "/"
;

  #endif
 /*
 __orxLINUX__ || __orxMAC__ || __orxGP2X__ || __orxWII__ || __orxIPHONE__
*/

#endif
 /*
 __orxWINDOWS__
*/


做跨平台应用的总会感叹有个标准多好,因为不同厂商在自己系统上定义的东西总是那么的千差万别,甚至感觉故意为了不同而不同,上面这些都不多说了。想起OpenGL和D3D中右手左手坐标系,纵优先行优先矩阵等问题就脑袋疼。

orxModule.h/orxModule.c:

这两个文件算是Orx的模块的基础,因为他们就是管理各个模块的。

记得有个编程中的话是这么说的,“你给我看流程图后,我什么都还不清楚,你给我看你的表结构,我就知道你的流程图会是怎么样了。”

所以这里先看Orx模块管理中的基础结构,看了以后的确不用看具体函数实现了。

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

/*
* Internal module info structure

 */

typedef
 struct
 __orxMODULE_INFO_t

{

  orxU64                    u64DependFlags;                 /*
*< Dependency flags : 8
*/

  orxU64                    u64OptionalDependFlags;         /*
*< Optional dependency flags : 16
*/

  orxMODULE_SETUP_FUNCTION  pfnSetup;                       /*
*< Setup function : 20
*/

  orxMODULE_INIT_FUNCTION   pfnInit;                        /*
*< Init function : 24
*/

  orxMODULE_EXIT_FUNCTION   pfnExit;                        /*
*< Exit function : 28
*/

  orxU32                    u32StatusFlags;                 /*
*< Status flags : 32
*/

} orxMODULE_INFO;

/*
* Static structure

 */

typedef
 struct
 __orxMODULE_STATIC_t

{

  orxMODULE_INFO astModuleInfo[orxMODULE_ID_NUMBER];

  orxU32 u32InitLoopCounter;

  orxU32 u32Flags;

} orxMODULE_STATIC;

/*
**************************************************************************

 * Static variables                                                        *

 **************************************************************************
*/

/*
* static data

 */

static
 orxMODULE_STATIC sstModule;

所有的相关函数都是操作sstModule这个全局的变量。此变量的主要结构是orxMODULE_INFO
类型的数组,每个orxMODULE_INFO
类型的变量都包含自己的依赖标志,可选依赖标志和状态标志,以及3个函数指针变量。3个函数指针变量分别是Init,Setup,Exit。

结构定义完以后,该怎么操作就和能清晰了。首先为每个模块指定确定的3大函数,指定各模块自己依赖的模块,每次操作后修改状态标志。

 

各函数实现概述:

orxModule_Register:指定"三大函数":

  /* Stores module functions */

  sstModule.astModuleInfo[_eModuleID].pfnSetup  = _pfnSetup;

  sstModule.astModuleInfo[_eModuleID].pfnInit   = _pfnInit;

  sstModule.astModuleInfo[_eModuleID].pfnExit   = _pfnExit;

Setup函数设置模块依赖的模块,Init用于初始化,Exit用于退出。

orxModule_RegisterAll:依次用orxModule_Register注册所有的模块:

并且,orx作者为了方便,使用了一个宏:

#define orxMODULE_REGISTER(MODULE_ID, MODULE_BASENAME)  orxModule_Register(MODULE_ID, MODULE_BASENAME##_Setup, MODULE_BASENAME##_Init, MODULE_BASENAME##_Exit)

宏的意义很明显,即用MODULE_BASENAME来按对应规则生成对应的Setup,Init,Exit函数。

比如说,Orx的内存管理模块的ID是orxMODULE_ID_BANK,

定义的三个函数是:

/*
* Setups the bank module

 */

extern
 orxDLLAPI void
 orxFASTCALL           orxBank_Setup();

/*
* Inits the bank Module

 * @return orxSTATUS_SUCCESS / orxSTATUS_FAILURE

 */

extern
 orxDLLAPI orxSTATUS orxFASTCALL      orxBank_Init();

/*
* Exits from the bank module

 */

extern
 orxDLLAPI void
 orxFASTCALL           orxBank_Exit();

注册的时候,使用宏就简单的用

orxMODULE_REGISTER(orxMODULE_ID_BANK, orxBank);

效果相当于

orxModule_Register(orxMODULE_ID_BANK
, orxBank_Setup
, orxBank_Init()
, orxBank_Exit()
)

 

其实也没有什么太多好说的,就是个宏扩展的使用而已。

 

orxModule_AddDependency:

设定标志位而已。

sstModule.astModuleInfo[_eModuleID].u64DependFlags |= ((orxU64)1) << _eDependID;

可以看到,对于依赖的标志位是以一个无符号64位的整数来指定的,每一位表示对一个模块的支持,所以Orx目前最多支持64个模块。

 

Setup,SetupAll,InitAll,Exit,ExitAll的函数感觉没有什么太多好说的了,就是调用对应模块/所有模块Register好的3大函数指针指向的函数。

Init,单独初始化某个模块的部分可以稍微分析一下:

首先,初始化时,会先通过

if(!(sstModule.astModuleInfo[_eModuleID].u32StatusFlags & (orxMODULE_KU32_STATUS_FLAG_INITIALIZED|orxMODULE_KU32_STATUS_FLAG_TEMP)))

来判断当前模块是否已经初始化,已经初始化的话就直接返回了。

在确认当前模块需要进行初始化时,需要先初始化此模块的依赖的模块,在初始化所有依赖的模块后,为正在初始化模块的状态位或上orxMODULE_KU32_STATUS_FLAG_TEMP标志

表示开始初始化,然后调用Register过此模块Init的函数指针指向的函数进行实质的初始化,初始化过后,再加置orxMODULE_KU32_STATUS_FLAG_INITIALIZED标志。

 

 

同时在初始化模块开始时

  /* Increases loop counter */

  sstModule.u32InitLoopCounter++;

结束时

  /* Decreases loop counter */

  sstModule.u32InitLoopCounter--;

然后通过

  /* Was external call? */

  if(sstModule.u32InitLoopCounter == 0)

来判断什么时候一整轮初始化的结束,结束时调用

      /* For all modules */

      for(u32Index = 0; u32Index < orxMODULE_ID_NUMBER; u32Index++)

      {

        /* Cleans temp status */

        sstModule.astModuleInfo[u32Index].u32StatusFlags &= ~orxMODULE_KU32_STATUS_FLAG_TEMP;

      }

取消所有本轮初始化的临时状态。

 

光是讲流程的话可能有些混乱,形象的描述一下初始化单独一个模块的过程。

因为模块之间的依赖问题,初始化一个模块就相当于需要初始化此模块依赖的所有模块,也需要初始化依赖模块依赖的模块,最后形成的是一个树状结构。只有当依赖关系到达树的叶节点(也就表示此模块是最基础的模块了,没有其他依赖)时才进行叶节点实际的初始化。同时,没初始化一个都置标志位,防止同一个模块初始化两次。当sstModule.u32InitLoopCounter为0时,表示此轮初始化已经又回到了最开始初始化的那个模块(根节点),此轮初始化结束。

从这个描述中也说明了一个问题,那就是Orx不允许模块的循环依赖,否则会初始化失败。但是多个模块依赖一个模块和一个模块依赖多个模块都是允许的。

同时,稍微看一下Orx的模块之间的依赖关系,可以发现相互的依赖非常复杂,我本来想做一个依赖图的,发现难度太大。。。。。。。。。Orx的模块依赖关
系虽然总体上是个树形结构,但是该树没有太良好的层级结构,大量跨层的依赖,导致显得非常混乱。

小结

首先,这么一大堆模块管理代码存在的必要性。从作用来看,假如非要讲优点的话,我感觉模块管理的作用有下面几个:

1。正常初始化,释放各模块,对于C语言来讲,没有构造函数,没有析构函数,对于一个模块只能使用一个结构来完成,初始化和退出都需要手动来做,使用Orx这样的模块管理可以在模块级别实现稍微自动化一些的初始化和退出。

2。集中管理各模块之间的依赖,使得初始化更加自动化,不易出错。

3。依赖于第2点,对于完全不需要的模块,可以完全不初始化。

 

但是,这些作用(其实我也不知道真实目的)都没有太好的完成。

首先,初始化的依赖问题虽然比较麻烦,但是可以在一个集中的初始化函数中一次完成,没有必要弄这么多代码出来。另外,虽然现在Orx的模块管理代码可以使得不需要的模块不初始化,但是因为每个模块都是用一个相关结构的全局变量来实现的,所以仅仅只能省一些初始化时间而已,内存上并没有节省,另外,对于不想初始化的代码,直接去掉初始化函数的调用就可以了,费不着用这么多代码来实现自动化。即使说初始化什么模块可能是通过配置来决定的,所以自动化有必要,但是仅仅剩下一些初始化时间的必要性并不大,还不如全部都初始化了。

 

但是,我还是很欣赏这种将模块管理起来的代码,虽然有些冗余,但是将模块统一管理在一个数组之中,比在某些巨大的Init,Exit函数去一一调用各模块的相应函数要来的漂亮。新添加模块的时候,只需要添加对应的3大函数就能自动化的完成一些初始化及收尾工作,也比每添加一个就需要在巨大的Init,Exit函数中添加一条来的方便。

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....