diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index e236276ce9c2..c43c0027a965 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -663,6 +663,7 @@
+
@@ -1308,6 +1309,7 @@
+
diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp
index 90d77d8ec96b..ff30a14c4c1b 100644
--- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp
+++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp
@@ -13,6 +13,8 @@
#include "Common/JsonUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
+#include "Core/System.h"
+#include "VideoCommon/Assets/CustomAssetLoader.h"
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/MeshAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
@@ -53,7 +55,7 @@ std::size_t GetAssetSize(const CustomTextureData& data)
CustomAssetLibrary::TimeType
DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const
{
- std::lock_guard lk(m_lock);
+ std::lock_guard lk(m_asset_map_lock);
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
iter != m_assetid_to_asset_map_path.end())
{
@@ -436,8 +438,40 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
AssetMap asset_path_map)
{
- std::lock_guard lk(m_lock);
- m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
+ AssetMap previous_asset_map;
+ {
+ std::lock_guard lk(m_asset_map_lock);
+ previous_asset_map = m_assetid_to_asset_map_path[asset_id];
+ }
+
+ {
+ std::lock_guard lk(m_path_map_lock);
+ for (const auto& [name, path] : previous_asset_map)
+ {
+ m_path_to_asset_id.erase(PathToString(path));
+ }
+
+ for (const auto& [name, path] : asset_path_map)
+ {
+ m_path_to_asset_id[PathToString(path)] = asset_id;
+ }
+ }
+
+ {
+ std::lock_guard lk(m_asset_map_lock);
+ m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
+ }
+}
+
+void DirectFilesystemAssetLibrary::PathModified(std::string_view path)
+{
+ std::lock_guard lk(m_path_map_lock);
+ if (const auto iter = m_path_to_asset_id.find(path); iter != m_path_to_asset_id.end())
+ {
+ auto& system = Core::System::GetInstance();
+ auto& loader = system.GetCustomAssetLoader();
+ loader.ReloadAsset(iter->second);
+ }
}
bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path,
@@ -495,7 +529,7 @@ bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_p
DirectFilesystemAssetLibrary::AssetMap
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
{
- std::lock_guard lk(m_lock);
+ std::lock_guard lk(m_asset_map_lock);
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
iter != m_assetid_to_asset_map_path.end())
{
diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h
index c4d99baf82c1..c63ca77ad38e 100644
--- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h
+++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h
@@ -8,14 +8,14 @@
#include
#include
-#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include "VideoCommon/Assets/CustomTextureData.h"
+#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
namespace VideoCommon
{
// This class implements 'CustomAssetLibrary' and loads any assets
// directly from the filesystem
-class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
+class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary
{
public:
using AssetMap = std::map;
@@ -34,13 +34,18 @@ class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
void SetAssetIDMapData(const AssetID& asset_id, AssetMap asset_path_map);
private:
+ void PathModified(std::string_view path) override;
+
// Loads additional mip levels into the texture structure until _mip texture is not found
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data);
// Gets the asset map given an asset id
AssetMap GetAssetMapForID(const AssetID& asset_id) const;
- mutable std::mutex m_lock;
+ mutable std::mutex m_asset_map_lock;
std::map> m_assetid_to_asset_map_path;
+
+ mutable std::mutex m_path_map_lock;
+ std::map> m_path_to_asset_id;
};
} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp
new file mode 100644
index 000000000000..4808463b4a0f
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp
@@ -0,0 +1,57 @@
+// Copyright 2024 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
+
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+
+namespace VideoCommon
+{
+void WatchableFilesystemAssetLibrary::Watch(const std::string& path)
+{
+ const auto [iter, inserted] = m_watched_paths.try_emplace(path, nullptr);
+ if (inserted)
+ {
+ iter->second = std::make_unique(path, [this](wtr::event e) {
+ if (e.path_type == wtr::event::path_type::watcher)
+ {
+ return;
+ }
+
+ if (e.effect_type == wtr::event::effect_type::create)
+ {
+ const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
+ PathAdded(path);
+ }
+ else if (e.effect_type == wtr::event::effect_type::modify)
+ {
+ const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
+ PathModified(path);
+ }
+ else if (e.effect_type == wtr::event::effect_type::rename)
+ {
+ if (!e.associated)
+ {
+ WARN_LOG_FMT(VIDEO, "Rename on path seen without association!");
+ return;
+ }
+
+ const auto old_path = WithUnifiedPathSeparators(PathToString(e.path_name));
+ const auto new_path = WithUnifiedPathSeparators(PathToString(e.associated->path_name));
+ PathRenamed(old_path, new_path);
+ }
+ else if (e.effect_type == wtr::event::effect_type::destroy)
+ {
+ const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
+ PathDeleted(path);
+ }
+ });
+ }
+}
+
+void WatchableFilesystemAssetLibrary::Unwatch(const std::string& path)
+{
+ m_watched_paths.erase(path);
+}
+} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h
new file mode 100644
index 000000000000..947127a469fa
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h
@@ -0,0 +1,38 @@
+// Copyright 2024 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include