Advertisement

How might I refactor my resource manager to also handle loading?

Started by March 12, 2024 09:47 PM
1 comment, last by Aressera 8 months, 1 week ago

I've created a resource manager in my engine which adds some basic functions for adding and accessing loaded resources the actual loading is handled wherever it's needed though for example in News my mesh file I have the class and a free function

struct Mesh : Resource {
// vetices, vao, vbo, etc
};

void LoadMeshFromFile(const std::string& name, const std::string& path)

I realized it'd make more sense if the resource manager also handled loading that way I can do some common things like checking if the file is valid instead of having multiple load functions scattered around, however, I'm not exactly sure how I'd refactor for this to work.

struct ResourceLibrary {
	std::map<std::string, std::unique_ptr<Resource>> resources;

    template<typename TResource>
    void AddResource(const std::string& name, std::unique_ptr<TResource> resource) {

    }
    
    template<typename TResource>
    TResource& GetResource(const std::string& name) {

    }
};

The easiest and only solution I've seen is to have some sort of registering of loader classes or functions and then have a load function (probably templated) that calls the proper load function, but I feel like this is a bit messy since i have to find a convenient spot to register all of them and perhaps do some inheritance if I use classes.

The resource system for my engine and editor works similar to what you propose. The major components are:

  • ResourceType - a UUID + type name string + optional pointer to polymorphic parent resource type. I define a template function that can lookup the resource type (a global) for a template type parameter.
  • ResourceID - a class that stores metadata about a specific resource. It has a pointer to ResourceType, the resource's UUID, name, child/parent arrays, and modify/creation dates.
  • Resource<T> - reference-counted storage for the resource data and its ResourceID.
  • Reference<T> - a weak reference to a resource, stored as a pair of pointers to resource data and ResourceID, with no reference counting. This is the main method used to store a reference to resource + metadata. In most other places in the core engine I use raw pointers.
  • ResourceSet - a collection of resources of any type, organized by type. This is used to store anything that the engine can handle in a consistent way. There are template functions to get<T>() resources by UUID, name, or index. Internally, the resource set stores objects of type Resource<T>, and returns Reference<T>.
  • ResourceFormatTranscoder<T> - an interface with encode(), decode() functions that read/write at a provided file path for a specific format of a resource type. I use the curiously recurring template pattern here. This helps to reduce boilerplate.
  • ResourceFormatManager - a collection of ResourceFormatTranscoder subclases, organized by the type of resource they load, with support for multiple formats per resource type. The format manager chooses the right transcoder based on the file extension (a specific format can also be specified). The transcoder subclasses are added to the manager in main() before anything else.
  • ResourceSetTranscoder - handles encoding/decoding ResourceSet objects to/from my custom engine file format. This is a subclass of ResourceFormatTranscoder and is also registered with the ResourceFormatManager. This class has its own set of ResourceTypeTranscoder subclasses which implement the serialization for specific resource types to/from the internal engine format. These are also registered in main().
  • ResourceManager - a class that wraps the rest of the system in a simple interface and handles streaming or partial loading/unloading of resources from the main ResourceSet. It has a load(UUID) and load(name) functions, and unload(UUID). These can be used to request specific resources by UUID or name. These are main APIs used by the rest of the engine for loading resources. This class caches the loaded resources in the ResourceSet, and counts how many times each has been loaded/unloaded. Resources are freed when load count reaches 0.

This is obviously quite a bit of infrastructure, it would take some significant time to develop your code to this level.

To start, I would strongly discourage using strings to refer to resources. This will break as soon as you want to rename something. Instead, use UUIDs to refer to resources, and then do a lookup (using hash table) to determine the resource from the UUID. UUIDs are also more efficient than strings in almost every way (no memory allocations).

This topic is closed to new replies.

Advertisement