一、资源管理数据结构
有效内存管理的关键之一就是如何限制内存分配的总次数,最通用的做法便是把相似的对象放一块,给他们一起分配一块大空间。在需要的时候直接从已分配好的空间中找出一小块直接拿来用,而且空间内存放的都是相同类型的资源,所以某个资源释放后产生的片段刚好可以用于存放另一个同样的资源。这样的一块大空间在GAIA中就被称之为DataPool(数据池)。数据池作为一个存放相同资源的容器,支持动态的添加和释放资源。为了保证数据池能够用于所有类型的资源,所以在实现的时候用了模板。同时,想要做的数据池的通用,统一的接口也是很必要的,以后无论什么资源都以相同的资源进行构造和释放等操作。此时我们就至少该有两个类了,一个叫DataPool,另一个可以是DataPoolInterface接口类。而且DataPool需要继承DataPoolInterface以获取相同的对外接口。
DataPool是如何管理这些资源呢,它的数据结构是什么?
资源何其多啊,如果将资源放在数组中如何呢?放在数组中查询是相当快,但是灵活性变差了,而且事先需要预估数组的长度,在游戏中资源的总数就被限制了。那如果放在链表中呢?这样可以支持动态的增长,但是查找比数组慢多了。GAIA在这里选择了树来存储。用树就意味需要分层次,于是就产生了组的概念。这种组在代码中描述为cPoolGroup(池组),池组中包含了固定数量的资源对象,每个对象存放在某个可用的插槽中。而在DataPool(数据池)中包含了池组的链表,支持动态添加和删除池组,这样既保证了数组的灵活性,也保证了查找的速度。要查找资源的时候先找到资源所在组,再从组中查找具体资源。树的概念就在这里体现出来了。
前面讲到了三个概念:DataPool、PoolGroup、DataPoolInterface。下面我们来看看它们的关系:
下面看看他们的具体实现。
cPoolGroup(池组)
template
class cPoolGroup
{
public:
cPoolGroup(uint16 maxCount);
~cPoolGroup(){};
void create(); //创建池组
void destroy(); //销毁池组
uint16 addMember(const T& member); //添加资源数据
uint16 nextMember(); //查找下一个可用插槽
void release(uint16 index); //释放index所指处的资源
uint16 totalOpen()const; //获取剩余插槽数目
uint16 totalUsed()const; //已使用插槽数目
uint16 firstOpen()const; //第一个可用的插槽索引
bool isOpen(uint16 index)const; //检查某个插槽是否可用
T& member(uint16 index); //读取插槽中的资源数据
const T& member(uint16 index)const;
T* memberPtr(uint16 index);
const T* memberPtr(uint16 index)const;
private:
uint16 m_totalOpen; // 剩余插槽数目
uint16 m_firstOpen; // 第一个可用插槽
uint16 m_maxCount; // 总共可用插槽数目
uint16* m_nextOpenList; // 空闲插槽索引表
T* m_memberList; // 实际资源存放数组
};
从上面的类中可以看出池组存储资源的数据结构是一个数组。通过m_nextOpenList可以快速定位到当前可用的存放插槽。
cDataPoolInterface(数据池外部接口)
class cDataPoolInterface
{
public:
// 遍历回调函数
typedef void (*MEMBER_CALLBACK)(cDataPoolInterface* pPool, cPoolHandle handle, void* member);
cDataPoolInterface():m_initialized(false){};
virtual ~cDataPoolInterface(){};
virtual void initialize(uint16 growSize)=0;;
virtual void destroy()=0;;
virtual void clear()=0;
virtual cPoolHandle nextHandle()=0;
virtual void release(cPoolHandle* pHandle)=0;
virtual void forEach(MEMBER_CALLBACK function)=0;
//获取资源指针
virtual void* getGenericPtr(cPoolHandle index)=0;
bool isInitialized()const {return m_initialized;}
protected:
bool m_initialized;
};
数据池接口部分为数据池的访问提供了统一的接口,最重要的就是getGenericPtr,根据cPoolHandle类型的index访问具体的数据资源。
注:这里的cPoolHandle是一个uint16类型,包含了组号和索引号。
cDataPool(数据池)
template
class cDataPool : public cDataPoolInterface
{
public:
// Data Types & Constants...
typedef T DATA_TYPE;
typedef cPoolGroup MemberGroup;
typedef std::list MemberGroupList;
typedef cDataPoolInterface::MEMBER_CALLBACK MEMBER_CALLBACK;
// Creators...
cDataPool();
~cDataPool();
void initialize(uint16 growSize);
void destroy();
const T& operator[](cPoolHandle handle)const {return get(handle);}
T& operator[](cPoolHandle handle) {return get(handle);}
// Mutators...
cPoolHandle add(const T& member);//添加资源到数据池中
void clear();
cPoolHandle nextHandle();
void release(cPoolHandle* pHandle);//是否某个指定的资源
void forEach(MEMBER_CALLBACK function);
// Accessors...
bool isHandleValid(cPoolHandle index)const;
const T& get(cPoolHandle index)const;//获取资源
T* getPtr(cPoolHandle index);
void* getGenericPtr(cPoolHandle index);
private:
// Private Data...
MemberGroupList m_groupList;
uint16 m_totalMembers;
uint16 m_totalOpen;
uint16 m_groupCount;
uint32 m_indexMask;
int m_indexShift;
cPoolGroup* addGroup();//在组不够用的时候,新增组
cPoolGroup* findOpenGroup(unsigned* groupNumber);
cPoolGroup* getGroup(unsigned index);
const cPoolGroup* getGroup(unsigned index)const;
int getGroupNumber(cPoolHandle handle)const; //通过handle值生成组号
int getItemIndex(cPoolHandle handle)const; //通过handle生成索引号
cPoolHandle buildHandle(int group, int index)const;//根据组号和索引号生成handle
//不支持拷贝构造和赋值
explicit cDataPool(const cDataPool& Src);
cDataPool& operator=(const cDataPool& Src);
};
源代码非常清晰,这里没有太多需要解释的。总之,记住资源是以树的形态管理起来的。
二、资源管理器
刚刚说到同种类型的资源被放置在同一个DataPool(数据池)中,但是有如此多种类的资源(纹理,动画,模型,材质等)又该如何管理呢?呵呵,GAIA在处理这个问题的时候,用到了上面讨论过的哲学。
GAIA引擎按照Family(系)和Type(类)对资源进行了区分。先将资源分为三大系列,每个系列中再细分出具体的类型。
以下代码片段是cResourceCode的定义:
union cResourceCode
{
struct
{
uint16 family;
uint16 type;
};
uint32 value;
};
//资源系
enum RESOURCE_FAMILY
{
k_nVideoResource=0,//视频资源
k_nAudioResource, //音频资源
k_nGameResource, //其他游戏资源
k_nTotalResourceFamilies //资源系总数
};
//资源类型
enum VIDEO_RESOURCES
{
k_nTextureResource=0, //纹理资源
k_nVBufferResource, //顶点缓存
k_nIBufferResource, //索引缓存
k_nRenderResource, //fx文件资源
k_nRenderSetResource, //渲染方式资源(RenderMethod,具体后面讲到绘制的时候详解)
k_nModelResource, //模型动画资源
k_nImageResource, //图片资源
k_nSMaterialResource, //材质资源
k_nTotalVideoResources //资源类型总数
};
每个DataPool只用于存放同一种类型的资源,这么多的资源要统一管理,所以GAIA定义了cResourcePool类来表示某一种类型的资源,并创建一个DataPool来为自身存储数据。同样为了通用,采用了模板类和继承同一个接口的方式,继承了cResourcePoolInterface。那么所有的资源都通过cResourcePoolInterface提供的接口进行操作了。由于所有资源都有统一的接口进行操作,那么再添加一个资源管理器(ResourceManager)就可以完成对资源的控制了。这里又产生了三个概念:ResourcePool、ResourcePoolInterface、ResourceManager。接着来看看它们的关系图:
具体的类代码,这里就不贴了。大家可以参考源代码。今天先写到这里吧,头晕了。。