Resources Tutorial

Welcome to the tutorial about the Resources module of the Thor Library. Resources are heavyweight objects that can be loaded from the application, such as images, fonts, sound files, etc. In SFML, there exist five classes with resource semantics:

Note that sf::Music is a special case; it behaves quite distinctly from the rest. Music is streamed and thus not completely loaded into memory. It has no real data, so shared access makes not much sense.

The Thor Resources module provides an interface that simplifies loading and access to these resources. Basically, the module consists of three widely independent parts:

Resource keys

Keys are IDs to access the resources. They contain loading information for the corresponding resource. A resource key for an sf::Image which should be loaded from a file “image.jpg” is declared like this (given we write using thor::Resources::ImageKey; for simplicity):

ImageKey key = ImageKey::FromFile("image.jpg");

These static factory methods indirect to the loading functions in sf::Image, namely Create(), LoadFromFile() and LoadFromMemory(), and they have the same signature. This relation applies for the other resource classes in SFML, too.

As mentioned, resource keys represent identifiers for resource access. Keys that are initialized identically refer to the same resource. Analogously, keys being initialized with different parameters (or even different factory functions) are always distinct, which means they refer to different resources. Note that resource keys do neither store nor reference the resource. The only thing they have is the abstract knowledge about how to load resources and how to differ between them.

Resource managers

The class template thor::ResourceManager is the core of the resource management system. It maps keys to resources and returns pointers to access them. But before, it has to load the resources into memory, what can be done using the resource keys:

ImageKey key = ImageKey::FromFile("image.jpg");
thor::ResourceManager<sf::Image> mgr;

The Acquire() function loads the resource according to the way in which the resource key was initialized. In the upper example, the image is loaded from a file called “image.jpg”. The next time we acquire the image using the same key, the ResourceManager recognizes that the corresponding resource has already been allocated and does not load it again. But when we use a different key (initialized with other parameters or another factory function), we address another resource, therefore it is not cached. To make it clear:

thor::ResourceManager<sf::Image> mgr;
mgr.Acquire(key);   // Loads a new resource
mgr.Acquire(key2);  // Loads a new resource
mgr.Acquire(key);   // No loading (resource already stored)

In case a resource cannot be loaded (e.g. wrong filename), the method Acquire() throws an exception of type thor::ResourceLoadingException.

Resource pointers

We have now seen how to load resources, but how can we actually use them? Here comes the third component of the Resources module into play: The class template ResourcePtr. This smart-pointer offers a safe and elegant way to access the resources as soon as they are loaded. It is returned by the Acquire() function.

thor::Resources::ImageKey key = …;
thor::ResourceManager<sf::Image> mgr;
thor::ResourcePtr<sf::Image> image = mgr.Acquire(key);

The smart pointer ResourcePtr has the following properties:

When you want to access loaded resources without possibly allocating new ones, Search() is the function of your choice. It never loads resources. In case nothing is found, a null pointer is returned.

Releasing resources

When you allocate resources, you also have to deallocate them. Or maybe not you alone, the ResourceManager helps you doing this task. To allow reasonable deallocation, the resource class must support RAII, hence its destructor must take care of cleaning its own resources up. But this is normally no problem when you program in C++. Every SFML resource class does this automatically.

All resources stored in a resource manager are released in the destructor ~ResourceManager() – unless you release them before. Without further configurations, resources are kept in memory until you explicitly release them or the resource manager is destroyed. This behavior is quite meaningful, as it saves time to reallocate resources that are temporarily unused. On the other hand, there are situations which require a preferably low resource allocation. Here, resources are released as soon as they become unused. The Thor library supports both release strategies; they reside as ExplicitRelease and AutoRelease in namespace thor::Resources.

Now you still don’t know how to release resources! Well, that’s simple, just call the Release() function:

// as always

If the corresponding resource is not used at the moment of the call, it is immediately released. Otherwise, things get more complex. As mentioned above, ResourcePtr instances keep their resources alive, so the resource is not released. However, the ResourceManager will take care of releasing it ASAP – this happens the moment that the last ResourcePtr loses ownership of the resource.

Const correctness [advanced]

It is often desirable that resources are not altered after they have been initialized. ResourcePtr is powerful enough to imitate the const-correct behavior of raw pointers. That is to say, you can add const qualifiers, but not remove them.

thor::ResourcePtr<sf::Image> ptr;
thor::ResourcePtr<const sf::Image> cptr;
 ptr =  ptr; // ok
cptr = cptr; // ok
cptr =  ptr; // ok, adding const-qualifier
 ptr = cptr; // not okay, removing const-qualifier

So when you use pointers to constant resources, you prevent accidental modification through ResourcePtr. If you are very radical, you can even declare the template argument of ResourceManager const! Like this, you have no possibility to change resources once they have been loaded.

thor::ResourceManager<const sf::Image> mgr;
thor::ResourcePtr<const sf::Image> cptr = mgr.Acquire(key); // ok
thor::ResourcePtr<sf::Image> ptr = mgr.Acquire(key);        // not ok

Different management strategies [advanced]

The resource manager allows tweaking some behavior. This concerns the following actions, which concern Acquire() calls in the future:

Custom resources [advanced]

Having your own resource class, the following steps are required to make it compatible with thor::ResourceManager:

That’s it.