首页 > 代码库 > 第五章 游戏支持系统
第五章 游戏支持系统
5.1 子系统的启动和终止
游戏中各个子系统间有相互依赖关系,有确定的启动和终止次序,为游戏引擎中主要子系统定义单例类(singleton class)(通常称为管理器)。我们明确为各个单例管理器类定义启动和终止函数,以此取代建构和析构函数。这种方法还有别的实现方式,例如,用一个全局的优先队列来记录各个管理器。
5.2 内存管理
软件的效能同时受算法和内存运用的影响。其中内存的影响有两方面:动态内存分配和内存访问模式。
1 优化动态内存分配
C++的全局 new/delete 或 malloc()/ free() 动态分配内存,又称为堆分配速度很慢。低效率主要是由于管理开销和管态目态之间的上下文切换。游戏开发中的一个经验法则:维持最低限度的堆分配,绝不在紧凑的循环中使用堆分配。游戏引擎无法避免使用动态内存分配,所以多数游戏引擎会实现一个或多个定制分配器。定制分配器从预分配的内存中完成分配请求,对其使用模式做多个假设,使它更高效。
堆栈分配器(stack allocator):使用malloc()、全局new或者声明一个全局字节数组,安排一个指针指向堆栈的顶端,指针以下内存是已分配的,以上则是未分配的。对于每个分配请求,只需把指针往上移动请求所需的字节数;释放最后分配的内存块,也只需要把指针向下移动相应字节数。还有一种实现是双端堆栈分配器。
池分配器(pool allocator):用来分配大量同等尺寸的小块内存,例如矩阵、迭代器、链表中的节点、可渲染的网格实例等。池分配器会预先分配一大块内存,大小刚好是分配元素的倍数,池内每个元素会加到一个存放自由元素的链表。池分配器收到分配请求时,把自由链表的下一个元素取出,并传回该元素;释放元素时,只需把元素插回自由链表中,分配和释放操作时间复杂度都是O(1)。
含对齐功能的分配器:分配有字节对齐的需求时,只要在分配内存时,分配多一点内存,再向上调整至内存地址对齐,最后传回调整后的的地址。(字节对齐是为了CPU高效的读取操作)。
计算偏移量的方法如下:首先用掩码(mask)把原内存块地址的最低有效位取出,再把期望的对齐减去此值,得到的就是调整偏移量。掩码等于对齐字节数减1。例,若请求16字节对齐的内存块,掩码就是15。假如原来分配的内存块地址是0x00001233,将其与0x0000000F进行&运算,得到3,(16-3)=13,13就是偏移量,将原地址加上13得到最终对齐的内存地址0x00001240。
释放内存时,为了得到调整前的地址来释放原本分配的内存,我们在额外分配的内存存储一些元信息,在调整后地址前一字节存储偏移量,最后在释放内存时,通过简单计算便可得到调整前分配的地址。
单帧和双缓冲内存分配器:用来给游戏循环中的临时数据分配内存。
单帧分配器:预留一块内存,以堆栈分配器管理。在每帧开始时,把顶端指针重置到内存块的底端地址。
双缓冲分配器:允许在第 i 帧分配的内存块用于第 i+1 帧。实现方法就是建立两个相同尺寸的单帧堆栈分配器,在每帧交替使用。能保留上一次循环的结果。
第五章 游戏支持系统