7
\$\begingroup\$

I have a resource manager that manages the lifetime of resources in memory. A "resource" is something like a texture, mesh, et cetera. I also have an entity class that, the base class for things in my game world, which has pointers to individual resources.

I'm thinking about giving my resource manager a memory quota so it can free and reload resources as it needs to in order to satisfy this quota, and I'm wondering how I could go about this without ending up with dangling pointers in my entity objects.

I'll need the ability to free up a mesh or texture at any time in the resource manager.

I could use shared_ptr in the resource manager and weak_ptr in the entities (as long as I don't expose a shared_ptr out side of the resource manager, I'd still get the ability to free them on demand), but I am more interested in how you would do it without those classes, for example before C++11 existed or before Boost's smart pointer library existed.

\$\endgroup\$
0

2 Answers 2

7
\$\begingroup\$

Instead of holding raw pointers, hold some other lightweight reference to the resource. A shared_ptr and corresponding weak_ptr instances are one such mechanism, but you can easily implement your own if you don't have access to those types (or their earlier Boost equivalents) or there are other reasons why those types would not be appropriate for your scenario (such as being too heavyweight).

This kind of 'handle' system can be as simple as handing out integers which refer to internal slots in the resource system, which can then be used to dynamically reload a resource if access to it is requested while it's been unloaded.

In practice you probably want something a little more robust, at least a way to treat the handle "like" a pointer and provide operator -> overloads, for example, by wrapping it in a Handle type. That Handle type can just contain a pointer to a "control block" stored within the resource manager. The control block has the raw resource reference and a boolean flag that indicates if the resource is alive or not, and some kind of key that can be used to reload the resource if needed (I assume a string in the example below). A very simple example of this might look like:

template <typename Resource>
struct Handle {
    Resource * operator -> () }
        return m_owner->retrieve_resource(m_resource_id);
    }

private:
    int m_resource_id;
    resource_manager * m_owner;
};

namespace detail {
    struct control_block {
      Resource * resource;
      bool is_alive;
      std::string key;
    };
};

struct resource_manager {
    Resource * retrieve_resource (int id) {
        auto block = m_resources[id];
        if (block->is_alive) {
          return block->resource;
        } else {
          block->resource = reload_resource(key);
          block->is_alive = true;
          return block->resource;
        }
    }

private:
    std::vector<detail::control_block> m_resources;
};

In this implementation, the resource manager just stores a flat array of control blocks which can be referenced by an integer, and provides a method to recover a resource pointer by ID, reloading that resource if needed. The Handle class is just a useful wrapper around that behavior. Obviously this is a very simple example, though. If you want something more complete, consider Scott Bilas' article on a generic handle-based resource manager. It takes a somewhat different approach, but is well worth the read.

(Be warned that a need your question implies may mean your resource utilization is very chaotic in nature, and that isn't necessarily a good thing for performance, so you might want to consider that.)

\$\endgroup\$
8
\$\begingroup\$

Smart pointers or handle classes, just like C++11. You could write your own long before C++11. I would even argue that the shared_ptr in C++11 is probably the wrong model entirely.

All you do is make a type that holds a reference to a resource. This reference could be a pointer with some kind of lifetime management or it could just be some kind of numeric identifier used as a key into a data structure (array index, map key, whatever). Use that to access resources. Similar to how weak_ptr works, except you shouldn't need to worry about "locking" them with a shared_ptr first. Resources should only ever be released after all logic and rendering for the frame is done and there's no possibility of dangling pointers remaining (assuming you use the appropriate handles everywhere in your non-temporary data structures).

Note that just paging out resources randomly is a bad idea. If an object exists and says it needs a texture, it needs that texture. If you're running out of memory, either (a) target better hardware, (b) make a smaller game, or (c) implement a more sophisicated resource management system that can more accurately determine the current working set and appropriate times for objects to release or reacquire their resources (using level-of-detail, area-of-interest, or so on).

\$\endgroup\$
5
  • \$\begingroup\$ can you elaborate why C++11 shared_ptr is the wrong model? even with make_shared? is it because of performance or use pattern/syntax ? \$\endgroup\$
    – concept3d
    Nov 13, 2013 at 20:50
  • \$\begingroup\$ @concept3d: I've been working on an article on it; it's hard to sum up. In the end it's just a bad ownership model. Only one object should ever own another; everyone else should be "subscribers" requesting that the owner keep it around, and which should be traceable/debuggable (you should be able to list at any time all active handles to a resource, which you can't do with shared_ptr). It has all the faults of automatic GC in regards to semantics and all the downsides of reference counting in regards to implementation. \$\endgroup\$ Nov 14, 2013 at 0:11
  • \$\begingroup\$ well I understand. I was thinking to implement my resource managers using shared_ptrs, but things will be clearer if I read your article is it finished ? \$\endgroup\$
    – concept3d
    Nov 14, 2013 at 0:14
  • \$\begingroup\$ @concept3d: maybe. I wouldn't count on it being finished soon. I can just say for right now from a few decades of collective experience between me and a few lead engine architects I've spoken to on the matter recently that shared_ptr-like smart pointers tend to cause more headaches than they're worth. A lot of very hard to debug problems can start popping up close to shipping which all boil down to reference leaks (not clearing a shared_ptr when you should have) or destructors being called at the wrong time (objects staying alive too long then being destroyed at a bad time). \$\endgroup\$ Nov 14, 2013 at 1:21
  • \$\begingroup\$ thanks for the details, after thinking thoroughly about what you said it totally makes sense. There should only be one owner, and other subscribers ( or sth similar to weak_ptr ). +1. \$\endgroup\$
    – concept3d
    Nov 14, 2013 at 7:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .