dimanche 26 avril 2015

Locking for multi-threaded hot swapping

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 (and sometimes write) 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.

Aucun commentaire:

Enregistrer un commentaire