Advanced Tutorial 1
From PyWiki
Advanced Tutorial 1 From Ogre Wiki Jump to: navigation, search
Advanced Tutorial 1: Resources and ResourceManagers
This first part of this article will outline in detail the process by which resources are loaded, unloaded, reloaded and destroyed. In the second half, we will create a new resource type, and a manager to go with it.
Please, use this forum thread to discuss problems, suggestions, etc. Contents [hide]
* 1 The Lifecycle of a Resource
o 1.1 Terminology
o 1.2 Resource Creation, from the very beginning
o 1.3 Resource Unloading and Destruction
o 1.4 Reloading Resources
* 2 Creating a new Resource type, and the accompanying ResourceManager
o 2.1 ScriptLoader
o 2.2 ManualResourceLoader
o 2.3 Usage
[edit] The Lifecycle of a Resource
The OGRE API documentation explains the basic lifecycle of a resource, but is slightly washy with some of the concepts. Hopefully, things will be made clearer here. [edit] Terminology
The following terms will be used to distinguish the various stages of loading that a resource may be in:
Unknown: OGRE is not aware of the resource. Its filename is stored in a ResourceGroup, but OGRE has no idea what to do with it. Declared: The resource has been flagged for creation, either directly or as a side-effect of some other action. Ogre knows what type of resource it is, and what to do with it when the time comes to create it. Created: OGRE has created an empty instance of the resource, and added it to the relevant ResourceManager. Loaded: The created instance has been fully loaded, and the resource's full data now resides in memory. This is typically the stage at which the resource's file is actually accessed. You do not want to access the file in the Creation stage. [edit] Resource Creation, from the very beginning
1. OGRE's native ResourceManagers are created in Root::Root.
2. The first thing that needs to be done is to specify resource locations.
This is done by calling ResourceGroupManager::addResourceLocation. This function does
several things:
1. Creates the specified ResourceGroup if this hasn't been done already.
2. Creates a new Archive instance of the type specified
3. Creates a new ResourceLocation, adds the Archive to it, and then adds the
ResourceLocation to the ResourceGroup.
4. The final step is to get a list of all the files in the Archive, and add them to a
list in the ResourceGroup. After this step has completed, resources are in the
Unknown stage.
3. The next step is to manually declare resources. At this point no resources are declared,
though many of them will be declared when the ResourceManager starts parsing scripts. If
you wish to manually declare resources, call the ResourceGroupManager::declareResource
function. At this point, any resources that have been manually declared are in the
Declared stage. The rest are still Unknown.
4. Next the ResourceGroups are initialized. This is done with
ResourceGroupManager::initialiseResourceGroup or
ResourceGroupManager::initialiseAllResourceGroups, the latter just calling the former for
all ResourceGroups. This does the following:
* Parses any scripts in the ResourceGroup. Scripts are defined by ResourceManagers
which inherit from ScriptLoader. This may cause some resources to be Declared.
* Creates any Declared resources.
* The relevant ResourceManager creates a new instance of the resource, and adds it to
itself. All resources are stored in ResourceManagers.
* The resource is also inserted into an "ordered loading list". This allows resources
to be loaded in a specific order, if you want to load an entire ResourceGroup at
once. The load order for a resource is specified by its ResourceManager.
* At this point, any Declared resources are now in the Created stage.
5. Finally, we're done with initialization. No resources are loaded yet, but that's fine,
because we're not using any. The final step - the transition from Created to Loaded - can
come about in the following ways:
* The resource was used. For example, an Entity was created which needed a specific
mesh. Obviously, once a resource has been loaded in this way, it will not be loaded
again if another Entity needs it. If the resource is currently in the Unknown
state, it will be created and fully loaded.
* ResourceGroupManager::loadResourceGroup is called - any Created resources are
loaded.
* The relevant ResourceManager's load method is called. This can be used to load
resources which have not yet been Created, because it will automatically Create
them for you first, if necessary.
* The resource is loaded directly, by obtaining a pointer to it, and calling its load
method. You can only do this if the resource is in the Created stage, of course.
* At this point, Loaded resources are ready to use straight away.
Note: If you have created any custom ResourceManagers, you must initialize them before manually declaring resources or some of them may not be found.
In your application, this generally boils down to the following sequence of events:
1. Create the Root object.
2. Call ResourceGroupManager::addResourceLocation repeatedly until you have added all
resource locations.
3. Create any custom ResourceManager objects you have made and register them using
ResourceGroupManager::_registerResourceManager. You should also register any ScriptLoader
objects with the ResourceGroupManager::_registerScriptLoader function.
4. Manually declare any resources you require with the ResourceGroupManager::declareResource
function.
5. Call the appropriate initialization function for your resource groups. Either use
ResourceGroupManager::initialiseResourceGroup for a single group or call
ResourceGroupManager::initialiseAllResourceGroups to initialize everything at once.
[edit] Resource Unloading and Destruction
* ResourceManager::unload reverts a resource from Loaded to Created. * To completely remove a resource, call ResourceManager::remove. This returns the resource all the way back to the Unknown stage, from whichever stage it was in previously. You can get a pointer to the resource with ResourceManager::getByName and unload or remove it manually, if you wish. * Any existing resources are removed when a ResourceManager is destructed.
[edit] Reloading Resources
Reloading resources is a very useful feature for editors. Essentially, the resource is unloaded, and then loaded. It moves from Loaded, to Created and then back to Loaded again. Resources must be in the Loaded stage to be reloaded.
* ResourceManager::reloadAll reloads all resources of one type. * Resources can be individually reloaded with Resource::reload
[edit] Creating a new Resource type, and the accompanying ResourceManager
Now that we know how OGRE's resource system works, creating a new resource type is actually pretty easy. Your application will almost certainly use extra resources, be they sound files, XML or just plain text. In this example, we'll create a simple text file loader. The code is neatly compartmentalized, and easy to extend to any file type - the only thing that will need to change is the Resource::load method, and the TextFile resource's public interface to access the data we have loaded.
There are two caveats to this: script resources, and manual resource loaders. This example will use neither, but they will be explained.
The first file to create is TextFile.h. This declares our resource, TextFile, and creates a shared pointer implementation for it. This is what it looks like:
- 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
Here is the accompanying .cpp file. We are using a simple string to store our data, and as such it needs no special initialisation. If you are using more complex objects, they must be initialised appropriately.
- 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;
}
You will have noticed the reference to "TextFileSerializer" in the includes. This is a helper class which does the actual loading. It is not vital, especially for a resource this simple, but it allows us to serialize an object without having to wrap a Resource around it, should we want to. The Serializer base class contains lots of useful utility functions. We won't use them, but will subclass from it anyway.
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());
}
The last class we need to write is of course the 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
And finally, 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);
}
[edit] ScriptLoader
Some resources, such as materials, are loaded from scripts, and in these cases, the ResourceManager will derive from ScriptLoader. It will then set up its 'script patterns' - the filetypes that it considers as scripts (*.material, *.compositor, etc) - in the constructor. It will also call ResourceGroupManager::_registerScriptLoader at this point to register itself as a script loading ResourceManager. Later, when ResourceGroupManager::initialiseResourceGroup is called, any registered script files will be parsed.
If you wish to write a script-loading ResourceManager, you will need to derive from ScriptLoader, and implement the parseScript method. It is parseScript that gets called on the resources during ResourceGroupManager::initialiseResourceGroup, and this is where you should declare any resources that you want created. [edit] ManualResourceLoader
When a resource is declared with ResourceGroupManager::declareResource, it may be given an optional ManualResourceLoader. ManualResourceLoaders can be used for resources which are not loaded from file - they may be created programmatically. Here is a simple example, using TextFile:
// Do not add this to the project class ManualTextFileLoader : public Ogre::ManualResourceLoader { public:
ManualTextFileLoader() {}
virtual ~ManualTextFileLoader() {}
void loadResource(Ogre::Resource *resource)
{
TextFile *tf = static_cast<TextFile *>(resource);
tf->setString("manually loaded");
}
};
The TextFile is then declared as follows:
// Do not add this to the project ManualTextFileLoader *mtfl = new ManualTextFileLoader; Ogre::ResourceGroupManager::getSingleton ().declareResource("hello.txt", "TextFile", "General", mtfl);
[edit] Usage
To use our new resource manager, we create an instance before Root::initialise or any ResourceGroups have been initialized.
TextFileManager *tfm = new TextFileManager();
And we destruct it when we shutdown - before we destroy the Ogre::Root object, of course. OGRE does not destruct it itself, it's your responsibility:
delete Ogre::ResourceGroupManager::getSingleton()._getResourceManager("TextFile");
The last thing we will do is look at an example of what we have created in action. Create a file called "hello.txt" and place it in a media directory where Ogre can find it. Since there really isn't a location for custom scripts, I suggest putting the file in "media/materials/scripts". Add the following to that text file:
Hello world!
Now create a file called main.cpp and add the following code to it. Be sure to read through the createScene function to see some of the things we can do with this:
- include <ExampleApplication.h>
- include "TextFileManager.h"
- include "TextFileSerializer.h"
class TutorialApplication : public ExampleApplication { private:
TextFileManager *mTFM;
public:
TutorialApplication()
: mTFM(0)
{
}
~TutorialApplication()
{
delete mTFM;
}
void setupResources()
{
mTFM = new TextFileManager();
// hello.txt will be created when initialiseResourceGroup is called
ResourceGroupManager::getSingleton().declareResource("hello.txt", "TextFile");
ExampleApplication::setupResources();
}
void createScene(void)
{
// Load the file, get the data
TextFilePtr textfile = mTFM->load("hello.txt", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
String str = textfile->getString();
// Reload the file
textfile->reload();
// export the file
TextFileSerializer serializer;
serializer.exportTextFile(static_cast<TextFile *>(textfile.getPointer()), "hello.out.txt");
// unload/remove the file
mTFM->unload("hello.txt");
mTFM->remove("hello.txt");
}
};
- if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- define WIN32_LEAN_AND_MEAN
- include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
- else
int main(int argc, char **argv)
- endif
{
// Create application object TutorialApplication app;
try {
app.go();
} catch(Exception& e) {
- if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
- else
fprintf(stderr, "An exception has occurred: %s\n",
e.getFullDescription().c_str());
- endif
}
return 0;
}
If you step through this function with a debugger, you will see that see that calling getString will indeed return the contents of hello.txt. While this may not be the most useful thing in its current state, you can easily expand this to create your own resources and resource loaders.
Retrieved from "http://www.ogre3d.org/wiki/index.php/Advanced_Tutorial_1"
| Python-Ogre Tutorials |
|---|
|
Python-Ogre Beginner Tutorials: Beginner 1 - Beginner 2 - Beginner 3 - Beginner 4 - Beginner 5 - Beginner 6 - Beginner 7 - Beginner 8 Intermediate Tutorials: Intermediate 1 - Intermediate 2 - Intermediate 3 - Intermediate 4 - Intermediate 5 - Intermediate 6 Advanced Tutorials: Advanced 1 See also: Artist Tutorials - Ogre Articles - Cookbook |
