0
\$\begingroup\$

I have implemented a resource handle and resource cache class. The handle stores an integer id. The cache stores a vector of objects e.g., Textures. The handle id acts as an index into the cache asset vector.

I then have a third class which provides methods to get the object(Texture) associated with a given handle. The class also stores a map of filename, handle for previously loaded objects.

I am trying to add reference counting to the system. At the moment I store a std::size_t* ref_count in Resource_handle and have the following constructor/destructor/assignment operators:

Resource_handle() : id(0), verifier(0), ref_count(new std::size_t(1)) {}

Resource_handle(const Resource_handle<T>& handle) 
: id(handle.id), verifier(handle.verifier), ref_count(handle.ref_count) { ++*ref_count; }

template<typename T>
Resource_handle<T>& Resource_handle<T>::operator=(const Resource_handle<T>& rhs)
{
    ++*rhs.ref_count;
    if (--*ref_count == 0) {
        delete ref_count;
        // reset handle
    }
    id = rhs.id;
    verifier = rhs.verifier;
    return *this;
}

template<typename T>
Resource_handle<T>::~Resource_handle() // reference handling 515 C++ Primer
{
    if (--*ref_count == 0) {
        delete ref_count;
        // unload resource in handle
        // reset handle?
        reset();
    }
}

The above code is not complete but I have two problems I'm aware of so far.

  1. The objects in the Resource_cache vector are not allocated on the heap and Resource_handle has no pointer to them to delete them.

  2. The reference count ref_count is actually counting copies of a particular handle rather than of objects in the cache vector.

How can I resolve these two issues? I'm wondering if the reference handling should be done in the cache class instead? However the cache would have no knowledge of how many handles refer to a particular object in its object vector.

Many thanks.

Edit: I did consider std::shared_ptr<T> however research lead me to using handles for the ability to reload resources, easier serialisation and the more lightweight approach.

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

Before answering your other questions one thing that could break your design. If you delete an asset in the middle of the vector all other asset indices will move and effectively ruin your cache ids since now all handles point to the wrong asset. You probably want to use something else for identifier. Either a key or switching the vector to a map could be enough.

I think your overall design goes to the correct direction, decoupling the actual asset from the handler that holds all the metadata for it. The handle doesn't need a pointer to the actual asset. As long as you have a unique identifier that can be used to pinpoint to a single asset you are ok. ( But having a pointer is probably going to make things simpler.)

What I would probably suggest is to not have the responsibility of the freeing of assets on the handle. You can have a struct like this stored in the cache vector ( or cache_hashmap; one thing to be very careful here is that vector may move things in memory if it reallocates. Holding pointers to vector elements will break. Prefer to either allocate them in heap or use a hashmap or a list):

struct cache_entry
{
    int ref_count;
    Texture* actual_asset;
    //other data
    std::string filename;
}

And the handle instead of holding a pointer to the ref count can have a pointer to the cache_entry. The handle then instead of increasing the ref count, it notifies the cache that it's acquiring/releasing the resource. Resource_cache::Free(entry_ptr)

This way :

  • You can iterate over your assets for debug reasons and see how many refs are still alive.
  • You can lock assets that you may not want to release even if they don't have any references.
  • You can decide when to react to a asset being freed whenever you want. Maybe you want o wait for the next frame or there is a chance someone will try to request the asset immediately. The cache can make that decision without making the handle more complicated.
\$\endgroup\$
2
  • \$\begingroup\$ Many thanks for your reply. I understand the point you raised regarding deleting an element in a vector. I have considered making a wrapper class around the cache object, something like a Texture_ptr class, holding a Texture* and a use* (ref count). I think this is the same idea as your cache_entry? The problem is that the handle has no knowledge of the cache at the moment. I have a third class which provides functions to, for example, get the object(Texture) associated with a given handle. The class also stores a map of string filename->handle for previously loaded objects. \$\endgroup\$ Feb 2, 2021 at 19:22
  • \$\begingroup\$ Yeah it sounds the same as the cache_entry. There are multiple ways you could make the handle aware of the cache. You could make the cache a singleton (or a service gameprogrammingpatterns.com/service-locator.html ) or you could add a reference to the cache when you create the handle. \$\endgroup\$
    – Exaila
    Feb 2, 2021 at 21:08

You must log in to answer this question.

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