Creating a proper resource manager class

Started by
8 comments, last by Kylotan 6 years, 5 months ago

So I'm attempting to make a system where I have these resource manager classes and I came across a serious flaw with my set up. When I add a new object using my add function it returns a pointer. Do this again and the previous pointer is now garbage. I now know this is happening because adding (or even if I was trying to remove an object) will cause allocations / shifting cause the pointers to get desynced.

So my question is what is the proper way to make resource manager class? Should I just use a vector of pointers and create a new object every time I need to add?


Obj* addObj() {

  Obj* test = new Obj;
  test->val = count;
  ++count;

  objects.push_back(test); //objects is a std::vector<Obj*> in this case. Instead of std::vector<Obj>
  return objects.back();
}

 

Just in case my set up basically looks like this:


//My resouce
class Obj
{
public:
	Obj() {
		val = 0;
	}
	~Obj() {}

	int val;
};

//Resouce manager
class ObjManager
{
public:
	ObjManager() {
		count = 0;
	}

	~ObjManager() {
	}

	Obj* addObj() {
		
		Obj test;
		test.val = count;
		++count;

		objects.push_back(test);
		return &objects.back();
	}

	int count;
	std::vector<Obj> objects;
};

int main()
{
	ObjManager test;
	Obj* obj = test.addObj(); //Add a new Object. Pointer is ok
	test.addObj(); // Add another object. The pointer saved to obj is now garbage

	std::cout << "Complete!" << std::endl;
}

 

Advertisement

I'd likely construct a vector of unique pointers to the objects in the manager, so

  • the objects don't move when the array is re-allocated, and
  • the object is still owned by the manager, so it always gets properly deleted.

You can then give away bare pointers to the objects that do not carry ownership semantics.

 

If you know how many resources you get, you can use vector::reserve to make sure the vector has enough space for additions without moving. That would solve your re-allocation problem too.

Another option is to ask the resource manager for the pointer each time you need the resource. Getting the address of a vector element isn't expensive, as far as I know (especially if you allow inlining that computation), and it would always give the right address without having bare pointers to all the resources all over the place.

 

I am a bit puzzeled by lack of resource identification in your class. How does each object know what number to get, and how do you prevent/avoid loading the same resource several times?

 

 

59 minutes ago, Alberth said:

I am a bit puzzeled by lack of resource identification in your class. How does each object know what number to get, and how do you prevent/avoid loading the same resource several times?

Oh what I posted is basically just a dumbed down version to show my issue where the pointer turns to garbage.

Honestly my class is probably not a "true" resource manager and really just more of a factory where created resources will get cleaned up in the end if for some reason you lose track of the pointer returned. I'm currently not doing anything to prevent double loading of resources. So you could do:


//Load in the boat texture
Texture* boat1 = textureManager.load("boat.png");

//Load in a boat texture again
//Has no idea that you already loaded this texture above so it will create a second texture
Texture* boat2 = textureManager.load("boat.png");

 I'm guess if I did want to prevent creation of duplicate resources I really should use std::map. That way I can check if the resource already exists using some unique id as the key. If it does return the already created resource, if not create a new one

59 minutes ago, Alberth said:

Another option is to ask the resource manager for the pointer each time you need the resource. Getting the address of a vector element isn't expensive, as far as I know (especially if you allow inlining that computation), and it would always give the right address without having bare pointers to all the resources all over the place.

I'm curious what this looks like? Even though I feel that it might be best to just have a vector of pointers. I'm guessing instead of returning a pointer you would just return some kind of id? Then you can use that id to get the resource when you need it

1 minute ago, noodleBowl said:

I'm curious what this looks like? Even though I feel that it might be best to just have a vector of pointers. I'm guessing instead of returning a pointer you would just return some kind of id? Then you can use that id to get the resource when you need it

That would work.

 

Under the assumption that you know what resources will exist beforehand, you could make an enumeration for it, so you can just ask by fixed number, which is a lot faster especially if you want to do that each frame.

Since managing the enumeration and the link to the resource may become cumbersome to do by hand, you can write a simple script eg in Python, that collects all resources eg in some directory, and then generates the enumeration, and a block of code that loads all resources (or on demand, or whatever you prefer). You compile those files into your program so the entire program has access to the enumeration, and the resource manager  can find / load what resources to use.

The tricky part is to come up with a consistent and predictable enumeration name for each resource. One option is to use the file name of the resource, and replace every 'weird' character such as / \ . : etc, by _ . It does lead to long enumeration constants though, but they are quite descriptive.

Take a look at DirectXTK's SharedResourcePool.h which uses variadic template arguments to pass constructor arguments to the pool.

 

🧙

2 hours ago, matt77hias said:

Take a look at DirectXTK's SharedResourcePool.h which uses variadic template arguments to pass constructor arguments to the pool

The template thing is pretty cool and fits pretty well! I keep forgetting that you can do stuff like this

Related side question:

When and how often should I be using dynamic memory variables ( Object * obj = new Object() ) vs traditional variables ( Object obj )? Normally I try to avoid using dynamic memory variables at all costs, but I'm starting to think that this a really bad way to think about dynamic memory

3 hours ago, noodleBowl said:

When and how often should I be using dynamic memory variables ( Object * obj = new Object() ) vs traditional variables ( Object obj )? Normally I try to avoid using dynamic memory variables at all costs, but I'm starting to think that this a really bad way to think about dynamic memory

https://stackoverflow.com/questions/102009/when-is-it-best-to-use-the-stack-instead-of-the-heap-and-vice-versa

I think that's a reasonable way to think of the heap. Avoid it unless you need to create an object that outlives the current scope, or to circumvent stack overflow with large objects.

This is an oft-discussed topic, so I recommend searching the forums.

Giving the short form:

* If you are building your own hobby game on your own, with your own tools, do what you want. It doesn't really matter as you won't be pushing the hardware.

* In big systems, the typical from is a family of items as a system, and each part has a single responsibility.  In general terms there are four parts: The Store, which provides the interface for others to access; The Proxy, which provides a stable object that can be an indirection to the loaded instance or to a face object that is usable even while the data is loading or is unavailable; The Cache, which is the actual container or processor for the resources; the Loader, which pulls items in to the system.

* It is usually a terrible idea to make a single unified resource manager for all types of resources.  Graphics textures are processed to live on the video card and be addressed as part of rendering. Event audio clips tend to be long-lived and loaded based on what will be used soon. Streaming audio tends to be pulled in dynamically and fed to audio hardware. Animation script data should be kept in cache-friendly patterns for repeated scanning and processing. Go on and on for every system.  Each one has different requirements for how it should be stored, how it is accessed, how it is traversed, and when it is loaded and unloaded.  A few engines and tools will provide a unified interface to The Store, but under the hood each resource management system is highly specialized for the resource being contained.

* Object lifetimes are critical in games.  You need to understand the lifetime of objects, how and when they are created, their life cycle as they are created, as they grow and are placed into the game world, as they are processed over time, how they are removed from the scene, and how they eventually are removed and die.  The resource systems described above with four parts can help with those responsibilities, but no matter what route you take, the task is critical.

 

Each of these can be searched individually.  These used to be part of some of the FAQs on the system, but a recent overhaul cleared a few of them out. 

Regarding the very original problem, I recommend getting hold of a book like The C++ Standard Library, which covers things like pointer invalidation in containers very well. (Strangely, the main C++ online references don't seem to mention this fact very clearly.)

This topic is closed to new replies.

Advertisement