In my multi-threaded graphics application, I have certain assets like images, models, sound files and so on. Some of them are loaded from files on the disk. When those files change, I want to automatically reload them and update the corresponding assets that may be used all over the application. A similar use case is LOD. When models get far away from the camera, I want to replace them with cheaper less detailed versions.

While the assets get replaced, other parts of the application run in different threads and may read to those assets. So I need some locking. How can I provide proper locking to replace the assets while making it as easy as possible for the other parts of the application to use them?

For example, I could provide an abstract base class for assets. This could contain a shared mutex. Then, there would be a loader class, that internally stores assets and returns references to them.

class Asset { public: std::shared_mutex access_; }; class Loader { public: template <typename T> T &load(std::string filename); private: std::map<std::pair<std::string, std::type_index>, void*> assets_; }; template <typename T> class AssetLoaderTraits;

However, there potentially are a lot of assets, so let's try fewer mutexes. For example, the loader could keep a list of locked assets. There would be only one mutex to access the list. Moreover, we wouldn't need the asset base class anymore.

class Loader { public: template <typename T> T &load(std::string filename); void lock(std::string filename); bool try_lock(std::string filename, std::chronos::duration trying); void unlock(std::string filename); private: std::map<std::pair<std::string, std::type_index>, void*> assets_; std::shared_mutex map_access_; std::map<std::string> locked_assets_; }; template <typename T> class AssetLoaderTraits;

However, I still feel that this isn't the best solution. If there are thousands of assets, they are often processed in bulk. So there is a loop over a vector of assets and the locking mechanism would be required in every iteration. For locking, I'd also need to remember the filenames of all assets I want to use. (Also, it feels weird that the loader holds the locks.)

std::vector<std::pair<std::string, Image&>> images; Image &image = loader.load<Image>("/path/to/image.png"); images.push_back(std::make_pair("/path/to/image.png", image)); // ... for (auto &i : images) { std::string &filename = i.first; Image &image = i.second; loader.lock(filename); // ... loader.unlock(filename); }

Is there a better way to do this? I feel like I'm over-complicating this and have overseen a much simpler solution. How is this situation commonly solved? My goals are to have a simple interface and good performance for iterations over large collections of assets.