quake2源码分析(一)
我希望通过quake2的源码分析来理解这个优秀引擎的动作方式。 由于我比较熟悉delphi的代码组织方式,故从http://www.sourceforge.net/quake2delphi/下载了quake2的delphi代码来进行分析。 说明:为了描述的简单化,我没有对细节方面进行描述。
下面让我们进入正题: 起始点: sys_win: WinMain 这是我们非常熟悉的windows入点
Qcommon_Init //qcommon是通用模块,进行通用模块的初始化,这个步骤处理的东西相当多,下一步会详细讲解 while(true) Qcommon_Frame(time) //绘制每一帧
简单地说WinMain要处理的主要就是这两步操作
下面让我们分析一下初始化操作: Qcommon_Init的内容
//一 初始化z_chain z_chain.prev := @z_chain; z_chain.next := z_chain.prev; //二 初始化参数 COM_InitArgv(argc, argv); //三 初始化交换算法及命令行缓冲区 Swap_Init; Cbuf_Init; //四 命令解释器的初始化 Cmd_Init; //五 变量操作的初始化 Cvar_Init; //六 键盘映射表初始化 Key_Init;
//七 初始化命令行,然后初始化文件系统 Cbuf_AddEarlyCommands(False); Cbuf_Execute;
FS_InitFilesystem; //八 运行脚本 Cbuf_AddText('exec default.cfg'#10); Cbuf_AddText('exec config.cfg'#10);
Cbuf_AddEarlyCommands(True);//添加命令行并将参数清空 Cbuf_Execute; //九 初始化变量 Cmd_AddCommand('z_stats', Z_Stats_f); Cmd_AddCommand('error', Com_Error_f);
host_speeds := Cvar_Get('host_speeds', '0', 0); log_stats := Cvar_Get('log_stats', '0', 0); developer := Cvar_Get('developer', '0', 0); timescale := Cvar_Get('timescale', '1', 0); fixedtime := Cvar_Get('fixedtime', '0', 0); logfile_active := Cvar_Get('logfile', '0', 0); showtrace := Cvar_Get('showtrace', '0', 0); {$IFDEF DEDICATED_ONLY} dedicated := Cvar_Get('dedicated', '1', CVAR_NOSET); {$ELSE} dedicated := Cvar_Get('dedicated', '0', CVAR_NOSET); {$ENDIF} //十 设置版本 s := va('%4.2f %s %s %s', [VERSION, CPUSTRING, __DATE__, BUILDSTRING]); Cvar_Get('version', s, CVAR_SERVERINFO or CVAR_NOSET);
if (dedicated.value <> 0) then Cmd_AddCommand('quit', Com_Quit); //十一:初始化系统(控制台) Sys_Init; //十二:初始化网络 及 端口 NET_Init; Netchan_Init; //十三 初始化服务端 SV_Init; //十四 初始化客户端 CL_Init;
//十五:如果用户没有输入+命令,而且没有要显示“献给”标志,则显示演示程序 // add + commands from command line if not Cbuf_AddLateCommands then begin // if the user didn't give any commands, run default action if (dedicated.value = 0) then Cbuf_AddText('d1'#10) else Cbuf_AddText('dedicated_start'#10); Cbuf_Execute; end else begin // the user asked for something explicit // so drop the loading plaque SCR_EndLoadingPlaque; end; 十六 结束 Com_Printf('====== Quake2 Initialized ======'#10#10, []);
接下来对qcommon_init的十六步操作进一步分析
一:初始化z_chain 这就引出了对z_chain的理解,z_chain是quake独创的一种数据链表,用以管理分配的内存空间,以保证临时申请的内存能正确地释放。 每一次内存分配请求会调用common的z_malloc来分配,z_malloc调用Z_TagMalloc来分配带标志的内存空间。 我们来看一下z_chain的类型zhead_t的定义 zhead_s = record prev, next: zhead_p;//典型的链表 magic: SmallInt; //一个标志位,保留const Z_MAGIC = $1D1D; tag: SmallInt; // 用以成批释放,看来类似于gc的概念 size: Integer; //所分配的内存块的大小(包括zhead_s的大小) end; zhead_t = zhead_s; 二 初始化参数 将命令行数据存入 com_argc_ 及com_argv_[]
三 初始化交换算法及命令行缓冲区 为了适应不同的cpu结构,在程序中判断是否大印第安格式还是小印第安格式,我的p4 cpu上是小印第安格式。 给命令行缓冲区分配内存
四 命令解释器的初始化 初始化命令解释器。quake有一个很独特的模式,就是采用的是命令行配合全局变量驱动方式。这让我想起了古老的dos操作系统。 这种方式的优点是模块之间的耦合度可以很低。也很容易配置。 这一步中添加了以下几条默认命令 cmdlist:列出所有可用的命令 exec:运行脚本 echo: 显示文本 alias 显示别名 wait 等待
五 变量操作的初始化 添加了以下用于操作变量的命令 set 设置变量 cvarlist 列出变量
六 键盘映射表初始化 定义了默认的键盘映射表 添加了用于操作键盘映射表的命令 bind 将一个键绑定到一个字符上 unbind 取消一个键的绑定 unbindall 取消所有键的绑定 bindlist 显示绑定列表
七 初始化命令行,然后初始化文件系统 将命令行参数传入,设置好初始化文件系统所需的变量,然后初始化文件系统
八 运行脚本 运行default.cfg 运行config.cfg 用命令行传入的参数覆盖现有变量,也就是说命令行传入的参数有较高的优先级
九 初始化变量 添加命令: z_stats: 显示分配的内存大小及块数 error:抛出一个异常,估计用于测试异常
添加以下变量: host_speeds 主机程度 log_stats 日志状态 developer 开发? timescale 时间刻度 fixedtime 固定时间? logfile_active 日志是否活动 showtrace 是否显示跟踪信息 {$IFDEF DEDICATED_ONLY} 是否是献词版本 dedicated := Cvar_Get('dedicated', '1', CVAR_NOSET); {$ELSE} dedicated := Cvar_Get('dedicated', '0', CVAR_NOSET); {$ENDIF} 十 设置版本 设置软件版本变量 十一:初始化系统(控制台) 这步操作是为了能像dos窗口一下输入命令
十二:初始化网络 及 端口 这里初始化端口有一个小技巧,采用了毫秒的最后四位作为端口,不过我没有看到端口冲突的解决方法。大概因为这个概率很小的原因吧。
十三 初始化服务端 添加了服务端要用到的命令及变量
十四 初始化客户端 初始化控制台 初始化渲染模块vid_dll 初始化声音模块 初始化视图用的命令 初始化菜单 初始化屏幕 初始化cd声音 初始化本地操作命令 初始化 input 运行autoexec.cfg脚本
十五:如果用户没有输入+命令,而且没有要显示“献给”标志,则显示演示程序
至此,第一阶段分析告一段落。

|