cGameHost包含的一个关键管理对象就是资源管理器(resource manager).我们的程序使用他自己的管理系统来管理我们将要创建的设备。这个资源管理系统的根,cResourcePoolManager类,是建立在一个我们引擎中提供的支持类的集合的基础上的。在我们讨论资源对象本身,我们花一点时间来了解这个我们将要使用的资源池和将会使用的资源管理计划。
创建数据池
一个高效内存管理的关键就是限制分配的总数。完成这个最简单的方法就是将相似的物体组合起来成为一个大的内存分配。我们把这个叫做内存池(date pools),使用他们作为复合物体的容器。其中包含的物体被认为是池的成员,并且通过使用句柄被提供给请求者。成员可以被客户请求和释放,同时以固定的增量来扩张数据池以满足更多成员空间的需要。
这是一个代码会变得复杂的地方。我们想要这些池是类型安全的,所以他们被建成模板类(template calsses)——实用一个特殊的数据类型来代表池的成员。然后,依然需要使用一个普通的接口所有这些池间的通信。当我们随后建立维互纹理,顶点,动画的数据池的资源管理器的时候,在不同的池间通过一个普通的接口实现通信对于创建和销毁这些资源就非常重要了。因此,即使数据池他们自己是独立的模板类的实例,他们也必须继承自一个可以用来创建和释放成员的外部管理类的普通接口。
虽然有充足的理由来简单的分配一个大的数组并且通过他们的数组索引引用他们,但是这个方法非常的不灵活。数组的大小必须事先知道,并且数组不能超过预先分配的大小,因此限制了能够在游戏中使用的对象的数量。固定数组提供了优化的内存使用,同时却牺牲了能够容纳的对象的数量。当我们明确的知道我们游戏中要使用的物体的数量并且确实可以被预先分配内存的时候,这是一个好主意,但对于我们开发循环,我们需要更多一些的灵活性。
程序中管理数据池的解决方案,cDatePool模板类,不是真的包含一个成员对象的大小。事实上,它包含了一个成员对象的链表(linked list),叫做cPoolGroups。每个组包含一个固定数量的成员对象,能够根据需要从数据池中增加和删除。这是一个在高效内存使用和固定对象数目的折中方案。当数据池根据需要增长,它只能按照预订的块(chunk)增长,每次分配一块新的成员对象。
这个设计最有价值的地方就是他可以被转化成常规数组而不会影响游戏的其他方面。在数据池创建对象树的同时嵌套的为每个成员创建了一个独立的缩影。如果数据池维护的对象数退化为常规数组,这个索引值依然不会变。示意图4.1并列的展示了这两个数据结构,并且比较的展示了他们索引方法的相似性。数据池的这个属性使用数据池的灵活性来工作,尽管是少一些效率的方式,我们随后可以用一个高效的,常规的数组一次分配所有的需要的内存来代替。对游戏来说,数据池中的对象作为句柄使用的索引值不会改变。
使用cDatePool类的树状存储方式或者是常规数组方式意味着一块内存块将被重复使用。当数据池中的成员被释放,这些存储空间变为可用的以被接下来的请求使用。因此,我们需要追踪哪些成员是可用的,这样当请求空间的时候可以被快速的在池中找到可用的缝隙。在cPoolGroup中,我们维护一个开放成员的列表。它只是一个对应于组中成员数目的简单的WORD值数组。数组中的每个值表示它对应的池成员是否可用。
每个正在被使用的成员在数组中使用一个常量(INVALID_INDEX)表示它是不可用的。那些可用的成员形成一个索引值的链表。每个成员包含表中下一个可用成员的索引。我们的cDatePool类存储第一个可用实例的索引值,最后一个可用实例的包含它自己的索引值来表示这条链的结束。我们可以通过简单的返回第一个可用的索引并且将我们的内部句柄移动到下一个可用索引(如果有的话)来返回一个可用的成员给调用者。当调用者把成员释放回数据池,我们简单的将我们的句柄直接指向返回的成员,这个句柄先前是指向先前可用成员链表的头部。
查看源代码将更容易理解这个过程。完整的数据池对象的源代码可以在配套CD的source_code/core/date_pool.h中找到。这些对象的函数要全部在书中列出实在是很棘手的事情,但是负责从数据池中添加和移除成员的函数可以在清单4.2中找到。这些例子展示了定位可用cPoolGroup,从中释放一个可用索引,创建一个包含索引值的cPoolHandle对象给调用者的基本操作。释放扯远,成员将在池中背定位并且放在可用成员链表的头节点中。