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腳本
十五:如果用戶沒有輸入+命令,而且沒有要顯示“獻給”標志,則顯示演示程序
至此,第一階段分析告一段落。