7
\$\begingroup\$

I have a resource manager that loads and returns resources. Currently, I just return shared_ptrs for the resources I create, but I'm thinking about going over to using something like simple handles, whereby you get an ID you use to access the resource. The reasons are more explicit ownership, and also less overhead, rather than using smart_ptrs.

Are resource handles the most efficient way to access resources, should I stick with shared_ptrs or are there other alternatives?

The reason I am asking is that I was reading this article, which made me wonder if I should implement something like that, instead.

\$\endgroup\$
  • \$\begingroup\$ I would definitely say that integer based handles are the way to go. If you want it to be more flashy/intuitive, you could also just use enums. This way you could better enumerate the various assets. \$\endgroup\$ – Krythic Nov 3 '16 at 15:25
4
\$\begingroup\$

I disagree with Vittorio about not needing shared ownership of resources and, while I dislike the implementation of shared_ptr, it can be made to work just fine.

You want shared ownership because, of course, multiple game objects might be using the same resource. More importantly, if you have multiple levels and want to minimum level loading, you need some way to compute the union and intersection of resources needed by both.

One way to do this, which does require something more than just a shared_ptr, is to allow "loading" a level without resources and letting it take shared ownership of resources it needs. Then unload the old level, removing its ownership. Then, for any resource that still has ownership active but isn't loaded, load the resource.

For a resource A needed by levels 1 and 2, the reference count goes something like:

load level 1
ref=1
load A
load level 2
ref=2
unload level 1
ref=1

For a resource B used by only level 1, it goes like:

load level 1
ref=1
load B
load level 2
ref=1
unload level 1
ref=0 (removed)

And a resource C used by only level 2, it goes like:

load level 1
ref=0 (implicit, as no resource C exists)
load level 2
ref=1
unload level 1
load C

One method might be to have a layer of indirection. You store shared_ptr to a ResourceHolder. These holders are also in a list of some kind. When loading a level, you create and hold on to these ResourceHolder objects. They contain a unique_ptr to the actual resource (initially nullptr) and the path or other information needed to eventually load the item.

After loading the new level and unloading the old, you can then iterate through the list of live ResourceHolder objects, find any that have a nullptr handle to the actual resource, and then load it. When the shared_ptr to a ResourceHolder hits a ref count of 0 and unloads it, it also naturally frees the resource, and you should ensure that it removes itself from the list of live resources.

You can do much better than shared_ptr but it's a perfectly fine place to start to get up and running. Optimize it into something better later on once you have a working game.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ What are the benefits of shared resource ownership? Even if many objects require the same resource, they can simply store a reference or non-owning pointer to it. \$\endgroup\$ – Vittorio Romeo Jul 18 '13 at 18:48
  • 2
    \$\begingroup\$ I explained it in this post with reference count sequence examples. If you don't have shared ownership, you don't know when to unload or reload resources when you transition levels, unless you make the levels themselves owners (which is crappy, because I don't want to have to manually tag which resources each levels needs; I just want to add objects to a level resource and have it all work automatically). \$\endgroup\$ – Sean Middleditch Jul 18 '13 at 20:04
  • \$\begingroup\$ Your use case is really interesting and maybe a very reasonable case for genuine shared ownership. I've often developed a phobia of shared_ptr and even GC in general though from past experiences working in teams, where both of these were thought to be excuses to avoid thinking about resource management and ownership at all.. and the result was a lot of logical leaks, where the analogical dangling pointer crash turned into a silent memory leak that flew under the radar of testing, since a resource would store a shared_ptr to another whose lifetime should have been terminated long [...] \$\endgroup\$ – user77245 Jan 13 '18 at 10:47
  • \$\begingroup\$ [...] before the outer resource storing that shared_ptr. I have become reluctant of shared_ptrs and even GC ever since with all the hard-to-spot leaks I encountered. \$\endgroup\$ – user77245 Jan 13 '18 at 10:48
  • \$\begingroup\$ In our case it was like the renderer might store a matte exclusion list of strong mesh references to exclude from rendering, a light might store a light exclusion list of strong refs, a shader plugin might share ownership for a texture it uses as input, etc. etc. etc. Everything ends up kinda sharing in the ownership of all kinds of other things, and often a bulky resource like a mesh might be shared in ownership by 20 places in the system... and 19 of those places might null out the reference or remove it from a list in response to the proper event, but one would fail... \$\endgroup\$ – user77245 Jan 13 '18 at 10:53
0
\$\begingroup\$

No, they are not the most efficient way to access resources. The same Sean Middleditch who gave you an answer wrote an article "Dangers of std::shared_ptr" against using shared_ptr about one year later. You might consider using handles again. As he wrote himself:

Game engines have long supported this kind of borrowed reference using things like unique ID handles. Instead of storing a pointer (smart or otherwise) to an object, store a numeric ID instead (possibly wrapped in a templated type to ensure type safety).

Please review the section Efficiency in his article for more explanations on how to be much more efficient when using handles in contrast to shared_ptr.

For more advantages of IDs you can read the short Entity Component System Pattern article.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.