中级教程七
资源与资源管理器
这篇文章的第一部分,将详细描述资源的载入、卸载和销毁的过程。第二部分中,我们将创建一个新的资源类型,以及一个相应的管理器。
目录
[隐藏]
1 资源的生命周期
1.1 术语
1.2 从头开始创建资源
1.3 资源卸载和销毁
1.4 重新载入资源(reload)
2 创建一个新的资源,并交付给资源管理器
2.1 脚本装载器(ScriptLoader)
2.2 手动资源装载器(ManualResourceLoader)
2.3 使用
资源的生命周期
OGRE的API文档里介绍了一个资源的基本生命周期,但其中一些概念比较乏味。希望在这里,事情会更清楚。
术语
以下术语被用来区分一个资源载入时可能处于的不同阶段:
未知的(Unknown):OGRE不知道有这么一个资源。资源组里面保存有它的文件名,但OGRE还不知道它用来干什么的。
声明的(Declared):直接地,或其它动作引起的,资源已经被标记为可创建的。Ogre知道它是什么类型的资源,以及当创建它时,需要做些什么。
已创建(Created):OGRE已经为这个资源创建了一个空的实例,并添加到了相应的资源管理器。
已载入(Loaded):创建的实例被完全载入,资源的所有数据都驻留在内存里。这是资源文件能被实际访问的典型阶段。你不能在创建阶段访问这些文件。
从头开始创建资源
1. OGRE的原生资源管理器是在Root::Root里创建的。
2.
首先要做的是指定一个资源位置,它是通过调用ResourceGroupManager::addResourceLocation来完成的。这个方法做了一些事情:
1. 创建指定的资源组(ResourceGroup),如果还没有创建的话。
2. 创建新的指定类型的存档(Archive)实例。
3. 创建新的资源位置(ResourceLocation),并把存档(Archive)添加给它,然后把这个资源位置(ResourceLocation)添加到资源管理组(ResourceGroup)。
4. 最后一步,获取Archive里所有文件的列表,并把它们添加到ResourceGroup的列表中。这一步完成之后,资源就处于Unknown状态了。
3.
下一步是手动声明资源。虽然当ResourceManager开始解析脚本时,许多资源会被声明,但目前它们还没有被声明。如果你打算手动声明资源,调用ResourceGroupManager::declareResource方法。这样,所有手动声明了的资源,都处于Declared状态。其它仍然是Unkown。
4.
接下面初始化ResourceGroup,通过ResourceGroupManager::initialiseResourceGroup或者ResourceGroupManager::initialiseAllResourceGroups,后者只是对于所有的ResourceGroup来调用前者。它干了这么一些事情:
1. 解析ResourceGroup里的所有脚本。脚本继承ScriptLoader,是由ResourceManager定义的。这可导致一些资源成为Declared。
2. 创建所有的Declared资源。
3. 相关的ResourceManager创建资源的一个新实例,并把它添加给自己。所有的资源都保存在ResourceManager里。
4. 这个资源同样也被插入到“有序载入列表(ordered loading list)”里。这样使资源按照指定的顺序载入,如果你想要一次性载入整个资源组的话。一个资源的载入顺序在它的ResourceManager中指定。
5. 目前,所有的Declared进入了Created阶段。
5. 我们完成初始化了。仍然没有资源被载入,但这是正常的,因为我们现在不使用。最后一步,从Created到Loaded,通过如下几种途径:
1. 使用资源。比如,创建一个需要指定mesh的实体。显然,一旦一个资源以这种方式载入,如果另一个实体也需要它则不必再次载入。如果这个资源目前还是Unknown状态的,则它将被创建并且完全载入。
2. 调用ResourceGroupManager::loadResourceGroup,所有Created资源将被载入。
3. 相应的ResourceManager的load方法被调用。这可以用在载入那些还没有创建的资源,因为如果需要的话,它会自动地为你创建。
4. 通过获取资源的指针,然后调用它的load方法,则资源将直接载入。当然,只有资源处于Created状态下,你才能这么做。
5. 好了,被载入的资源可以直接使用了。
注意:如果你创建了自定义的资源管理器,你必须在手动声明资源之前初始化它们,否则可能会找不到它们的当中一些。
在你的程序里,这通常表现为如下的事件:
1. 创建Root对象。
2. 重复调用ResourceGroupManager::addResourceLocation,直到你已经添加了所有的资源位置。
3. 创建所有自定义的ResourceManager对象,并通过调用ResourceGroupManager::_registerResourceManager注册它们。你可能还要注册ScriptLoader对象,通过调用ResourceGroupManager::_registerScriptLoader方法。
4. 手动声明你要的任何资源,通过ResourceGroupManager::declareResource函数。
5. 为你的资源组调用适当的初始化方法。对于单个组,调用ResourceGroupManager::initialiseResourceGroup,或者ResourceGroupManager::initialiseAllResourceGroups一口气初始化所有的。
资源卸载和销毁
ResourceManager::unload将一个资源从Loaded状态返回到Created。
想要完全移除资源,调用ResourceManager::remove。这样不论资源处于何种状态,都将打回Unknown状态。你可以通过ResourceManager::getByName来获取资源的指针,卸载或者移动它,如果你想这样做的话。
当一个资源管理器销毁时,所有存在的资源都会被移除。
重新载入资源(reload)
重新载入资源在编辑器里是非常有用的。本质上来说,资源先被卸载,然后再被载入。它从Loaded状态转到Created,然后再回到Loaded状态。资源必须处于Loaded状态,才能重新载入。
ResourceManager::reloadAll重新载入某一类型的所有资源。单个资源可以通过Resource::reload重新载入。
创建一个新的资源,并交付给资源管理器
现在我们知道了OGRE的资源系统的工作方式,创建一个新的资源类型实际上是非常简单的。你的程序几乎肯定要用到其它的资源,比如声音文件、XML或仅仅是普通文本。在这个例子里,我们将创建一个简单的文本装载器。这些代码都经过优美的划分,并且很容易扩展到其它的文件类型
-- 唯一需要改变的Resource::load方法以及TextFile资源的访问载入数据的公共接口。
有两种需要告诫的:脚本资源和手动资源装载器。这个例子也不会使用它们,但将作一些解释。
首先要创建的文件是TextFile.h。它声明了我们的资源,TextFile,并为它创建了一个共享指针的实现。如下:
#ifndef __TEXTFILE_H__
#define __TEXTFILE_H__
#include <OgreResourceManager.h>
class TextFile : public Ogre::Resource
{
Ogre::String mString;
protected:
// must implement these from the Ogre::Resource interface
void loadImpl();
void unloadImpl();
size_t calculateSize() const;
public:
TextFile(Ogre::ResourceManager *creator, const Ogre::String &name,
Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false,
Ogre::ManualResourceLoader *loader = 0);
virtual ~TextFile();
void setString(const Ogre::String &str);
const Ogre::String &getString() const;
};
class TextFilePtr : public Ogre::SharedPtr<TextFile>
{
public:
TextFilePtr() : Ogre::SharedPtr<TextFile>() {}
explicit TextFilePtr(TextFile *rep) : Ogre::SharedPtr<TextFile>(rep) {}
TextFilePtr(const TextFilePtr &r) : Ogre::SharedPtr<TextFile>(r) {}
TextFilePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr<TextFile>()
{
// lock & copy other mutex pointer
OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
pRep = static_cast<TextFile*>(r.getPointer());
pUseCount = r.useCountPointer();
if (pUseCount)
{
++(*pUseCount);
}
}
/// Operator used to convert a ResourcePtr to a TextFilePtr
TextFilePtr& operator=(const Ogre::ResourcePtr& r)
{
if (pRep == static_cast<TextFile*>(r.getPointer()))
return *this;
release();
// lock & copy other mutex pointer
OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
pRep = static_cast<TextFile*>(r.getPointer());
pUseCount = r.useCountPointer();
if (pUseCount)
{
++(*pUseCount);
}
return *this;
}
};
#endif
这有一个相应的cpp文件。我们使用一个简单string来保存数据,由此不需要特殊的初始化。如果你使用更复杂的对象,它们必须要正确地初始化。
#include "TextFile.h"
#include "TextFileSerializer.h"
TextFile::TextFile(Ogre::ResourceManager* creator, const Ogre::String &name,
Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual,
Ogre::ManualResourceLoader *loader) :
Ogre::Resource(creator, name, handle, group, isManual, loader)
{
/* If you were storing a pointer to an object, then you would set that pointer to NULL here.
*/
/* For consistency with StringInterface, but we don't add any parameters here
That's because the Resource implementation of StringInterface is to
list all the options that need to be set before loading, of which
we have none as such. Full details can be set through scripts.
*/
createParamDictionary("TextFile");
}
TextFile::~TextFile()
{
unload();
}
// farm out to TextFileSerializer
void TextFile::loadImpl()
{
TextFileSerializer serializer;
Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(mName, mGroup, true, this);
serializer.importTextFile(stream, this);
}
void TextFile::unloadImpl()
{
/* If you were storing a pointer to an object, then you would check the pointer here,
and if it is not NULL, you would destruct the object and set its pointer to NULL again.
*/
mString.clear();
}
size_t TextFile::calculateSize() const
{
return mString.length();
}
void TextFile::setString(const Ogre::String &str)
{
mString = str;
}
const Ogre::String &TextFile::getString() const
{
return mString;
}
你可能注意到了在包含文件里有一个"TextFileSerializer"的引用。它是一个执行实际载入的工具类。它并不重要,尤其对于这种简单的资源,但它让我们序列化一个对象,而不用对Resource封装,尽管我们想这么做。基类Serializer包含了很多有用的工具函数。我们不使用它们,但会继承它们。
TextFileSerializer.h:
#ifndef __TEXTSERIALIZER_H__
#define __TEXTSERIALIZER_H__
#include <OgreSerializer.h>
class TextFile; // forward declaration
class TextFileSerializer : public Ogre::Serializer
{
public:
TextFileSerializer();
virtual ~TextFileSerializer();
void exportTextFile(const TextFile *pText, const Ogre::String &fileName);
void importTextFile(Ogre::DataStreamPtr &stream, TextFile *pDest);
};
#endif
TextFileSerializer.cpp:
#include "TextFileSerializer.h"
#include "TextFile.h"
TextFileSerializer::TextFileSerializer()
{
}
TextFileSerializer::~TextFileSerializer()
{
}
void TextFileSerializer::exportTextFile(const TextFile *pText, const Ogre::String &fileName)
{
std::ofstream outFile;
outFile.open(fileName.c_str(), std::ios::out);
outFile << pText->getString();
outFile.close();
}
void TextFileSerializer::importTextFile(Ogre::DataStreamPtr &stream, TextFile *pDest)
{
pDest->setString(stream->getAsString());
}
最后我们要写的类当然是TextFileManager。
TextFileManager.h:
#ifndef __TEXTFILEMANAGER_H__
#define __TEXTFILEMANAGER_H__
#include <OgreResourceManager.h>
#include "TextFile.h"
class TextFileManager : public Ogre::ResourceManager, public Ogre::Singleton<TextFileManager>
{
protected:
// must implement this from ResourceManager's interface
Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
const Ogre::NameValuePairList *createParams);
public:
TextFileManager();
virtual ~TextFileManager();
virtual TextFilePtr load(const Ogre::String &name, const Ogre::String &group);
static TextFileManager &getSingleton();
static TextFileManager *getSingletonPtr();
};
#endif
最后,TextFileManager.cpp
#include "TextFileManager.h"
template<> TextFileManager *Ogre::Singleton<TextFileManager>::ms_Singleton = 0;
TextFileManager *TextFileManager::getSingletonPtr()
{
return ms_Singleton;
}
TextFileManager &TextFileManager::getSingleton()
{
assert(ms_Singleton);
return(*ms_Singleton);
}
TextFileManager::TextFileManager()
{
mResourceType = "TextFile";
// low, because it will likely reference other resources
mLoadOrder = 30.0f;
// this is how we register the ResourceManager with OGRE
Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
}
TextFileManager::~TextFileManager()
{
// and this is how we unregister it
Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
}
TextFilePtr TextFileManager::load(const Ogre::String &name, const Ogre::String &group)
{
TextFilePtr textf = getByName(name);
if (textf.isNull())
textf = create(name, group);
textf->load();
return textf;
}
Ogre::Resource *TextFileManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
const Ogre::NameValuePairList *createParams)
{
return new TextFile(this, name, handle, group, isManual, loader);
}
脚本装载器(ScriptLoader)
有些资源比如材质,是从脚本载入的。在这种情况下,需要ResourceManager从ScriptLoader继承,然后在构造器里设置它的“脚本模式”--
视为脚本的文件类型(*.material,
*.compositor等)。再调用ResourceGroupManager::_registerScriptLoader来把自己注册成一个载入脚本的资源管理器。最后,调用ResourceGroupManager::initialiseResourceGroup时,所有注册了的脚本文件都将被解析。
如果你想要写一个载入脚本的资源管理器,就要从ScriptLoader继承,并实现parseScript方法。当执行ResourceGroupManager::initialiseResourceGroup时,要调用这个parseScript,在那里你应该声明所有你想创建的资源。
手动资源装载器(ManualResourceLoader)
当通过ResourceGroupManager::declareResource声明一个资源时,有一个可选的ManualResourceLoader。对于不是从文%E