IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> cocos2d-x 内存管理机制 -> 正文阅读

[游戏开发]cocos2d-x 内存管理机制

注: 本文从个人博客园摘录而来。

Ref

cocos2d-x中几乎所有的对象节点继承于Ref,而Ref主要对对象进行引用技术管理

class CC_DLL Ref:
{
public:
    void retain();                    // 增加引用计数
    void release();                   // 减少引用计数,引用计数为0时进行释放
    Ref* autorelease();               // 添加自动缓存池
    unsigned int getReferenceCount(); // 获取对象引用计数
protected:
    Ref();                            // 构造函数设为protected,保证可被继承且智能子类实例化 
public:
    virtual ~Ref();										
protected:
    unsigned int _referenceCount;     // 引用计数数目
    friend class AutoreleasePool;     // 自动释放池
}  

其主要接口为:

// 引用计数+1
void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    ++_referenceCount;
}

// 引用计数-1
void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_referenceCount;

  	// 引用计数为0时,销毁对象
    if (_referenceCount == 0)
    {
        delete this;
    }
}

简单理解,就是retainrelese对对象节点进行了引用计数的加减1操作而已。

如果引用计数为0的时候,对象就会被销毁掉。

通过new创建一个对象,简单的示例:

auto node = new Node();         // 引用计数为1
addChild(node);                 // 引用计数为2
node->removeFromParent();       // 引用计数为1
node->release();                // 引用计数为0,销毁对象

autoRelease

retain()release()接口要配对使用。

上面的例子,如果忘记调用release()接口,就会导致内存泄漏。

为此Ref提供了autoRelease()来帮助我们自动管理内存的释放相关。

// 添加对象到自动释放池中
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

// 对象会被添加到_managedObjectArray的vector容器中
// std::vector<Ref*> _managedObjectArray;
void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

而对于autorelease的使用,主要体现在cocos2d-x提供的静态方法:create

此方法又称为二次构建模式, 主要是为了简化程序对节点的创建,以及忘记初始化相关。

// CCNode.cpp
Node * Node::create()
{
    Node * ret = new (std::nothrow) Node();
    if (ret && ret->init())
    {
        ret->autorelease(); 			// 将对象添加到自动释放池中
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}
// CCSprite.cpp
Sprite* Sprite::create(const std::string& filename)
{
    Sprite *sprite = new (std::nothrow) Sprite();
    if (sprite && sprite->initWithFile(filename))
    {
        sprite->autorelease();		// 将对象添加到自动释放池中
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}
// UIButton.cpp
Button* Button::create()
{
    Button* widget = new (std::nothrow) Button();
    if (widget && widget->init())
    {
        widget->autorelease();		// 将对象添加到自动释放池中
        return widget;
    }
    CC_SAFE_DELETE(widget);
    return nullptr;
}

cocos2d-x会在每帧结束时,清理当前自动释放池中的对象。

void Director::mainLoop()
{
    if (! _invalid)
    {
        drawScene();
     		// 清理当前释放池对象
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

void AutoreleasePool::clear()
{
    // 千万注意此处
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
  
    // 遍历所有对象,进行引用计数-1,为0的销毁对象
    for (const auto &obj : releasings)
    {
        obj->release();
    }
}

如果一个对象在创建后,在当前帧结束没有被使用(addChild), 对象就会被销毁掉。

local node = cc.Node:create()
print("引用计数为:", node:getReferenceCount())			-- 引用计数为:1

-- 延迟1秒
local delay = cc.DelayTime:create(1)
local action = cc.Sequence:create(delay, cc.CallFunc:create(function()
    -- Error: invalid 'cobj' in function 'lua_cocos2dx_Ref_getReferenceCount'
    print("引用计数为:", node:getReferenceCount())       
end))
self._root:runAction(action)

所以对象一定要在当前帧结束前调用(addChild)

local node = cc.Node:create()
print("引用计数为:", node:getReferenceCount())			 -- 引用计数为:1
self._root:addChild(node)
print("引用计数为:", node:getReferenceCount())			 -- 引用计数为:2

-- 
local delay = cc.DelayTime:create(1)
local action = cc.Sequence:create(delay, cc.CallFunc:create(function()
    print("引用计数为:", node:getReferenceCount())  	-- 引用计数为:1     
end))
self._root:runAction(action)
end 

在对象addChild之后,引用计数由2变为1的缘故,在于:

  • mainLoop每帧结束执行的clear操作

  • clear中使用swap方法保证了节点对象在池中仅存在一帧的时间,避免重复遍历

void AutoreleasePool::clear()
{
    // 使用临时对象,交互容器数据
    // _managedObjectArray会被置空,releasings在函数运行结束后也会被释放
    std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
}

当对象不再使用后,通过:

  • removeFromParent来删除对象,引用计数由1变为0,对象会被释放
  • 退出场景,每个节点会被调用release,完成场景节点树的释放
// 比如removeFromParent的释放
void Node::removeFromParent()
{
    this->removeFromParentAndCleanup(true);
}

void Node::removeFromParentAndCleanup(bool cleanup)
{
    if (_parent != nullptr)
    {
        _parent->removeChild(this,cleanup);
    } 
}

void Node::removeChild(Node* child, bool cleanup /* = true */)
{
    // explicit nil handling
    if (_children.empty())
    {
        return;
    }

    ssize_t index = _children.getIndex(child);
    if( index != CC_INVALID_INDEX )
        this->detachChild( child, index, cleanup );
}

void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
   // ..
   _children.erase(childIndex);
}

iterator erase(ssize_t index)
{
   CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
   auto it = std::next( begin(), index );
   (*it)->release(); 				// Ref::release()
   return _data.erase(it);
}

以此,实现了Ref的自动化管理。

PoolManager

自动释放池是由PoolManager管理类控制的。

/*
* 构造函数私有化,且创建了getInstance(), 说明该类是个单例模式,全局仅有一个实例对象
* 析构函数私有化,且创建了destroyInstance(), 说明该类放置在栈中创建对象
*/
class CC_DLL PoolManager
{
public:
    static PoolManager* getInstance();
    static void destroyInstance();
		// 获取当前释放池
    AutoreleasePool *getCurrentPool() const;
    // 判定指定节点是否在对象池中
    bool isObjectInPools(Ref* obj) const;
    
private:
    PoolManager();
    ~PoolManager();
    // 压入池数据
    void push(AutoreleasePool *pool);
    // 移除池数据
    void pop();
    
    static PoolManager* s_singleInstance;
    std::vector<AutoreleasePool*> _releasePoolStack;
};

该类在初始化状态下会默认添加创建一个AutorelesePool的池对象。主要原因在于存储cocos2d-x UI节点对象的清理相关。

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new (std::nothrow) PoolManager();
        // Add the first auto release pool
        new AutoreleasePool("cocos2d autorelease pool");
    }
    return s_singleInstance;
}

AutoreleasePool在构造函数中,会将自身指针添加到PoolManager内的管理池队列中。

这样就能通过PoolManager来管理池队列。

AutoreleasePool::AutoreleasePool(): _name("")
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}

AutoreleasePool::AutoreleasePool(const std::string &name): _name(name)
{
    // 初始化对象池
    _managedObjectArray.reserve(150);
    // 将自身添加到管理类的队列中
    PoolManager::getInstance()->push(this);
}

AutoreleasePool::~AutoreleasePool()
{
    CCLOGINFO("deallocing AutoreleasePool: %p", this);
    clear();
    
    PoolManager::getInstance()->pop();
}

End

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:50:05  更:2021-12-24 18:51:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 21:03:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码