源碼剖析:The Mana World_資源管理系統

 

 

TheManaWorld是一個開源2D MMORPGhttps://www.themanaworld.org/ 如下簡稱TMW,它的資源管理比較典型:node

1 基於引用計數使資源自動歸還ide

2 各類資源在加載時根據類型作分派函數

 

先從Resouce與ResouceManager這兩個類開始,Resouce主要提供了引用計數的功能,ResouceManager基於Resouce提供的抽象接口編寫資源管理的方法。動畫

class Resource
{
    friend class ResourceManager;

    public:
        enum OrphanPolicy {
            DeleteLater,
            DeleteImmediately
        };

        Resource():
            mRefCount(0)
        {}

        /**
         * Increments the internal reference count.
         */
        void incRef() { ++mRefCount; }

        /**
         * Decrements the reference count. When no references are left, either
         * schedules the object for deletion or deletes it immediately,
         * depending on the \a orphanPolicy.
         */
        void decRef(OrphanPolicy orphanPolicy = DeleteLater);

        /**
         * Return the path identifying this resource.
         */
        const std::string &getIdPath() const
        { return mIdPath; }

    protected:
        virtual ~Resource() {}

    private:
        std::string mIdPath; /**< Path identifying this resource. */
        time_t mTimeStamp;   /**< Time at which the resource was orphaned. */
        unsigned mRefCount;  /**< Reference count. */
};

OrphanPolicy指明瞭在引用計數降爲0時資源回收的策略,立刻回收與延後回收。延後回收的策略很是有用,好比遊戲中常有同造型的怪刪除後又立刻建立出來。this

void Resource::decRef(OrphanPolicy orphanPolicy)
{
    // Reference may not already have reached zero
    if (mRefCount == 0) {
        logger->log("Warning: mRefCount already zero for %s", mIdPath.c_str());
        assert(false);
    }

    --mRefCount;

    if (mRefCount == 0)
    {
        ResourceManager *resman = ResourceManager::getInstance();

        switch (orphanPolicy)
        {
            case DeleteLater:
            default:
                resman->release(this);
                break;
            case DeleteImmediately:
                resman->remove(this);
                delete this;
                break;
        }
    }
}

ResouceManager::getInstance()說明資源管理器採用的是單例模式,比較直觀。由於遊戲只須要一個資源管理器,且爲其餘地方的代碼直接訪問資源管理器提供了方便。spa

而後跟蹤Reslese和remove兩種方法有何不一樣指針

class ResouceManager
{
    ...
        typedef std::map<std::string, Resource*> Resources;
        typedef Resources::iterator ResourceIterator;

        Resources mOrphanedResources;
        Resources mResources;
    
}

void ResourceManager::release(Resource *res)
{
    ResourceIterator resIter = mResources.find(res->mIdPath);

    // The resource has to exist
    assert(resIter != mResources.end() && resIter->second == res);

    timeval tv;
    gettimeofday(&tv, NULL);
    time_t timestamp = tv.tv_sec;

    res->mTimeStamp = timestamp;
    if (mOrphanedResources.empty())
        mOldestOrphan = timestamp;

    mOrphanedResources.insert(*resIter);
    mResources.erase(resIter);
}

void ResourceManager::remove(Resource *res)
{
    mResources.erase(res->mIdPath);
}

ResouceManager維護了兩個列表mOrphanedResources和mResources,code

延後刪除的資源從mResources中移除後放到mOrphanedResources中時時保存起來,而且記錄下時間戳.時間戳主要做用就是判斷mOrphanedResources中的資源是否長期不引用到了應該完全回收的時候xml

void ResourceManager::cleanOrphans()
{
    timeval tv;
    gettimeofday(&tv, NULL);
    // Delete orphaned resources after 30 seconds.
    time_t oldest = tv.tv_sec;
    time_t threshold = oldest - 30;

    if (mOrphanedResources.empty() || mOldestOrphan >= threshold)
        return;

    ResourceIterator iter = mOrphanedResources.begin();
    while (iter != mOrphanedResources.end())
    {
        Resource *res = iter->second;
        time_t t = res->mTimeStamp;
        if (t >= threshold)
        {
            if (t < oldest)
                oldest = t;
            ++iter;
        }
        else
        {
            logger->log("ResourceManager::release(%s)", res->mIdPath.c_str());
            ResourceIterator toErase = iter;
            ++iter;
            mOrphanedResources.erase(toErase);
            delete res; // delete only after removal from list, to avoid issues in recursion
        }
    }

    mOldestOrphan = oldest;
}

從當前時間點往前推30秒threshold,若是在這個時間以前release的則應該回收,以後的保留,而且統計出保留的資源裏最老的時間戳mOldestOrphan,若是下次清理資源的時候發現mOldestOrphan還在threshold以後,那說明沒有資源須要回收,能夠避免沒必要要的遍歷.那麼cleanOrphans()什麼時候調用呢?這個因各個遊戲的資源管理策略而定,TMW是在每次請求資源的時候調用一次.blog

那外部如何從資源管理器請求資源,請求的資源又如何從硬盤加載?咱們從上層邏輯代碼往下跟.

怪物,NPC,玩家都是類Being,Being的構造函數調用setSubtype()->setupSpriteDisplay

void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display, bool forceDisplay) { clear(); SpriteRefs it, it_end; for (it = display.sprites.begin(), it_end = display.sprites.end(); it != it_end; it++) { std::string file = paths.getStringValue("sprites") + it->sprite; int variant = it->variant; addSprite(AnimatedSprite::load(file, variant)); }

setupSpriteDisplay會遍歷所須要加載的sprite信息,調用addSprite,addSprite是CompoundSprite類的成員函數,Being繼承於CompoundSprite,是Sprite的組合。跟進AnimatedSprite::load(file, variant)看sprite是如何加載的

AnimatedSpritre::load

ResourceManager *resman = ResourceManager::getInstance();
    SpriteDef *s = resman->getSprite(filename, variant);

 

SpriteDef *ResourceManager::getSprite(const std::string &path, int variant) { SpriteDefLoader l = { path, variant }; std::stringstream ss; ss << path << "[" << variant << "]"; return static_cast<SpriteDef*>(get(ss.str(), SpriteDefLoader::load, &l)); } Resource *ResourceManager::get(const std::string &idPath, generator fun, void *data) { // Check if the id exists, and return the value if it does. ResourceIterator resIter = mResources.find(idPath); if (resIter != mResources.end()) { resIter->second->incRef(); return resIter->second; } resIter = mOrphanedResources.find(idPath); if (resIter != mOrphanedResources.end()) { Resource *res = resIter->second; mResources.insert(*resIter); mOrphanedResources.erase(resIter); res->incRef(); return res; } Resource *resource = fun(data); if (resource) { resource->incRef(); resource->mIdPath = idPath; mResources[idPath] = resource; cleanOrphans(); } // Returns NULL if the object could not be created. return resource; }

 

struct SpriteDefLoader
{
    std::string path;
    int variant;
    static Resource *load(void *v)
    {
        SpriteDefLoader *l = static_cast< SpriteDefLoader * >(v);
        return SpriteDef::load(l->path, l->variant);
    }
};

注意get資源的第二個參數SpriteDefLoader::load ,參數類型在ResouceManager中有定義typedef Resource *(*generator)(void *) 是一個函數指針,函數指針爲完成各類類型資源加載提供了間接層

而SpriteDef繼承於Resouce,其成員函數load完成讀取xml配置加載動畫的任務.加載動畫時又會進一步分解爲加載幀圖片的任務

void SpriteDef::loadImageSet(xmlNodePtr node, const std::string &palettes)
{
    const std::string name = XML::getProperty(node, "name", "");

    // We don't allow redefining image sets. This way, an included sprite
    // definition will use the already loaded image set with the same name.
    if (mImageSets.find(name) != mImageSets.end())
        return;

    const int width = XML::getProperty(node, "width", 0);
    const int height = XML::getProperty(node, "height", 0);
    std::string imageSrc = XML::getProperty(node, "src", "");
    Dye::instantiate(imageSrc, palettes);

    ResourceManager *resman = ResourceManager::getInstance();
    ImageSet *imageSet = resman->getImageSet(imageSrc, width, height);

    if (!imageSet)
    {
        logger->error(strprintf("Couldn't load imageset (%s)!",
                                imageSrc.c_str()).c_str());
    }

    imageSet->setOffsetX(XML::getProperty(node, "offsetX", 0));
    imageSet->setOffsetY(XML::getProperty(node, "offsetY", 0));
    mImageSets[name] = imageSet;
}

獲取幀圖片是又會經過ResouceManager提供的獲取圖片的接口.而getImageSet與getSprite代碼幾乎一致 也是有個ImageSetLoader配接類型和繼承於Resource的ImageSet.

ImageSet是調用一連串的幀圖片,也會經過ResourceManager的getImage接口

struct DyedImageLoader
{
    ResourceManager *manager;
    std::string path;
    static Resource *load(void *v)
    {
        DyedImageLoader *l = static_cast< DyedImageLoader * >(v);
        std::string path = l->path;
        std::string::size_type p = path.find('|');
        Dye *d = NULL;
        if (p != std::string::npos)
        {
            d = new Dye(path.substr(p + 1));
            path = path.substr(0, p);
        }
        SDL_RWops *rw = PHYSFSRWOPS_openRead(path.c_str());
        if (!rw)
        {
            delete d;
            return NULL;
        }
        Resource *res = d ? Image::load(rw, *d)
                          : Image::load(rw);
        delete d;
        return res;
    }
};

Image *ResourceManager::getImage(const std::string &idPath)
{
    DyedImageLoader l = { this, idPath };
    return static_cast<Image*>(get(idPath, DyedImageLoader::load, &l));
}

image的load很是簡單,PHYSFSRWOPS_openRead是PHYSFS與SDL_RWops的封裝函數.PHYSFS好像是一個提供了掛接解壓等功能的跨平臺文件庫,開源遊戲裏常常見到,有時間研究下…

致此TMW的資源管理思路就大體摸清了,其餘如粒子和音效的資源也是這麼管理的,跟蹤resourceManager的get就能看到脈絡。

TMW的資源管理比較簡單,功能也比較單一,只是使用引用計數來跟蹤回收。遊戲中的資源管理器有着各類各樣的策略,因需求而定。有的遊戲會分別擬定每種類型的資源佔用內存的上限,若是超過上限會單獨回收這種資源,還有的會結合內存池的使用,減小堆的碎片。遊戲的資源管理老是跟內存管理息息相關,而C++在內存管理方面天生的定製能力提供了優點。頁遊中的資源管理方式則與此截然不同的,如AS依賴於垃圾回收又苦於垃圾回收對穩定速度的干擾,帶寬的緊張使得資源的釋放也不能這麼隨意等等,思路又是大相徑庭。

總之,這個資源管理器對於2D小遊戲來講足夠簡單實用,想在上面加功能也是比較容易的。

相關文章
相關標籤/搜索