Sign in to follow this  

Resource manager for open world game

This topic is 1069 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm making an open world (outdoors) cross-platform 3D game with C++ and trying to rework some of the fundamental parts of the code. For loading resources, I have thus far had pretty specialized routines for different type of resources. I would like to unify and improve this part of the code.
 
As I've learned, there are many approached to implementing resource systems and each game has its own requirements. Below I try to list my requirements and issues.
 
TYPES OF RESOURCES
 
The types of resources my game will have:
- texture
- shader
- vertex array object (VAO)
- sound
- skeletal animation
- particle system
 
Those are pretty self-contained resources. But some resources
will depend on others, such as:
- material -> texture,shader
- mesh -> material, VAO
 
I also get the feeling that VAO is a special type of resource in that it is not referenced by name and not declared outside the execution of the game. Perhaps it deserves a dedicated manager.
 
WHEN TO LOAD
 
The world can become quite large, so it would be essential to support loading and discarding resources during gameplay. I think that mostly this applies to textures, as they usually take the most memory. Controlling which resources are loaded and when may not necessarily be the responsibility of resource managers, but they would need no provide an interface for it.
 
I'm thinking of dividing the world into imaginary rectangular sectors. Only resources required by, say, 9 sectors closest to camera need to be in memory. The engine could keep track of this and ask managers to discard/load resources as camera crosses sector boundaries.
 
SMART POINTERS OR HANDLES
 
I've been learning C++11 and am interested in leveraging its functionalities. I've given thought for smart pointers, but their use for resource managers gives mixed feelings. I think that usually resource managers do not take ownership of resources, but only delegate them to users. So one approach would be to store weak pointers in the manager and deliver shared pointers. However, it is not clear whether a resource should be destroyed immediately after it loses its last user. For example, one could define quotas for resource memory use and only after the quota is reached, are some resources destroyed. I guess that handles would prove more flexible for this type of system, but with handles, the manager usually owns the data (not that this is a drawback by itself).
 
Another advantage of handles is that if a resource is requested, it may not be immediately available or may not even be found in some error situation. It would be nice to be able to handle this gracefully, i.e., the handle would temporarily correspond to some dummy resource.
 
RESOURCE HEADERS
 
All resources (except VAO) are defined in text files (XML). Each resource is an XML element with name attribute. The element may have child elements that describe properties of the resource, such as filename, sound volume, animation frame rate, etc. I would think that this data could be read into memory at startup for all resources. A given resource would then have its header in memory and its status would be "not loaded", until the resource is requested to be loaded (a handle is requested).
 
I also want to be able to modify resource parameters in-game. Thus I have written a template class CVar, which is given some type and it can be easily read from XML and can be fetched for an in-game menu system for inspection and modification. A group of CVar objects (CVarGroup) is then given for each resource.
 
I could just store resources of particular type in a vector, which is fully populated at init from the XML file. Then an index to the resource would remain well-defined over the course of the game. A handle would then contain this index, which could point to some dummy data if the resource is not yet loaded.
 
TYPE SAFE STORAGE
 
A major problem I have not figured out yet is how to store the various types of resources in a convenient way. I could make a templated resource manager, that stores a vector of given type. But then I would need to have a separate instance of the manager for each type of resource. Also, as shown above, some resources need to invoke other resource managers, which could then be difficult (loading mesh entails loading material, which then loads textures).
 
On the other hand, I could write a resource manager, which only stores pointers to some resource base class. But in this approach, it seems that dynamic typecasts would have to be performed in hot rendering code. However, this approach could give rise to a really nice interface, such as
 
Handle<Mesh> mesh = manager.get<Mesh>("house");
 
where only a single manager object is used to get all types of resources.
 
A related matter is cache utilization. Is it worth the trouble to try to store the resources contiguously in memory or just as pointers? I would think that, e.g., when rendering meshes in game, it is not likely that mesh data required to draw visible mesh objects would be within a cache line anyway in a complex world.
 
SORTING BY RESOURCES
 
The resource should store some integer that can be use to contruct a sorting key for rendering. For example, meshes should be sorted by shader, material and VAO, so each resource should provide an integer. In the best case, these integer types would be only a few bits long so that they could be combined into one integer type. This should not be a major problem, though. If resources are stored in vector, then the vector index may serve as such integer.
 
MY ATTEMPT
 
I have written some preliminary code, which is based on handles and the approach that each resource has its own manager. Some methods are just there without implementation to sketch it out. I tried to solve the resource manager interdependency by dependency injection, which is illustrated with barebones implementations of material and mesh resources. Please find the code at
 
 
I'm always trying to avoid the singleton pattern so that the managers are objects in some world class.
 
I'm interested in any kind of feedback to improve my approach. Hopefully my goal is not too vaguely stated.

Share this post


Link to post
Share on other sites

You need to create a manager for each type of resource. It can be your compiled shader, shader, model, mesh, render target, etc.

The Single Responsibility of the manager is to avoid loading the resource multiple times. You can also put some kind of logic responsability to the class, but it must be related only with the type of resource you're managing

 

Eg.:

CTexture* ptTexture = CTextureManager::CreateTexture( const CString& _sFilePath ) ->There is a texture? Yes: return it. No: create it.

 

The CTextureManager is the most simple example of a resource manager, but in order to work it must be linked with a "CImageManager" and the "CGraphicsManager" to get all pixel valures and upload the data to GPU.

 

As a good advice you can search on the forums: Game Engine Architeture; after that you can go directly to what you need - basically the  components of the engine - do using the forums.

Edited by Irlan

Share this post


Link to post
Share on other sites


You need to create a manager for each type of resource. It can be your compiled shader, shader, model, mesh, render target, etc.

The Single Responsibility of the manager is to avoid loading the resource multiple times. You can also put some kind of logic responsability to the class, but it must be related only with the type of resource you're managing.

 

I'm not sure I follow. Preventing the loading of resources multiple times is not by itself a sufficient reason to make a separate manager for each type of resource. The resource identifier (hash) can contain the type of resource. The main reason I see for having separate managers is to avoid costly dynamic type casting in hot code.

 

What sort of logic are you talking about? Perhaps that each resource may have totally different loading procedure? In this case the loading functionality could be a virtual function in a resource base class. I'm not sure if this is the way to go, i.e., to let resources load themselves. In my initial code, I let the resource managers load the resource so that the resources themselves have less responsibility.

 


Eg.:

CTexture* ptTexture = CTextureManager::CreateTexture( const CString& _sFilePath ) ->There is a texture? Yes: return it. No: create it.

 

If CTexture is the actual data, I would like to avoid this type of approach. This is pretty close to what I already have in my game engine, although I try to avoid static methods and instead use manager objects, which are instantiated in the engine class.

 

The problem here is that if the manager decides to destroy the texture or move it in memory for some optimization, the user of the resource has a dangling pointer, which will lead to access violation. Also, if using data streaming, the data may not be immediately available. That's why I would prefer handles.

 


The CTextureManager is the most simple example of a resource manager, but in order to work it must be linked with a "CImageManager" and the "CGraphicsManager" to get all pixel valures and upload the data to GPU.

 

Because your CTextureManager depends on other managers, I would consider it "not simple" (if we neglect the actual code for loading various types of resource). I think I will make my texture manager (OpenGL textures) independent of any other manager. Any pixel manipulations can be done with plain old functions and I don't need to store the pixel data in RAM except during loading phase.

 


As a good advice you can search on the forums: Game Engine Architeture; after that you can go directly to what you need - basically the components of the engine - do using the forums.

 

I'm not exactly in the beginning of making a game engine. I have one running, but I want to consider rewriting many parts of it now that I have more clear picture of what I need for my game. I think that in my engine, the two things that require most abstraction are resource managers and entities. Other pieces of the engine are rather self-contained and usualy lead to only a single instantiation (e.g. terrain, special effects, shadowmap system). In my current game engine, I don't have a unified framework for resource managers and entities. I have decided to go with an existing ECS code with the latter, so now I'm focusing on the resource manager problem.

 

I did google around with "game resource/asset manager c++" before this thread. Most discussions are about plain pointer/smart pointer/handle aspect. This is one part of the topic and I'm starting to like the idea of handles, even though I'm currently keen on using smart pointers (maybe without proper judgement). Also the storage of varying types has been discussed and often some boost class, such as boost::any, is suggested. I'm not sure if I want to introduce boost dependency to my project without proper cause. My understanding is that even if using boost::any, dynamic type casting will be necessary.

 

For handles, the article http://scottbilas.com/publications/gem-resmgr/ is cited a lot. It's a good text on handles, but I'm not sure that the sample code on actual resource management is what I'm looking for.

Share this post


Link to post
Share on other sites

 


You need to create a manager for each type of resource. It can be your compiled shader, shader, model, mesh, render target, etc.

The Single Responsibility of the manager is to avoid loading the resource multiple times. You can also put some kind of logic responsability to the class, but it must be related only with the type of resource you're managing.

 

I'm not sure I follow. Preventing the loading of resources multiple times is not by itself a sufficient reason to make a separate manager for each type of resource. The resource identifier (hash) can contain the type of resource. The main reason I see for having separate managers is to avoid costly dynamic type casting in hot code.

 

What sort of logic are you talking about? Perhaps that each resource may have totally different loading procedure? In this case the loading functionality could be a virtual function in a resource base class. I'm not sure if this is the way to go, i.e., to let resources load themselves. In my initial code, I let the resource managers load the resource so that the resources themselves have less responsibility.

 

 

 


Eg.:

CTexture* ptTexture = CTextureManager::CreateTexture( const CString& _sFilePath ) ->There is a texture? Yes: return it. No: create it.

 

If CTexture is the actual data, I would like to avoid this type of approach. This is pretty close to what I already have in my game engine, although I try to avoid static methods and instead use manager objects, which are instantiated in the engine class.

 

The problem here is that if the manager decides to destroy the texture or move it in memory for some optimization, the user of the resource has a dangling pointer, which will lead to access violation. Also, if using data streaming, the data may not be immediately available. That's why I would prefer handles.

 

 

 


The CTextureManager is the most simple example of a resource manager, but in order to work it must be linked with a "CImageManager" and the "CGraphicsManager" to get all pixel valures and upload the data to GPU.

 

Because your CTextureManager depends on other managers, I would consider it "not simple" (if we neglect the actual code for loading various types of resource). I think I will make my texture manager (OpenGL textures) independent of any other manager. Any pixel manipulations can be done with plain old functions and I don't need to store the pixel data in RAM except during loading phase.

 

 

 


As a good advice you can search on the forums: Game Engine Architeture; after that you can go directly to what you need - basically the components of the engine - do using the forums.

 

I'm not exactly in the beginning of making a game engine. I have one running, but I want to consider rewriting many parts of it now that I have more clear picture of what I need for my game. I think that in my engine, the two things that require most abstraction are resource managers and entities. Other pieces of the engine are rather self-contained and usualy lead to only a single instantiation (e.g. terrain, special effects, shadowmap system). In my current game engine, I don't have a unified framework for resource managers and entities. I have decided to go with an existing ECS code with the latter, so now I'm focusing on the resource manager problem.

 

I did google around with "game resource/asset manager c++" before this thread. Most discussions are about plain pointer/smart pointer/handle aspect. This is one part of the topic and I'm starting to like the idea of handles, even though I'm currently keen on using smart pointers (maybe without proper judgement). Also the storage of varying types has been discussed and often some boost class, such as boost::any, is suggested. I'm not sure if I want to introduce boost dependency to my project without proper cause. My understanding is that even if using boost::any, dynamic type casting will be necessary.

 

For handles, the article http://scottbilas.com/publications/gem-resmgr/ is cited a lot. It's a good text on handles, but I'm not sure that the sample code on actual resource management is what I'm looking for.

 

#A I'm just saying that at the very end you'll end with something like what I've said. It is better to start specializing your classes even if your implementation its very basic. Of course, you don't need to have a "CTexture2DManager" if you want; just a "CTextureManager" it's fine if you don't want over engineer. I am saying that it is easy to manage your resources when you have this kind of specific data. Make an std::map < std::string, CTexture* > and you're fine for your first implementation; don't write code more than once; use smart pointers or make an template that deletes all resource pointers of the std::map when necessary.

 

#B You can let resources load themselves because they're the only one that have information of itself. You can search on the forums. Having web acess and don't use for this type of mission it's a waste of time and I think that everyone is tired of answering this specific type of question. Many engines handle resource loading in many ways. You may want to define which way is better for you.

 

#C Most of the questions on the forums are very specific. They won't give "The Asset Manager" for you. What I would recommend is that you do whatever you want and focus on the low-level sub-systems of the engine.

 

Since you're saying that you want simple things, it is impossible to take the right path without having complexity added to the equation.

Edited by Irlan

Share this post


Link to post
Share on other sites

The general case manager I use for a lot of things works well.

 

You just need to add a reference count to each asset.

 

So something along the lines of.

public class AssetManager
{
private:
       Dictionary<Hash,AssetType> loaded_assets = new Dictionary<Hash,AssetType>();
 
public :
       Asset AddAsset(String name)
       {
              if (loaded_assets.Contains(name.Hash))
              {
                     loaded_assets[name.Hash].ReferenceCount++;
              }else{
                    loaded_assets.Add(new AssetType(name));
              }
              return loaded_assets[name.Hash].Asset;
        }
 
        void RemoveAsset(String name)
        {
                loaded_assets[name.Hash].ReferenceCount--;
                if (load_assets[name.Hash].ReferenceCount==0)
                {
                         loaded_assets.Remove(name.Hash);
                }
        }
 
}
Edited by Stainless

Share this post


Link to post
Share on other sites


#A I'm just saying that at the very end you'll end with something like what I've said. It is better to start specializing your classes even if your implementation its very basic. Of course, you don't need to have a "CTexture2DManager" if you want; just a "CTextureManager" it's fine if you don't want over engineer. I am saying that it is easy to manage your resources when you have this kind of specific data. Make an std::map < std::string, CTexture* > and you're fine for your first implementation. Don't write code more than once. Use smart pointers or make an template that deletes all resource pointers of the std::map when necessary.

 

Thanks for you input Irlan.

 

Like I said, I have an implementation already. I have done simple non-abstract resource managers before. Now I wan't to take the next step. I use "std::map < std::string, CTexture* >" type of approach, but have ended up writing similar hard coded approaches to the different managers. Now I'm asking advice for the various aspects of resource managers.

 


#B You can let resources load themselves because they're the only one that have information of itself. You can search on the forums. Having web acess and don't use for this type of mission it's a waste of time and I think that everyone is tired of answering this specific type of question. Many engines handle resource loading in many ways. You may want to define which way is better for you.

 

I would like to have some opinions about this type of aspects. Letting resource load themselves may appear reasonable at first sight, but I have my doubts, because they may not have all the information required to load themselves. For example, if mesh loads itself, it will have to known how to load the materials. I don't find it very natural that resources have access to other managers. This is similar to the question "should mesh objects draw themselves". Nowdays I'm convinced that the answer is no, because they should not even have sufficient information to perform such a task (access to framebuffers, VAO managers, relation to other mesh objects, etc.).

 


#C Most of the questions on the forums are very specific. They won't give "The Asset Manager" for you. What I would recommend is that you do whatever you want and focus on the low-level sub-systems of the engine.

 

I agree. My topic is broad, but I made an effort to specify my requirements. I'm not asking for "the asset manager", but I would like to hear some common pitfalls to be avoided and if there are some approaches that have worked in games similar to mine. Specifically, it would be nice to read about any potential pitfalls in the code I provided and whether it is even going in the right direction, considering the goals I listed.

 


Since you're saying that you want simple things, it is impossible to take the right path without having complexity added to the equation.

 

Well, I didn't exactly say this, but I do wan't to avoid completely over engineering.

Share this post


Link to post
Share on other sites

 

The general case manager I use for a lot of things works well.

 

You just need to add a reference count to each asset.

 

So something along the lines of.

public class AssetManager
{
private:
       Dictionary<Hash,AssetType> loaded_assets = new Dictionary<Hash,AssetType>();
 
public :
       Asset AddAsset(String name)
       {
              if (loaded_assets.Contains(name.Hash))
              {
                     loaded_assets[name.Hash].ReferenceCount++;
              }else{
                    loaded_assets.Add(new AssetType(name));
              }
              return loaded_assets[name.Hash].Asset;
        }
 
        void RemoveAsset(String name)
        {
                loaded_assets[name.Hash].ReferenceCount--;
                if (load_assets[name.Hash].ReferenceCount==0)
                {
                         loaded_assets.Remove(name.Hash);
                }
        }
 
}

 

Thanks!

 

Implementing reference counting is one part of a manager, but perhaps c++11 smart pointers would do a better job if one is only worried about this aspect. I guess that your manager is only supposed to add reference counting on top of dictionary. Some further remarks:

 

- Your code clearly takes the path that there'll be one manager per resource type. That's already a major desicion. I would like to hear more experiences whether this tends to work or not in games that have many types of resources.

 

- Your manager directly returns the asset data, not a handle. Another big desicion, which I think won't work well if the manager decides to move that data in memory or the data is destroyed and a dangling pointer remains.

 

- There seems to be no way to actually load the resource. Or is the idea that after invoking AddAsset(), the asset is loaded? There would have to be some way then to know if it was already loaded.

 

- It does not seem robust that to remove a resource, one has to call a method of the manager. If some game object using a resource is destroyed, it will have to signal the manager that the resource is not used anymore. Injecting manager dependency to game objects does not seem natural.

Share this post


Link to post
Share on other sites

#A You're welcome.

 

#B There are basic two ways of letting this not happening. First: you can have pointers to different dependencies; you need to connect the "CImageManager" to the "CTextureManager". Second: if you're using the static classes approach (not the singleton one) you can just include your header file in the class if you want to make things "simple". The same applies to the "CModelManager". The "CTextureManager" should be connected to it. The Factory Pattern approach it is a good way of creating resources that are inherited. But it is just an example; a good way of saying I'll give  you an ID and filepath and you can return a resource for me.

 

#C I know  what you're saying. Since you know that you're not mixing responsibilities - you have to have that cognitive ability of the classes responsibility - you can do whatever you want to load the resources. But if you're talking about optimizations, it is impossible to answer because we don't know the overall architeture of your engine.

 

#D "[...] depends on other managers, I would consider it "not simple"..."

 

I forgot to Quote. But this is a reply of the Posted Today, 10:25 AM.

Edited by Irlan

Share this post


Link to post
Share on other sites


#B There are basic two ways of letting this not happening. First: you can have pointers to different dependencies; you need to connect the "CImageManager" to the "CTextureManager". Second: if you're using the static classes approach (not the singleton one) you can just include your header file in the class if you want to make things "simple". The same applies to the "CModelManager". The "CTextureManager" should be connected to it. The Factory Pattern approach it is a good way of creating resources that are inherited. But it is just an example; a good way of saying I'll give you an ID and filepath and you can return a resource for me.

 

Connecting the managers together seems more natural (than connecting a type of resource to another type of manager). I did this in my sample code: CMeshManager defined on line 267 stores a pointer to CMaterialManager. This is then used in the load() method.

 


#C I know what you're saying. Since you know that you're not mixing responsibilities - you have to have that cognitive ability of the classes responsibility - you can do whatever you want to load the resources. But if you're talking about optimizations, it is impossible to answer because we don't know the overall architeture of your engine.

 

There are certain aspects of resource manager that need to be fast and some aspects that don't. For example, getting the actual resource data, say texture id, in hot rendering code should be fast. That's why I think that "single manager containing all type of resources" is potentially slow due to required dynamic type casts. One thing that does not need to be that fast is obtaining a handle to the resource by resource name/hash (my sample code line 148), because this is usualy done at load time. In my sample code, I experimented with the idea that at load time, handles to resources are retrieved. At active game time, handles are used to access the actual data. This data should always be usable, but it may not be the actual resource data, if some error occured or the data is still being loaded. This is why I invoked the idea of "dummy" data: each container would have to have one resource that is used in case of error. The engine would have to ensure that such resource is always added before anything else.

Share this post


Link to post
Share on other sites

Some improvements that I have figured out for the sample code:

 

- I got the idea of "dummy" resource while making the code, but didn't really make it solid part of the code. I should make some "const size_t dummyIndex = 0;" to make it more explicit.

- The discard() method on line 62 may not work correctly. If the handle destructor is called after discard(), then the counter of resource at index 0 is decremented. I should also make some "const size_t invalidIndex = std::numeric_limits<std::size_t>::max();" and use that in discard(). I guess that the discard() method would be the main protocol for game objects to communicate that a resource is no longer used. The destructor ensures that this happens if one forgets to call discard() explicitly.

- Perhaps the pointers to resource managers, stored by handles and resources, should be shared_ptr to ensure that if for some reason the manager is destroyed while there are still handles stored, there won't be access violation.

 

On line 315 there is an example of the potential drawback of having multiple managers. Those type of expressions will be repeated. Any ideas how to streamline this?

Share this post


Link to post
Share on other sites

 


#B There are basic two ways of letting this not happening. First: you can have pointers to different dependencies; you need to connect the "CImageManager" to the "CTextureManager". Second: if you're using the static classes approach (not the singleton one) you can just include your header file in the class if you want to make things "simple". The same applies to the "CModelManager". The "CTextureManager" should be connected to it. The Factory Pattern approach it is a good way of creating resources that are inherited. But it is just an example; a good way of saying I'll give you an ID and filepath and you can return a resource for me.

 

Connecting the managers together seems more natural (than connecting a type of resource to another type of manager). I did this in my sample code: CMeshManager defined on line 267 stores a pointer to CMaterialManager. This is then used in the load() method.

 

 

 


#C I know what you're saying. Since you know that you're not mixing responsibilities - you have to have that cognitive ability of the classes responsibility - you can do whatever you want to load the resources. But if you're talking about optimizations, it is impossible to answer because we don't know the overall architeture of your engine.

 

There are certain aspects of resource manager that need to be fast and some aspects that don't. For example, getting the actual resource data, say texture id, in hot rendering code should be fast. That's why I think that "single manager containing all type of resources" is potentially slow due to required dynamic type casts. One thing that does not need to be that fast is obtaining a handle to the resource by resource name/hash (my sample code line 148), because this is usualy done at load time. In my sample code, I experimented with the idea that at load time, handles to resources are retrieved. At active game time, handles are used to access the actual data. This data should always be usable, but it may not be the actual resource data, if some error occured or the data is still being loaded. This is why I invoked the idea of "dummy" data: each container would have to have one resource that is used in case of error. The engine would have to ensure that such resource is always added before anything else.

 

DATA_STRUCTURE RENDER_ITEM {
       HANDLE Texture;
       HANDLE Shader;
};

DATA_STRUCTURE RENDER_ITEM_QUEUE {
      RETURN_VALUE Render();
      Queue<AA_RENDER_ITEM> RenderQueue;
};

/* 
Now it is time to render.
*/

for each RenderItem {
CTexture* Texture = TextureContainer.Get( RenderItem.Texture );
Texture->Activate( TextureSlot ); //This is virtual. The TextureSlot depends of other things such the "object" you have. Assumming a slot based engine.
}

The pseudo-code above is very high-level and just an example. But you got the idea. As with the reference counter and as your should know you must find a way of avoiding hash collision. Actually since you're talking about loading time, this is not a problem to compute because you're loading all resources. If you was doing this while playing the game you could have into problems.

Edited by Irlan

Share this post


Link to post
Share on other sites

The pseudo-code above is very high-level and just an example. But you got the idea. As with the reference counter and as your should know you must find a way of avoiding hash collision. Actually since you're talking about loading time, this is not a problem to compute because you're loading all resources. If you was doing this while playing the game you could have into problems.

 

Thanks for the example. Your approach of invoking TextureContainer to get the texture seems fine. In my code attempt, the handles store pointers to managers that created the handles, and then the rendering code can call handle.getData(). There are probably pros and cons in both approaches.

 

I'm not sure why you mention hash collisions at this point. Anyway, I'm not worried about those. In fact, I'm not convinced I should even use hashes. I could just use directly the names of the resources instead, since seeking the handles by name does not have to be that fast. With "one manager per resource type" there is no fear that sound "fire" is mixed up with particle system "fire".

 

I don't think that loading resources during game play will be a problem if utilizing handles. In your code, Get() method would just return a dummy texture until the actual texture has been loaded. I also tried to sketch this in my code. Of cource in practice is not very nice if an ogre mesh right in front of the camera is shown as a dummy teapot mesh while the ogre is still loading, but it's better than access violation. The game or higher level engine routine should take care that resources are loaded sensibly.

Edited by jmakitalo

Share this post


Link to post
Share on other sites




- Your code clearly takes the path that there'll be one manager per resource type. That's already a major desicion. I would like to hear more experiences whether this tends to work or not in games that have many types of resources.

 

 

It depends on your loader, but for me having seperate managers for different resource types is a valid approach and allows you tight control over budgets.

If you allocate 500 meg of ram to textures and the graphic artists use 512 meg, you know about it. The same for meshes and sound effects. Being able to say "the game crashes because Fred the painter used 4k textures for that tank" saves you a hell of a lot of heartache in crunch.


- Your manager directly returns the asset data, not a handle. Another big desicion, which I think won't work well if the manager decides to move that data in memory or the data is destroyed and a dangling pointer remains.

 

Don't hold onto the pointer then. Call the manager every time you want to use the resource.

 

    spriteBatch.Draw(Game1.Instance.TextureManager.GetTexture(backdrop_hash, full_screen, Color.White);

 

Works just as well, the performance hit of a function call is minimal and the manager can do what it likes with the data with out causing issues to the display code.

 

- There seems to be no way to actually load the resource. Or is the idea that after invoking AddAsset(), the asset is loaded? There would have to be some way then to know if it was already loaded.

 

It's high level example code, the actual details of loading the data would be hidden away in the new AssetType() call.

The already loaded check is the hashmap.Contains call.

- It does not seem robust that to remove a resource, one has to call a method of the manager. If some game object using a resource is destroyed, it will have to signal the manager that the resource is not used anymore. Injecting manager dependency to game objects does not seem natural.

 

May not seem natural, but it's damn effective.

 

If the game object is destroyed, you call the manager. Simple, robust, not the fastest in the world but if you are worried about the overhead of a few function calls at this stage you are really going to shit your pants later smile.png

Share this post


Link to post
Share on other sites

    class Content {

    public:
        Content();

        ~Content();



        template <class T>

        std::shared_ptr<T> Load(const std::string& File);



        template <>

        std::shared_ptr<Texture> Load(const std::string& File);

    protected:

    private:

        YourCacheGoesHere<std::shared_ptr<Texture>> Textures;



        Content(const Content&);

        Content(Content&&);

        Content& operator=(const Content&);

        Content& operator=(Content&&);

    };



    template <class T>

    std::shared_ptr<T> Content::Load(const std::string& File) {

        static_assert(false, "Content::Load<T>() must be specialized.");

        return std::shared_ptr<T>();

    }


    template <>

    std::shared_ptr<Texture> Content::Load(const std::string& File) {
        // Check if the item is already cached.  If it is, return it.
       // Otherwise load the resource, add it to the cache, and return a shared_ptr to it.
      // If the resource initialization fails, you can always return a std::shared_ptr<T>(nullptr)

    }

Use: // note, it is not enforced that only one instance of this class can be created, but you can easily modify it as such to do so.

Content content;

std::shared_ptr<MyType> myType = content.Load<MyType>("MyTypeFileName.ext");

 

Pros:

-Easy to use

-Easy to code

-Easy to maintain

-Easy to extend functionality for additional resource types

-Resource release is simple.  If the shared ptr for a given resource inside the cache has a reference count of 1, that resource is no longer being used externally and can be cleaned up when it's appropriate to do so.

-No dynamic casts.

-No potential memory leaks.

-No inheritance, only composition.

 

Cons:

-Requires an additional cache per resource type.

-Requires an additional function per resource type.

Edited by Phil123

Share this post


Link to post
Share on other sites

The way I have done my resources can cover at least some of your concerns.  The answers already given I think show this is primarily up to personal preference.

 

Phil's suggestion is closest to what I did.

 

I created a few templates for caching my resources:

 

CacheItem<Type>

The CacheItem wraps a pointer to the allocated resource and a reference count.

 

I started with a shared_ptr but had some issues and found it simpler to make my own object.  I also wanted to be able to track data other than just a reference count, so if I used a shared_ptr, I would still end up having to wrap that shared_ptr in an object with my extra data.

 

This sort of matches your desire for a handle.  This could point at a dummy object until the real data was loaded if you wanted to.

 

Other things you might want to track depending on how you intend to flush your cache: last time requested, maybe a sector id (from your original post)

 

CacheRef<Type>

The CacheRef is what my game uses external to the cache.  This is what the resource manager returns.  It takes a reference to a CacheItem.  This object is responsible for incrementing/decrementing the reference counter in the CacheItem and providing the pointer to the data.

 

When this object goes out of scope it will decrement the reference count.  Similar to using a shared_ptr.  This prevents me from having to worry about letting the resource manager know when I am done with a resource.

 

Cache<TKey, Type>

Implemented with a std::map.  The stored type for the map is a "CacheItem<TData>*".  

 

Relatively simple has functions for adding, finding, flushing the cache.  Could be tailored to flush by whatever you want to put into the CacheItem.  Sectors, reference counts, last request time, etc...

 

I have a single resource manager that uses instances of the Cache<Key,Type> with custom load functions for the various types.   The declarations get a little cumbersome, so some TypeDefs are used to make life a little easier.  With the templates there is very little duplicated code, just the specifics needed for the various resource loading/creation.

 

My resource requests are something like:

1) auto* item = m_whicheverCache.Find(name);

2) if (!item) -> Load data, create resource, add to cache.

3) return item

 

What gets returned is a CacheRef which can be queried to see if it is empty/null which indicates an error somewhere.  Hang onto the CacheRef for as long as you need it which keeps the reference count up.

 

This also provides another safety check for me in regards to whether I cleaned up what I thought I did.  When my cache goes out of scope it will check the reference counts on all of the items in the cache; they should be zero if the cache is about to be destroyed.  I have had times where I thought all my resources were cleaned up, but they were not.

 

General pattern of usage is pretty simple:

auto nameRef = resourceManager.GetString("login_name");
if (nameRef.IsEmtpy())
{
   DoSomethingAboutIt();
   return;
}

Print(nameRef.Data());

I have a .Data() and .DataPtr() which are just a reference and pointer to the underlying data.  With the templates, everything is nice and type safe, no casting required.

Share this post


Link to post
Share on other sites


Don't hold onto the pointer then. Call the manager every time you want to use the resource.

 

In your example code, you would search the pointer to the resource by hash each time the resource is used. I'm skeptic about the performance in hot code, say accessing textures when rendering all visible meshes. I guess the hash would refer to some map. Ok, traversing the map is only O(logN), but I would prefer not doing any unnecessary lookups in hot code.

 

After several thoughts and coding attempts, I'm feeling that a handle has the most pros of the alternatives.

 


Use: // note, it is not enforced that only one instance of this class can be created, but you can easily modify it as such to do so.

Content content;

std::shared_ptr myType = content.Load("MyTypeFileName.ext");

 

So you're after a "monolithic singleton" pattern. I don't see any reason to make a resource manager singleton, or any other class for that matter (well, maybe a logging class).

 

I don't understand how exactly you are utilizing templates here. If you just specialize it for each resource, you could as well write loadTexture, loadMesh, loadWhatever methods.

 

I think that if resources are stored in separate arrays, it makes sense to make a templated base manager class and derive it for each resource. Then much basic resource handling functionality can be implemented in the base class, while all the resource-specific functionality is implemented in separated derived classes.

 


The CacheItem wraps a pointer to the allocated resource and a reference count.

I started with a shared_ptr but had some issues and found it simpler to make my own object. I also wanted to be able to track data other than just a reference count, so if I used a shared_ptr, I would still end up having to wrap that shared_ptr in an object with my extra data.

This sort of matches your desire for a handle. This could point at a dummy object until the real data was loaded if you wanted to.

 

I would put the reference couter to the resource and not to the handle. In my attempt, when a handle is created, it accesses the manager class to increment the resource's use count.

Share this post


Link to post
Share on other sites

hi

you can create your resource manager like this:

class resource
{
public:
virtual void* load(std::string filename);
void unload();
};

then create your texture resource like this:

class texture: public resource
{
public:
texture();
virtual ~texture();
void* load(std::string filename);
};

for every of your resource, create a class like that, sound, mesh, etc

then:

template <class t>
class resourceManager
{
public:
resourceManager();
virtual ~resourceManager();
void add(t& item);
void add(t&item, int pos);
void remove (t& item);
void remove(int pos);
void deleteAll();
int getPosition(t& item);
t& getItem(int pos);
private:
std::vector<t> resourceItems;
typedef resourceItems::iterator it;
};

then you can use shared_ptr<resourceManager<resource> > and then add your resource items from XML file (you can use pugiXML), and manage it

Edited by brightening-eyes

Share this post


Link to post
Share on other sites


then you can use shared_ptr > and then add your resource items from XML file (you can use pugiXML), and manage it

 

Thanks for your input. I like the idea of having a resource base  class as in your example. Then I can add a reference counter and possibly other metadata shared by all resources to this base class. This is an acceptable use of inheritance IMHO. However, I'm not convinced that the resource should implement its loader. The loader may have dependencies to various things and then it might be better that the resource manager implements the loader.

 

I think it is a good idea to implement iterator for the resources as in your implementation. The manager should implement begin() and end() functions, so that the routines in STL could be used if needed. Then in C++11 one could iterate over resources as for(auto &res : resourceManager) {...}.

 

I'm not sure if you read the whole posting as you boldly suggest to just return a reference to a resource instead of a handle or a smart pointer. To some extent this may be a matter of taste, but I think that a handle will be most versatile.

 

I'm thankful for all the replies to this topic. I'm sure that there is no best model for a resource manager, as different games have different requirements. It's good to read how others have approached the problem and most of all I want to learn which patterns are not good in most cases.

 

I think my experimentation in

 

https://gist.github.com/jmakitalo/93102107bb44d66f83d5

 

is on the right track. It would be nice to get some feedback on it. I tried to comment as much as possible, but I realize that some parts may be difficult to interpret. The resource manager stores resources by value in a vector and then a map that can be used to quickly associate a hash value with an index to the vector. This way data locality is imposed for the resources. One thing not discussed here that I tried to impose is that handles and resources cannot be created ouside the manager (private constructors). This way one can be sure that all resources and references to them can be found inside managers. I'm not sure if this restriction is necessary, but it might make the resource management more controlled.

 

I'm not entirely sure if I should store resources by value. The pros and cons I think about:

+ data locality

- slow vector resizing if resources are bulky

- requires that the resurces implement proper copy constructors if necessary

 

I'm not sure if data locality will actually be utilized, because usually one draws objects that are visible to viewer, and these may use only a small subset of all the reources. There is no guarantee that these few resources are located within overlapping cache lines.

 

The slow resize can be mitigated by preallocating a sufficiently large vector.

 

The third point is tricky. If some resource manages dynamically allocated memory, there is a chance that proper copy constructors are forgotten and may lead to errors that are difficult to trace. The safest solution would be to store pointers in the manager.

Share this post


Link to post
Share on other sites

I went with a handle type of approach in my engine but it can be turned on or off. I can turn it off and just work with the reference counted version.

The main reason i went with handles was i wanted to be able to swap out the underlying data and be able to do dynamic reloads of content.

The entities in the scene have the handle but the data the handle points too has now changed. The hot reloading is nice for shaders and textures.

 

I can compile this out as well for a release type of build where I don't need the reloading and can save the extra level of indirection.

 

I also have a single resource manager for managing resource types using hashes/guids. Its based loosely off of unitys type of asset library. I also just have functions for LoadTexture etc instead of templates. Either way though you shouldn't have to worry about dynamic casts. I don't see a point in asking for a resource not knowing what type it is.

 

So specifying the type of resource based on a specialized template or a function shouldn't be an issue, in general the loader should fail to load a mesh as a texture and the need for a dynamic cast is avoided. There should be checks to catch issues like this during testing that shouldn't need to carry over into a final release.

 

I also allow the resource types to create/load them selves. I have the base resource types in the my core library and then in a platform specific version library, which is where my resource manager is, there are custom types of the resources that know how to load themselves off of disk and then pass the raw data to the base class that takes care of the rendering specific creation dx11/gl etc. I switch between rendering implementations based on build flags instead of dynamic loading.

 

My resource other than knowing how to create/bind/unbind their gpu versions (buffer/textures/etc) are mostly dumb.

 

Just what I chose to do.

Share this post


Link to post
Share on other sites

hi again,

in my idea, it is better to use smart pointers

because you may get memory leeks

many resource managers in games have memory leeks

about stl, i have to say that stl in my idea have everything to create your resource manager

that example that i've written is a very very simple resource manager, but with stl you can create more flexible and better resource manager, loader and use it

inside stl, you can use boost that is a good option

Share this post


Link to post
Share on other sites

hi again,

in my idea, it is better to use smart pointers

because you may get memory leeks

many resource managers in games have memory leeks

about stl, i have to say that stl in my idea have everything to create your resource manager

that example that i've written is a very very simple resource manager, but with stl you can create more flexible and better resource manager, loader and use it

inside stl, you can use boost that is a good option

 

In your sample code, smart pointer is only used for the manager object. The resources are returned as references. Remembering to destroy a manager shouldn't be an issue, as usually it is done once at the termination. Smart pointers are good when there is no clearly anticipated point of object deletion.

 

If the manager is responsible for deallocating resources and only returns handles to them, then memory leaks should not be an issues as long as the manager implements the resource deallocation properly. However, even in my code sample, the manager returns a pointer to a resource via a handle:

const T *getData(const CHandle<T> &handle) const;

This is necessary as in the end the user of the resource may need direct access to the resource. Returning a const pointer should be safe as the user cannot delete the memory, but I'm starting to think that in some cases the user of the resource may want to alter the resource, thus there is need for also non-const version of the pointer. This will jeopadize memory management, but I'm not sure if it is even possible to make completely fail-safe resource manager that still allows altering of the resources. One way could be to implement the resource alterations as methods of the manager, but this will add a lot of programming work if the resource can be manipulated in many ways.

Share this post


Link to post
Share on other sites

This topic is 1069 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this