VideoCommon: add additional locks around asset access and usage to ensure thread safety

This commit is contained in:
iwubcode 2023-06-04 23:01:29 -05:00
parent ce2b63dcc0
commit 9d7ab47738
7 changed files with 92 additions and 56 deletions

View File

@ -16,6 +16,7 @@ bool CustomAsset::Load()
const auto load_information = LoadImpl(m_asset_id); const auto load_information = LoadImpl(m_asset_id);
if (load_information.m_bytes_loaded > 0) if (load_information.m_bytes_loaded > 0)
{ {
std::lock_guard lk(m_info_lock);
m_bytes_loaded = load_information.m_bytes_loaded; m_bytes_loaded = load_information.m_bytes_loaded;
m_last_loaded_time = load_information.m_load_time; m_last_loaded_time = load_information.m_load_time;
} }
@ -29,6 +30,7 @@ CustomAssetLibrary::TimeType CustomAsset::GetLastWriteTime() const
const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const
{ {
std::lock_guard lk(m_info_lock);
return m_last_loaded_time; return m_last_loaded_time;
} }
@ -39,6 +41,7 @@ const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const
std::size_t CustomAsset::GetByteSizeInMemory() const std::size_t CustomAsset::GetByteSizeInMemory() const
{ {
std::lock_guard lk(m_info_lock);
return m_bytes_loaded; return m_bytes_loaded;
} }

View File

@ -30,6 +30,7 @@ public:
// Queries the last time the asset was modified or standard epoch time // Queries the last time the asset was modified or standard epoch time
// if the asset hasn't been modified yet // if the asset hasn't been modified yet
// Note: not thread safe, expected to be called by the loader
CustomAssetLibrary::TimeType GetLastWriteTime() const; CustomAssetLibrary::TimeType GetLastWriteTime() const;
// Returns the time that the data was last loaded // Returns the time that the data was last loaded
@ -48,8 +49,10 @@ protected:
private: private:
virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0; virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0;
CustomAssetLibrary::AssetID m_asset_id; CustomAssetLibrary::AssetID m_asset_id;
mutable std::mutex m_info_lock;
std::size_t m_bytes_loaded = 0; std::size_t m_bytes_loaded = 0;
CustomAssetLibrary::TimeType m_last_loaded_time; CustomAssetLibrary::TimeType m_last_loaded_time = {};
}; };
// An abstract class that is expected to // An abstract class that is expected to
@ -70,7 +73,7 @@ public:
// they want to handle reloads // they want to handle reloads
[[nodiscard]] std::shared_ptr<UnderlyingType> GetData() const [[nodiscard]] std::shared_ptr<UnderlyingType> GetData() const
{ {
std::lock_guard lk(m_lock); std::lock_guard lk(m_data_lock);
if (m_loaded) if (m_loaded)
return m_data; return m_data;
return nullptr; return nullptr;
@ -78,7 +81,7 @@ public:
protected: protected:
bool m_loaded = false; bool m_loaded = false;
mutable std::mutex m_lock; mutable std::mutex m_data_lock;
std::shared_ptr<UnderlyingType> m_data; std::shared_ptr<UnderlyingType> m_data;
}; };

View File

@ -30,7 +30,7 @@ void CustomAssetLoader::Init()
std::this_thread::sleep_for(TIME_BETWEEN_ASSET_MONITOR_CHECKS); std::this_thread::sleep_for(TIME_BETWEEN_ASSET_MONITOR_CHECKS);
std::lock_guard lk(m_assets_lock); std::lock_guard lk(m_asset_load_lock);
for (auto& [asset_id, asset_to_monitor] : m_assets_to_monitor) for (auto& [asset_id, asset_to_monitor] : m_assets_to_monitor)
{ {
if (auto ptr = asset_to_monitor.lock()) if (auto ptr = asset_to_monitor.lock())
@ -50,11 +50,11 @@ void CustomAssetLoader::Init()
{ {
if (ptr->Load()) if (ptr->Load())
{ {
if (m_max_memory_available >= m_total_bytes_loaded + ptr->GetByteSizeInMemory()) std::lock_guard lk(m_asset_load_lock);
const std::size_t asset_memory_size = ptr->GetByteSizeInMemory();
if (m_max_memory_available >= m_total_bytes_loaded + asset_memory_size)
{ {
m_total_bytes_loaded += ptr->GetByteSizeInMemory(); m_total_bytes_loaded += asset_memory_size;
std::lock_guard lk(m_assets_lock);
m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr); m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr);
} }
else else

View File

@ -52,12 +52,17 @@ private:
{ {
auto [it, inserted] = asset_map.try_emplace(asset_id); auto [it, inserted] = asset_map.try_emplace(asset_id);
if (!inserted) if (!inserted)
return it->second.lock(); {
auto shared = it->second.lock();
if (shared)
return shared;
}
std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) { std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) {
asset_map.erase(a->GetAssetId()); {
m_total_bytes_loaded -= a->GetByteSizeInMemory(); std::lock_guard lk(m_asset_load_lock);
std::lock_guard lk(m_assets_lock); m_total_bytes_loaded -= a->GetByteSizeInMemory();
m_assets_to_monitor.erase(a->GetAssetId()); m_assets_to_monitor.erase(a->GetAssetId());
}
delete a; delete a;
}); });
it->second = ptr; it->second = ptr;
@ -66,6 +71,7 @@ private:
} }
static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500}; static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500};
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<RawTextureAsset>> m_textures; std::map<CustomAssetLibrary::AssetID, std::weak_ptr<RawTextureAsset>> m_textures;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures; std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures;
std::thread m_asset_monitor_thread; std::thread m_asset_monitor_thread;
@ -75,7 +81,10 @@ private:
std::size_t m_max_memory_available = 0; std::size_t m_max_memory_available = 0;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor; std::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor;
std::mutex m_assets_lock;
// Use a recursive mutex to handle the scenario where an asset goes out of scope while
// iterating over the assets to monitor which calls the lock above in 'LoadOrCreateAsset'
std::recursive_mutex m_asset_load_lock;
Common::WorkQueueThread<std::weak_ptr<CustomAsset>> m_asset_load_thread; Common::WorkQueueThread<std::weak_ptr<CustomAsset>> m_asset_load_thread;
}; };
} // namespace VideoCommon } // namespace VideoCommon

View File

@ -27,6 +27,7 @@ std::size_t GetAssetSize(const CustomTextureData& data)
CustomAssetLibrary::TimeType CustomAssetLibrary::TimeType
DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const
{ {
std::lock_guard lk(m_lock);
if (auto iter = m_assetid_to_asset_map_path.find(asset_id); if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
iter != m_assetid_to_asset_map_path.end()) iter != m_assetid_to_asset_map_path.end())
{ {
@ -47,50 +48,47 @@ DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) con
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id, CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
CustomTextureData* data) CustomTextureData* data)
{ {
if (auto iter = m_assetid_to_asset_map_path.find(asset_id); const auto asset_map = GetAssetMapForID(asset_id);
iter != m_assetid_to_asset_map_path.end())
// Raw texture is expected to have one asset mapped
if (asset_map.empty() || asset_map.size() > 1)
return {};
const auto& asset_path = asset_map.begin()->second;
const auto last_loaded_time = std::filesystem::last_write_time(asset_path);
auto ext = asset_path.extension().string();
Common::ToLower(&ext);
if (ext == ".dds")
{ {
const auto& asset_map_path = iter->second; LoadDDSTexture(data, asset_path.string());
if (data->m_levels.empty()) [[unlikely]]
// Raw texture is expected to have one asset mapped return {};
if (asset_map_path.empty() || asset_map_path.size() > 1) if (!LoadMips(asset_path, data))
return {}; return {};
const auto& asset_path = asset_map_path.begin()->second;
const auto last_loaded_time = std::filesystem::last_write_time(asset_path); return LoadInfo{GetAssetSize(*data), last_loaded_time};
auto ext = asset_path.extension().string(); }
Common::ToLower(&ext); else if (ext == ".png")
if (ext == ".dds") {
{ // If we have no levels, create one to pass into LoadPNGTexture
LoadDDSTexture(data, asset_path.string()); if (data->m_levels.empty())
if (data->m_levels.empty()) [[unlikely]] data->m_levels.push_back({});
return {};
if (!LoadMips(asset_path, data))
return {};
return LoadInfo{GetAssetSize(*data), last_loaded_time}; if (!LoadPNGTexture(&data->m_levels[0], asset_path.string()))
} return {};
else if (ext == ".png") if (!LoadMips(asset_path, data))
{ return {};
// If we have no levels, create one to pass into LoadPNGTexture
if (data->m_levels.empty())
data->m_levels.push_back({});
if (!LoadPNGTexture(&data->m_levels[0], asset_path.string())) return LoadInfo{GetAssetSize(*data), last_loaded_time};
return {};
if (!LoadMips(asset_path, data))
return {};
return LoadInfo{GetAssetSize(*data), last_loaded_time};
}
} }
return {}; return {};
} }
void DirectFilesystemAssetLibrary::SetAssetIDMapData( void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
const AssetID& asset_id, std::map<std::string, std::filesystem::path> asset_path_map) AssetMap asset_path_map)
{ {
std::lock_guard lk(m_lock);
m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map); m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
} }
@ -145,4 +143,16 @@ bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_p
return true; return true;
} }
DirectFilesystemAssetLibrary::AssetMap
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
{
std::lock_guard lk(m_lock);
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
iter != m_assetid_to_asset_map_path.end())
{
return iter->second;
}
return {};
}
} // namespace VideoCommon } // namespace VideoCommon

View File

@ -5,6 +5,7 @@
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include <mutex>
#include <string> #include <string>
#include "VideoCommon/Assets/CustomAssetLibrary.h" #include "VideoCommon/Assets/CustomAssetLibrary.h"
@ -18,6 +19,8 @@ class CustomTextureData;
class DirectFilesystemAssetLibrary final : public CustomAssetLibrary class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
{ {
public: public:
using AssetMap = std::map<std::string, std::filesystem::path>;
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override; LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
// Gets the latest time from amongst all the files in the asset map // Gets the latest time from amongst all the files in the asset map
@ -26,12 +29,16 @@ public:
// Assigns the asset id to a map of files, how this map is read is dependent on the data // Assigns the asset id to a map of files, how this map is read is dependent on the data
// For instance, a raw texture would expect the map to have a single entry and load that // For instance, a raw texture would expect the map to have a single entry and load that
// file as the asset. But a model file data might have its data spread across multiple files // file as the asset. But a model file data might have its data spread across multiple files
void SetAssetIDMapData(const AssetID& asset_id, void SetAssetIDMapData(const AssetID& asset_id, AssetMap asset_path_map);
std::map<std::string, std::filesystem::path> asset_path_map);
private: private:
// Loads additional mip levels into the texture structure until _mip<N> texture is not found // Loads additional mip levels into the texture structure until _mip<N> texture is not found
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData* data); bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData* data);
// Gets the asset map given an asset id
AssetMap GetAssetMapForID(const AssetID& asset_id) const;
mutable std::mutex m_lock;
std::map<AssetID, std::map<std::string, std::filesystem::path>> m_assetid_to_asset_map_path; std::map<AssetID, std::map<std::string, std::filesystem::path>> m_assetid_to_asset_map_path;
}; };
} // namespace VideoCommon } // namespace VideoCommon

View File

@ -9,31 +9,35 @@ namespace VideoCommon
{ {
CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{ {
std::lock_guard lk(m_lock);
auto potential_data = std::make_shared<CustomTextureData>(); auto potential_data = std::make_shared<CustomTextureData>();
const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get()); const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get());
if (loaded_info.m_bytes_loaded == 0) if (loaded_info.m_bytes_loaded == 0)
return {}; return {};
m_loaded = true; {
m_data = std::move(potential_data); std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info; return loaded_info;
} }
CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{ {
std::lock_guard lk(m_lock);
auto potential_data = std::make_shared<CustomTextureData>(); auto potential_data = std::make_shared<CustomTextureData>();
const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get()); const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get());
if (loaded_info.m_bytes_loaded == 0) if (loaded_info.m_bytes_loaded == 0)
return {}; return {};
m_loaded = true; {
m_data = std::move(potential_data); std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info; return loaded_info;
} }
bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
{ {
std::lock_guard lk(m_lock); std::lock_guard lk(m_data_lock);
if (!m_loaded) if (!m_loaded)
{ {