首页 > 代码库 > 源码剖析:TheManaWorld_资源管理系统

源码剖析:TheManaWorld_资源管理系统

 

 

TheManaWorld是一个开源2D MMORPGhttps://www.themanaworld.org/ 以下简称TMW,它的资源管理比较典型:

1 基于引用计数使资源自动归还

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时资源回收的策略,马上回收与延后回收。延后回收的策略非常有用,比如游戏中常有同造型的怪删除后又马上创建出来。

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()说明资源管理器采用的是单例模式,比较直观。因为游戏只需要一个资源管理器,且为其他地方的代码直接访问资源管理器提供了方便。

然后跟踪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,

延后删除的资源从mResources中移除后放到mOrphanedResources中时时保存起来,并且记录下时间戳.时间戳主要作用就是判断mOrphanedResources中的资源是否长期不引用到了应该彻底回收的时候

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是在每次请求资源的时候调用一次.

那外部如何从资源管理器请求资源,请求的资源又如何从硬盘加载?我们从上层逻辑代码往下跟.

怪物,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 = http://www.mamicode.com/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小游戏来说足够简单实用,想在上面加功能也是比较容易的。