ioquake代码分析一
- 目录结构
- 主流程框架
- main函数流程
- Com_Init 初始化过程
- OpenGL初始化流程
- InitOpenGL
- 其他信息
- NET_Init
- NET_Config 流程
wiki :http://wiki.ioquake3.org
目录结构
- misc:各个工程和配置文件
- msvc/msvc10
- nsis
- osxfe/setup
- ico图标和照片文件
- code:代码文件
- AL:OpenAL的头文件
- asm:汇编代码
- botlib
- cgame
- client
- game
- 相关lib:jpeg-8c、libcurl、zlib、libspeex音频编解码库
- null:内容和sdl目录下基本对应
- q3_ui
- qcommon
- renderer
- sdl:对sdl的部分包装,glimp、gamma、icon、input、snd
- SDL12: SDL的头文件
- server
- sys:主程序的入口出
- tools
- asm:q3asm tool,quake3的asm工具
- lcc:Local C Compiler 一款开源的ANSIC编译器
- ui
- ui:txt文件存放得是要加载的菜单名
- Makefile
- cross-make-*
- pak0.pk3
- README
- TODO
主要的代码在code下的sdl、qcommon、server、client、sys、renderer、game、cgame、botlib中
主流程框架
windwos中的主入口是在WinMain函数中,Winmain函数还是调用的main函数。linux的main函数在:code/sys/sys_main.c,后面的代码分析只看linux平台的。
main函数流程
- SDL最小版本判断
- Sys_PlatformInit,平台初始化,根据平台两个函数实现不太一样
Windows: code/sys/sys_win32.c
Unix: code/sys/sys_unix.c
const char* term = getenv( "TERM" ); //获取环境变量, getenv是系统函数signal( SIGHUP, Sys_SigHandler ); //创建信号,signal是系统函数,其他几个信号,也是关联到了Sys_SigHandler上,SIGQUIT、SIGTRAP、SIGIOT、SIGBUSstdinIsATTY = isatty( STDIN_FILENO ) &&!( term && ( !strcmp( term, "raw" ) || !strcmp( term, "dumb" ) ) );
isatty:检查设备类型 , 判断文件描述词是否是为终端机。stdinIsATTY 是true
STDIN_FILENO:三个流(标准输入、标准输出和标准出错)中的一个。UNIX系统shell使用文件描述符0与进程的标 准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错相关联。
Sys_SigHandler:信号处理,推测出程序,CL_Shutdown和SV_Shutdown和Sys_Exit
- Sys_Milliseconds: 获取当前时间,用gettimeofday,然后做一次时间转换
- Sys_ParseArgs: main中输入的参数解释,只解释了–version和-v
- 获取应用程序路经存放在binaryPath,获取安装路径放在installPath,unix下是一样的。
- 命令行参数截取存放在commandLine中
- Com_Init
- NET_Init :qcommon目录下,网络初始化
- CON_Init
- 中断信号关联:SIGILL、SIGFPE、SIGSEGV、SIGTERM、SIGINT。
详细意义可以参考信号列表,都是中断信号:非法指令、浮点例外、段非法错误、终止、来自键盘的中断信号 - 后面开启while 1循环 IN_Frame和Com_Frame函数
Com_Init 初始化过程
- 清空event队列eventQueue
- 初始化随机数种子
qboolean Sys_RandomBytes( byte *string, int len )
{FILE *fp;fp = fopen( "/dev/urandom", "r" );if( !fp )return qfalse;if( !fread( string, sizeof( byte ), len, fp ) ){fclose( fp );return qfalse;}fclose( fp );return qtrue;
}
static void Com_InitRand(void)
{unsigned int seed;if(Sys_RandomBytes((byte *) &seed, sizeof(seed)))srand(seed);elsesrand(time(NULL));
}
- 初始化 com_pushedEvents :清空队列,头尾设置为0
类型:
typedef enum {// SE_NONE must be zeroSE_NONE = 0, // evTime is still validSE_KEY, // evValue is a key code, evValue2 is the down flagSE_CHAR, // evValue is an ascii charSE_MOUSE, // evValue and evValue2 are reletive signed x / y movesSE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127)SE_CONSOLE // evPtr is a char*
} sysEventType_t;typedef struct {int evTime;sysEventType_t evType;int evValue, evValue2;int evPtrLength; // bytes of data pointed to by evPtr, for journalingvoid *evPtr; // this must be manually freed if not NULL
} sysEvent_t;
- 创建和初始化内存池ZoneMemory:
s_smallZoneTotal = 512 * 1024;smallzone = calloc( s_smallZoneTotal, 1 );Z_ClearZone( smallzone, s_smallZoneTotal );// 初始化memory zone,这个zone中存放的是debug信息void Z_ClearZone( memzone_t *zone, int size ) {memblock_t *block;zone->blocklist.next = zone->blocklist.prev = block =(memblock_t *)( (byte *)zone + sizeof(memzone_t) );zone->blocklist.tag = 1; // in use blockzone->blocklist.id = 0;zone->blocklist.size = 0;zone->rover = block;zone->size = size;zone->used = 0;block->prev = block->next = &zone->blocklist;block->tag = 0; // free blockblock->id = ZONEID;block->size = size - sizeof(memzone_t);
结构组成:zone中包含一堆block,block用blocklist管理,其他的rover、zise、used是blocklist的属性。
初始化将blocklist初始化成一个空的双向循环链表,里面只放一个block,作为和blocklist的彼此头尾,block也包含自己的属性
- 初始化环境变量参数
调用Cvar_Get函数 获取sv_cheats,所有的环境变量用hash table进行管理的搜索
用Cmd_AddCommand将对应的命令挂载到制定的函数上,每一个命令结构体是一个循环的链表
所有的cmd存在static变量cmd_functions中,是个单向链表,每次新来的存到头部,cmd_functions永远指向的是第一个。 - Com_ParseCommandLine:只是将前面的commandLine存放到数组com_consoleLines中
- 初始化 cmd_text : cmd_text.data = cmd_text_buf;
- Com_DetectSSE:判断CPU是否支持SSE,调用的是SDL_XXX 函数,这些函数最终调用的是CPU_XXX函数,这些函数中调用的是汇编语言
if( SDL_HasRDTSC( ) ) features |= CF_RDTSC;if( SDL_HasMMX( ) ) features |= CF_MMX;if( SDL_HasMMXExt( ) ) features |= CF_MMX_EXT;if( SDL_Has3DNow( ) ) features |= CF_3DNOW;if( SDL_Has3DNowExt( ) ) features |= CF_3DNOW_EXT;if( SDL_HasSSE( ) ) features |= CF_SSE;if( SDL_HasSSE2( ) ) features |= CF_SSE2;if( SDL_HasAltiVec( ) ) features |= CF_ALTIVEC;
支持了SSE之后实现这三个函数
Q_ftol = qftolx87;
Q_VMftol = qvmftolx87;
Q_SnapVector = qsnapvectorx87;
后面的函数都是用汇编指令实现的
- 把命令你行的参数全部写进 cmd list 中,cmd list是一对的,前面是命令,后面是命令的参数
初始化的后面操作也是在设置和写cmd list,这些list存放的是游戏的相关设置, - 根据命令行的com_zoneMegs,创建内存池 mainzone,创建方法和上面的一样
- Com_ExecuteCfg函数中执行所有的配置cmd函数,后面Cvar_Get一系列的参数
- 渲染开始在函数注册CL_StartHunkUsers中CL_InitRenderer,初始化UI界面CL_InitUI
在这个函数里创建窗口
动画播放在函数CL_InitUI中执行
re.BeginRegistration = RE_BeginRegistration;
OpenGL初始化流程
RE_BeginRegistration
R_Init(); //初始化生成相关的数据,fog,noise,注册render相关参数;初始化场景的顺序;
R_Register函数中所有的参数进行初始化,包括全屏参数r_fullscreen = ri.Cvar_Get( “r_fullscreen”, “1”, CVAR_ARCHIVE );
// InitOpenGL,其中GLimp_Init来实现OpenGL相关的初始化
*glconfigOut = glConfig;R_SyncRenderThread();tr.viewCluster = -1; // force markleafs to regenerate
R_ClearFlares();
RE_ClearScene();tr.registered = qtrue;
InitOpenGL
- GLimp_Init:
GLimp_StartDriverAndSetMode创建窗口,这里调用GLimp_SetMode,设置相关属性,SDL_CreateRGBSurfaceFrom创建窗口,获取opengl context GLimp_GetCurrentContext
gdb调试过程中全屏窗口的显示是再函数SDL_SetVideoMode时
IN_ActivateMouse到这里等待鼠标卡住
其他信息
- wiki[http://wiki.ioquake3.org/Main_Page]页面中包含了详细的编译和开发介绍
- log输出中用宏定义写固定的信息:
fprintf( stdout, Q3_VERSION " dedicated server (%s)\n", date );
#define Q3_VERSION PRODUCT_NAME " " PRODUCT_VERSION
#define PRODUCT_VERSION "1.36"
#define PRODUCT_NAME "ioq3"
- exit进行了包装:Sys_Exit
NET_Init
网络是否启用的控制参数:net_enabled->integer
NET_Config( qtrue );Cmd_AddCommand ("net_restart", NET_Restart_f);
NET_Config 流程
- 判断网络参数是否修改
- enableNetworking 和 networkingEnabled分别表示网络是否开始、网络是否已经开启。如果两个状态相同,并且网络参数没有修改,就可以保持现状退出改配置函数。
- 否则根据enableNetworking 和 networkingEnabled的值配置stop和start的状态,状态组合情况如下:
enableNetworking/networkingEnabled | T/T | F/F | T/F | F/T |
---|---|---|---|---|
stop | T | F | T | F |
start | T | F | F | T |
- if stop:调用closesocket关闭网络
- if start && net_enabled->integer:调用NET_OpenIP和NET_SetMulticast6函数。
- NET_OpenIP:
NET_GetLocalAddress:获取ip地址,调用getifaddrs函数获取ip地址的链表;然后遍历,ip中有标志IFF_UP,表示网络装置正常启用;NET_AddLocalAddress区分IPv4和IPv6然后填充LocalIP数组;freeifaddrs释放链表;Sys_ShowIP调用getnameinfo将地址转换成字符串显示。
根据net_enabled->integer & NET_ENABLEV6或NET_ENABLEV分别进行获取socket端口,进行遍历,调用socket、ioctlsocket、setsocketopt、bind函数 - NET_SetMulticast6:
只是做一个地址拷贝
- NET_OpenIP: