In general, a resource is defined as a source of supply or support – an asset in reserve that can be drawn upon when needed in order to function effectively. In the context of software, we define a resource in similar terms. Resources are abstract data objects that represent a loadable file, and they can be used and reused to build a complete application. A resource might be mesh, a texture, a sound, or any other piece of data.
A client application built using the Clairvoyance engine will need to provide the set of resources that make up the software. Because Clairvoyance deals strictly with the graphical aspect of an application, these resources should relate to generating visuals in some way. At minimum, an application includes 3D mesh data and textures stored in external file formats. More complex applications can implement shader files, or material definitions.
Resources are data files that exist outside of the software that use them. They must be loaded and managed efficiently throughout the application in terms of memory and retrieval, and able to be unloaded to free memory up, as to create low levels of stress on the memory budget. Resources should only ever be loaded once during the life of the application that uses them.
The Resource class is abstract, and does not denote a specific file type to use. Subclasses of the Resource class define specialized implementations and are associated with a related file type. The Resource interface contains methods common to all subclasses, such as loading and unloading.
The need to manage resources is a necessary part of utilizing them. A resource manager takes on the responsibilities of generalizing the operations that handle the lifecycle of a resource. The Resource Manager class concerns itself largely with the loading and creating of a resource.
The Resource Manager does not make any assumptions on the file type being loaded. Because the resources it loads are abstract, the manager class itself is also abstract. The specifics of a file type are handled in related subclasses of the resource manager.
Several implementations of the resource class can exist by subclassing the abstract interface. Clairvoyance defines already a few that are available to the programmer. This includes mesh data, textures, and shader files. As the engine develops, more options are becoming available.
Specifying a custom resource implementation is possible by subclassing Resource and Resource Manager. This can be useful at a client side level for handling things like external text files, such as XML or material definitions.
Smart Pointers and Resource Pointers
Clairvoyance uses its own implementation of smart pointers assist in memory management. A smart pointer automatically counts references and is used for objects where implicit destruction is required. The reference count is used to work out when to delete the object. This is important for keeping memory management succinct and for sharing pointers across the application from a single access point.
A smart pointer acts as a wrapper object for any kind of pointer variable type. It is a template class, and requires the specific data type to be defined at the time of its use. As such, all members are constructed around a template variable pointer and do only generic operations, such as setting the pointer or checking the pointer for null values. Every time the Smart Pointer class is instantiated or destructed, the reference count is incremented or decremented.
Using the concept of a smart pointer makes handling resources much more efficient. For this reason, the use of a resource is done almost exclusively through the Resource Pointer class. A Resource Pointer is a specific implementation of the Unique Pointer class, and is defined as a typedef of SmartPtr with Resource filling in the template. The ResourcePtr provides an abstract base class that ResourcePtr subclasses can delegate to for common functionality.
ResourcePtr subclasses are defined through another generic template class called the Resource Subclass Pointer (ResourceSubclassPtr). This class assigns smart pointers to subclasses of Resource, and gives way to using Mesh Pointers, Shader Pointers, and so forth. There are a couple advantages of using ResourceSubclassPtrs over standard pointers:
- Provides shared access of the pointer’s data from a single, globally accessible location,
- Eliminates the need to reload external data when needing to reuse it,
- Automatically counts the references to the pointer to manage garbage collection,
ResourceSubclassPtr is not an actual subclass of ResourcePtr; rather, it is another subclass of Smart Pointer, similar to ResourcePtr. When we define a subclass of Resource, we provide a typedef of the ResourceSubclassPtr, with the specific subclass filling in the template variable. The relationship between ResourcePtr and ResourceSubclassPtr exists in the ResourceSubclassPtr interface. Here, member methods are defined to allow direct downward conversion between ResourceSubclassPtrs and ResourcePtrs. This is done so that we can access the specific subclass data from a generic interface independent of the subclass implementation.
Resources are a necessary component to every application built off of the Clairvoyance engine. Complex applications will use a multitude of different resources, and managing them properly is imperative to designing an efficient program. In building Clairvoyance, we strive to keep the code architecture as robust as possible.
Because resources are such a large part of almost any given Clairvoyance application, it is important to put heavy emphasis on resource management.