`
webcenterol
  • 浏览: 914969 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

讨论新闻组及文件

有的引擎的源代码是越看越吸引人的,越看越让人惊呼设计之精巧,实现之完美的,很遗憾的,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) -- blog.csdn.net/vagrxie









分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics