游戏开发中经常需要频繁产生、销毁大量对象,内存本身够不够用是一方面,尤其是在手机等内存本来就有限的设备上面,另外一点是分配的速度不会对游戏体验造成影响,也就是不能影响帧率。
相比内存池,对象池更易用更容易管理,而且还可以利用脏数据,也就是上次被回收掉的对象的数据。而且偶尔的空间分配失败其实不是那么重要(后面会讲怎么在会失败的情况下完成分配任务),游戏中还是速度更重要些。
原理
一次申请大量连续内存(整数个对象大小),最好用堆,当然如果用栈数组也没人拦你,栈空间可是相当有限…
由于分配的对象生存期是不固定的(如下图),池不可能保持已分配对象的连续性,这时进行块移动会降低程序效率。
分配 | 分配 | 分配 | 分配 |
所以需要把闲置对象的指针放入容器中来管理。此容器必须能快速存取删,而且不需要频繁大距离移动容器元素指针,最好是刚从容器中释放的元素能马上让 下一个元素使用,这时候栈就是一个很好的选择了。初始时将所有闲置对象指针压入栈,分配时pop,栈为空时返回空;释放时将对象指针push进栈即可。
实现
其实boost已经提供了对象池了,那为什么还要自己实现一个呢?当然是要方便DIY了…其实你也可以用boost的对象池来第二次封装
这部分直接参看附件源码吧
使用
这才是真正的重点
分配时直接用Sobot* p = ObjPool<Sobot>::alloc()?不,还应该使用placement new调用其构造函数:
new(p) Sobot()
你想在你的代码中充斥大量这样的代码吗?放到工厂里面也许是一种办法,但是工厂引用到了对象池了。而大师告诉我们好的设计要保持职责单一,用与不用对象池应该不影响原系统的正常运行。而且还有一点,用这种办法,就只能和某些组件绝缘了,比如智能指针。
此时重载new与delete就至关重要了:
- static void* operator new(size_t) {
- return SobotPool::instance().alloc();
- }
- static void operator delete(void* p) {
- SobotPool::instance().free(reinterpret_cast<Sobot*>(p));
- }
一个对象中往往充斥着大量指针,而这些指针指向的空间往往大于包含他们的对象本身。如果将这些指针所在在类也应用对象池,一方面是池的容量你无法估 计,另一方面是使用起来麻烦。而且你也无法向上面这样给每个类注入new与delete的重载。用代理?呵呵,项目中估计会出一堆问题。这时候我们不妨使 用脏数据,也就是说对象池中保存的对象全是可以直接使用的对象,而并非空对象,对象中的成员指针变量引用到的内存不在池中。为了保证安全,清空这些内存在 池销毁时进行。
和上面的功能一起,我们可以定义一个宏,免得每次使用都得重复大量代码。如下:
- #define USING_DIRTY_DATA true
- // 如果不是方便测试需要,可以将这行
- // typedef ObjPool<obj_class, max_size> obj_class##Pool; \
- // 标注为private
- #define DECLARE_USING_OBJ_POOL(obj_class, max_size, _using_dirty_data) \
- public: \
- typedef ObjPool<obj_class, max_size> obj_class##Pool; \
- friend class obj_class##Pool; \
- static const bool using_dirty_data = _using_dirty_data; \
- public: \
- ~obj_class() { \
- if (!_using_dirty_data) {this->purge();} \
- } \
- static void* operator new(size_t) { \
- return obj_class##Pool::instance().alloc(); \
- } \
- static void operator delete(void* p) { \
- obj_class##Pool::instance().free(reinterpret_cast<obj_class*>(p)); \
- } \
- static bool loadCache() { \
- while (true) { \
- obj_class* obj = new obj_class; \
- if (obj != NULL) { \
- if (!obj->init()) { \
- return false; \
- } \
- } else { \
- break; \
- } \
- }; \
- obj_class##Pool::instance().freeAll(); \
- return true; \
- }
调用时在类中加入如下代码:
- // DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, (NOT USING_DIRTY_DATA))
- DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, USING_DIRTY_DATA)
LoadCache是游戏加载阶段调用的,这里将进行所有池对象的初始化。为此,你还需要实现init和purge函数,分别是初始资源,销毁资源 的,这些其实都只会被调用一次的。像状态的初始化,大可放构造函数中,每次使用对象构造函数都会被调用的。外界是不能直接操作pool的。
如果池容量过小,分配失败其实并不可怕。
见例子:
- // 大规模测试
- list<Entity*> timer;
- struct _Timer{
- list<Entity*>& _timer;
- _Timer(list<Entity*>& timer) : _timer(timer) {}
- void operator()() {
- for (list<Entity*>::iterator iter = _timer.begin();
- iter != _timer.end();) {
- Entity* entity = *iter;
- if (entity->isValid()) {
- (*iter)->update();
- } else {
- entity->destroy();
- iter = _timer.erase(iter);
- continue;
- }
- ++iter;
- } // end for
- }
- } update_timer(timer);
- const int num = 50;
- log << endl << "大规模测试:" << endl;
- for (int i = 0; i < num; ++i) {
- Entity* entity = ObjManager<Entity>::instance().make("Bullet");
- if (IS_VALID_POINTER(entity)) {
- log << " alloced index:" << i << endl;
- timer.push_back(entity);
- } else {
- log << " alloc bullet failed, waiting..." << endl;
- // 失败了就多尝试一次,反正任务量是20个
- --i;
- }
- update_timer();
- }
- // 不管使用什么模式都要自己回收所有的对象,
- // 不要依赖于池析构时的对象释放
- for (list<Entity*>::iterator iter = timer.begin();
- iter != timer.end(); ++iter) {
- (*iter)->destroy();
池容量为3,这是运行结果:
[0sec] 加载缓存 [0sec] Bullet1 with HP:2 [0sec] init Bullet1 [0sec] Bullet2 with HP:3 [0sec] init Bullet2 [0sec] Bullet3 with HP:5 [0sec] init Bullet3 [0sec] 大规模测试: [0sec] Bullet10 with HP:5 [0sec] alloced index:0 [0sec] Bullet11 with HP:1 [0sec] alloced index:1 [0sec] Bullet12 with HP:1 [0sec] alloced index:2 [0sec] destroy entity11 [0sec] Bullet13 with HP:2 [0sec] alloced index:3 [0sec] destroy entity12 [0sec] Bullet14 with HP:3 [0sec] alloced index:4 [0sec] alloc bullet failed, waiting... [0sec] destroy entity10 [0sec] destroy entity13 [0sec] Bullet15 with HP:2 (这里省略很多行…) [1sec] alloced index:46 [1sec] Bullet57 with HP:4 [1sec] alloced index:47 [1sec] alloc bullet failed, waiting... [1sec] destroy entity55 [1sec] Bullet58 with HP:2 [1sec] alloced index:48 [1sec] alloc bullet failed, waiting... [1sec] alloc bullet failed, waiting... [1sec] destroy entity56 [1sec] destroy entity57 [1sec] destroy entity58 [1sec] Bullet59 with HP:5 [1sec] alloced index:49 [1sec] destroy entity59 [1sec] 释放池 [1sec] purge Bullet59 [1sec] freeing sprite buf. size:3 [1sec] purge Bullet56 [1sec] freeing sprite buf. size:2 [1sec] purge Bullet57 [1sec] freeing sprite buf. size:1 请按任意键继续. . . |
附件下载