OGRE Wiki
Support and community documentation for Ogre3D
Ogre Forums
ogre3d.org
Log in
Username:
Password:
CapsLock is on.
Remember me (for 1 year)
Log in
Home
Tutorials
Tutorials Home
Basic Tutorials
Intermediate Tutorials
Mad Marx Tutorials
In Depth Tutorials
Older Tutorials
External Tutorials
Cookbook
Cookbook Home
CodeBank
Snippets
Experiences
Ogre Articles
Libraries
Libraries Home
Alternative Languages
Assembling A Toolset
Development Tools
OGRE Libraries
List of Libraries
Tools
Tools Home
DCC Tools
DCC Tutorials
DCC Articles
DCC Resources
Assembling a production pipeline
Development
Development Home
Roadmap
Building Ogre
Installing the Ogre SDK
Setting Up An Application
Ogre Wiki Tutorial Framework
Frequently Asked Questions
Google Summer Of Code
Help Requested
Ogre Core Articles
Community
Community Home
Projects Using Ogre
Recommended Reading
Contractors
Wiki
Immediate Wiki Tasklist
Wiki Ideas
Wiki Guidelines
Article Writing Guidelines
Wiki Styles
Wiki Page Tracker
Ogre Wiki Help
Ogre Wiki Help Overview
Help - Basic Syntax
Help - Images
Help - Pages and Structures
Help - Wiki Plugins
Toolbox
Freetags
Categories
List Pages
Structures
Trackers
Statistics
Rankings
List Galleries
Ogre Lexicon
Comments
History: Resources and ResourceManagers
View page
Source of version: 18
(current)
{DIV(class="achtung")}__IMPORTANT:__ The information here is outdated. For new projects, rather see [https://ogrecave.github.io/ogre/api/latest/_resource-_management.html|The according manual section].{DIV} 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. %tutorialhelp% {maketoc} !!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. !!!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. Resources do not take much space until they are loaded, therefore there is no harm in adding all of your resources to the resource manager first and then loading/unloading them as needed in your application. !!!Resource Creation, from the very beginning # OGRE's native ResourceManagers are created in Root::Root. # 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: ## Creates the specified ResourceGroup if this hasn't been done already. ## Creates a new Archive instance of the type specified ## Creates a new ResourceLocation, adds the Archive to it, and then adds the ResourceLocation to the ResourceGroup. ## 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. # The next step is to manually declare resources. No resources have been declared as of yet, though many of them will be 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__. # 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. # 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: # Create the Root object. # Call ResourceGroupManager::addResourceLocation repeatedly until you have added all resource locations. # 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. # Manually declare any resources you require with the ResourceGroupManager::declareResource function. # 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. !!!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. !!!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 !!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: {CODE(wrap="1", colors="c++")} #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>() { if( r.isNull() ) return; // 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(); useFreeMethod = r.freeMethod(); 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(); if( r.isNull() ) return *this; // resource ptr is null, so the call to release above has done all we need to do. // 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(); useFreeMethod = r.freeMethod(); if (pUseCount) { ++(*pUseCount); } return *this; } }; #endif {CODE} 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. {CODE(wrap="1", colors="c++")} #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; } {CODE} 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 we'll subclass from it anyway. TextFileSerializer.h: {CODE(wrap="1", colors="c++")} #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 {CODE} TextFileSerializer.cpp: {CODE(wrap="1", colors="c++")} #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()); } {CODE} The last class we need to write is of course the TextFileManager. TextFileManager.h: {CODE(wrap="1", colors="c++")} #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 {CODE} And finally, TextFileManager.cpp {CODE(wrap="1", colors="c++")} #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); } {CODE} !!!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. !!!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: {CODE(wrap="1", colors="c++")} // 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"); } }; {CODE} The TextFile is then declared as follows: {CODE(wrap="1", colors="c++")} // Do not add this to the project ManualTextFileLoader *mtfl = new ManualTextFileLoader; Ogre::ResourceGroupManager::getSingleton ().declareResource("hello.txt", "TextFile", "General", mtfl); {CODE} !!!Usage To use our new resource manager, we create an instance ''before Root::initialise or any ResourceGroups have been initialized''. {CODE(wrap="1", colors="c++")} TextFileManager *tfm = new TextFileManager(); {CODE} 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: {CODE(wrap="1", colors="c++")} delete Ogre::ResourceGroupManager::getSingleton()._getResourceManager("TextFile"); {CODE} 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: {CODE(wrap="1", colors="c++")} Hello world! {CODE} 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: {CODE(wrap="1", colors="c++")} #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; } {CODE} 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. --- Alias: (alias(Advanced Tutorial 1)) Alias: (alias(Advanced_Tutorial_1))
Search by Tags
Search Wiki by Freetags
Latest Changes
IDE Eclipse
FMOD SoundManager
HDRlib
Building Ogre V2 with CMake
Ogre 2.1 FAQ
Minimal Ogre Collision
Artifex Terra
OpenMB
Advanced Mogre Framework
MogreSocks
...more
Search
Find
Advanced
Search Help
Online Users
101 online users