пятница, 28 сентября 2012 г.

4. Управляем ресурсами

Внимательно посмотрев на наш проект, можно заметить, что мы довольно-таки бездумно распоряжаемся оперативной памятью. Главным пожирателем оперативной памяти, в нашем случае, является загрузка изображений. Каждый спрайт загружает изображение заново и если какой-то рисунок должен быть использован повторно, под него будет использовано в два раза больше памяти чем нужно. Кроме того, отсутствует безопасная возможность выгрузки уже не используемых изображений.

Пока мы загружаем не много изображений, эти недостатки не очень заметны, но как только мы попытаемся сделать что-то серьезное, памяти может не хватить. Попробуем это исправить. Главным нашим оружием станет класс с гордым названием ResourceManager:

ResourceManager.h:

#ifndef _RESOURCEMANAGER_H_
#define _RESOURCEMANAGER_H_

#if defined IW_DEBUG
#define LOAD_ALL_GROUPS true
#else
#define LOAD_ALL_GROUPS false
#endif

#include <map>
#include <string>

#include "s3e.h"
#include "IwResManager.h"
#include "IwSound.h"

#include "ResourceHolder.h"

using namespace std;

class ResourceManager {
    private:   
        map<string, ResourceHolder*> res;
    public:
        ResourceManager(): res() {}
        void init();
        void release();
        ResourceHolder* load(const char* name, int loc, bool isUnloaded = false);

    typedef map<string, ResourceHolder*>::iterator RIter;
    typedef pair<string, ResourceHolder*> RPair;
};

extern ResourceManager rm;
       
#endif    // _RESOURCEMANAGER_H_


ResourceManager.cpp:

#include "ResourceManager.h"
#include "Locale.h"

ResourceManager rm;

void ResourceManager::init() {
    IwResManagerInit();
#ifdef IW_BUILD_RESOURCES
    IwGetResManager()->AddHandler(new CIwResHandlerWAV);
#endif
    IwGetResManager()->LoadGroup("sounds.group");
    if (LOAD_ALL_GROUPS || (Locale::getCurrentImageLocale() == elEnImage)) {
        IwGetResManager()->LoadGroup("locale_en.group");
    }
    if (LOAD_ALL_GROUPS || (Locale::getCurrentImageLocale() == elRuImage)) {
        IwGetResManager()->LoadGroup("locale_ru.group");
    }
}

void ResourceManager::release() {
    for (RIter p = res.begin(); p != res.end(); ++p) {
        delete p->second;
    }
    res.clear();
    IwResManagerTerminate();
    IwSoundTerminate();
}

ResourceHolder* ResourceManager::load(const char* name, int loc, bool isUnloaded) {
    ResourceHolder* r = NULL;
    string nm(name);
    RIter p = res.find(nm);
    if (p == res.end()) {
        switch (loc) {
            case elEnImage:
            case elRuImage: loc = Locale::getCurrentImageLocale();
        }
        r = new ResourceHolder(name, loc);
        res.insert(RPair(nm, r));
    } else {
        r = p->second;
    }
    if (!isUnloaded) {
        r->load();
    }
    return r;
}

В методы init и release переносим весь код, связанный с загрузкой ресурсов из Main.cpp. Группы ресурсов, используемые для локализации интерфейса, загружаем выборочно, если собираем release. В debug-сборке, загружаем все группы, чтобы скомпилировать их для deployment-а. В методе load создаем объект-wrapper, задачей которого является управление хранением ресурса в оперативной памяти. В случае, если ресурс был ранее загружен, находим ResourceHolder и возвращаем его, для повторного использования. Реализация ResorceHolder предельно проста:

ResourceHolder.h:

#ifndef _RESOURCEHOLDER_H_
#define _RESOURCEHOLDER_H_

#include <string>

#include "s3e.h"
#include "Iw2D.h"
#include "IwResManager.h"

using namespace std;

class ResourceHolder {
    private:
        string name;
        int loc;
        CIw2DImage* data;
    public:
        ResourceHolder(const char* name, int loc);
       ~ResourceHolder() {unload();}
        void load();
        void unload();
        CIw2DImage* getData();
};
       
#endif    // _RESOURCEHOLDER_H_


ResourceHolder.cpp:

#include "ResourceHolder.h"
#include "Locale.h"

ResourceHolder::ResourceHolder(const char* name, int loc): name(name)
                                               , loc(loc)
                                               , data(NULL) {
}

void ResourceHolder::load() {
    if (data == NULL) {
        CIwResGroup* resGroup;
        const char* groupName = Locale::getGroupName(loc);
        if (groupName != NULL) {
            resGroup = IwGetResManager()->GetGroupNamed(groupName);
            IwGetResManager()->SetCurrentGroup(resGroup);
            data = Iw2DCreateImageResource(name.c_str());
        } else {
            data = Iw2DCreateImage(name.c_str());
        }
    }
}

void ResourceHolder::unload() {
    if (data != NULL) {
        delete data;
        data = NULL;
    }
}

CIw2DImage* ResourceHolder::getData() {
    load();
    return data;
}


В метод load, переносим код загрузки ресурса из Sprite. Метод getData будет загружать ресурс (если он еще не загружен) и возвращать указатель на него вызывающему. Также, добавляем метод unload, позволяющий выгружать неиспользуемый ресурс из оперативной памяти. Далее, вносим изменения в Sprite:

Sprite.h:

#ifndef _SPRITE_H_
#define _SPRITE_H_

#include "AbstractScreenObject.h"
#include "ISprite.h"
#include "ISpriteOwner.h"
#include "Locale.h"
#include "ResourceManager.h"

class Sprite: public AbstractScreenObject
            , public ISprite {
    protected:
        ISpriteOwner* owner;
        ResourceHolder* img;
        int capturedId;
    public:
        Sprite(ISpriteOwner* owner, int x, int y , int zOrder = 0);
        Sprite(ISpriteOwner* owner, const char* res, int x, int y, 
               int zOrder = 0, int locale = elImage);
        virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL);
        virtual bool sendMessage(int msg, int x, int y);
        virtual void update(uint64 timestamp) {}
        virtual void refresh();
        virtual void addImage(const char*res, int state = 0, int locale = 0);
        virtual CIw2DImage* getImage(int id = 0);
        virtual int  getState()  {return 0;}
        virtual int  getWidth();
        virtual int  getHeight();
        virtual void unload();
};

#endif    // _SPRITE_H_


Sprite.cpp:

...
void Sprite::addImage(const char*res, int state, int loc) {
    img = rm.load(res, loc);
}

CIw2DImage* Sprite::getImage(int id) {
    return img->getData();
}

void Sprite::unload() {
    if (img != NULL) {
        img->unload();
    }
}

Background.cpp:

#include "Background.h"

Background::Background(ISpriteOwner* owner, const char* res, int zOrder, 
           int locale): Sprite(owner, res, 0, 0, zOrder, locale) {}

void Background::refresh() {
    CIwMat2D m;
    m.SetRot(0);
    m.ScaleRot(IW_GEOM_ONE);
    m.SetTrans(CIwSVec2(0, 0));
    Iw2DSetTransformMatrix(m);
    Iw2DSetAlphaMode(alpha);
    Iw2DDrawImage(getImage(), CIwSVec2(0, 0), CIwSVec2(owner->getDesktopWidth(),
                  owner->getDesktopHeight()));
}


Деструктор убираем, поскольку вместо изображения, мы храним указатель на wrapper, хранением которого управляем ResourceManager. Реализация измененных методов тривиальна. В AbstractSpriteOwner добавляем код выгрузки ресурсов:

IObject.h:

#ifndef _IOBJECT_H_
#define _IOBJECT_H_

#include "s3e.h"

class IObject {
  public:
    virtual ~IObject() {}  
    virtual bool isBuzy()                                                      = 0;
    virtual int  getState()                                                    = 0;
    virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL) = 0;
    virtual bool sendMessage(int msg, int x, int y)                            = 0;
    virtual void update(uint64 timestamp)                                      = 0;
    virtual void refresh()                                                     = 0;
    virtual void unload()                                                      = 0;
};

#endif    // _IOBJECT_H_
AbstractSpriteOwner.h:

#ifndef _ABSTRACTSPRITEOWNER_H_
#define _ABSTRACTSPRITEOWNER_H_

#include <map>

#include "IObject.h"
#include "ISpriteOwner.h"
#include "AbstractScreenObject.h"

using namespace std;

class AbstractSpriteOwner: public ISpriteOwner {
         . . .
    public:
        AbstractSpriteOwner();
        virtual ~AbstractSpriteOwner();
                   . . .
        virtual void unload();
         . . .
};

#endif    // _ABSTRACTSPRITEOWNER_H_

AbstractSpriteOwner.cpp:

...
void AbstractSpriteOwner::unload() {
    for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) {
        p->second->unload();
    }
}


Здесь тоже все очевидно. Просто вызываем метод unload для каждого хранимого объекта. Осталось внести изменения в Main.cpp:

...
void init() {
    // Initialise Mamrlade graphics system and Iw2D module
    IwGxInit();
    Iw2DInit();

    // Init IwSound
    IwSoundInit();

    // Set the default background clear colour
    IwGxSetColClear(0x0, 0x0, 0x0, 0);

    // Initialise the resource manager
    rm.init();

    touchPad.init();
    desktop.init();
}

void release() {
    desktop.release();
    touchPad.release();

    // Shut down the resource manager
    rm.release();

    Iw2DTerminate();
    IwGxTerminate();
}
...
Весь исходный код проекта, как обычно, можно получить здесь.

Комментариев нет:

Отправить комментарий