Files
LibretroAdmin d87659dbcb Externals: remove watcher, and live reload of custom textures
watcher ran a thread per watched directory (the texture pack's, the
graphics mods') so that a texture file changed on disk while a game
ran was loaded again; on Windows its backend throws, so its file was
the one built with exceptions. A texture pack or mod is now read as the
game starts, and a change to it is seen at the next start.

With it go Common::FilesystemWatcher, the library's map from file to
asset kept only to answer the watcher, and the asset cache's dirty
asset queue that only the watcher filled.
2026-11-11 09:04:28 +01:00

159 lines
4.2 KiB
C++

// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/CustomAssetCache.h"
#include "Common/Logging/Log.h"
#include "Common/MemoryUtil.h"
#include "UICommon/UICommon.h"
#include "VideoCommon/Assets/CustomAsset.h"
namespace VideoCommon
{
void CustomAssetCache::Initialize()
{
// Use half of available system memory but leave at least 2GiB unused for system stability.
constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024);
const size_t sys_mem = Common::MemPhysical();
const size_t keep_unused_mem = std::max(sys_mem / 2, std::min(sys_mem, must_keep_unused));
m_max_ram_available = sys_mem - keep_unused_mem;
if (m_max_ram_available == 0)
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
m_asset_loader.Initialize();
}
void CustomAssetCache::Shutdown()
{
Reset();
m_asset_loader.Shutdown();
}
void CustomAssetCache::Reset()
{
m_asset_loader.Reset(true);
m_active_assets = {};
m_pending_assets = {};
m_asset_handle_to_data.clear();
m_asset_id_to_handle.clear();
m_ram_used = 0;
}
void CustomAssetCache::MarkAssetPending(CustomAsset* asset)
{
m_pending_assets.MakeAssetHighestPriority(asset->GetHandle(), asset);
}
void CustomAssetCache::MarkAssetActive(CustomAsset* asset)
{
m_active_assets.MakeAssetHighestPriority(asset->GetHandle(), asset);
}
void CustomAssetCache::Update()
{
ProcessLoadedAssets();
if (m_ram_used > m_max_ram_available)
{
RemoveAssetsUntilBelowMemoryLimit();
}
if (m_pending_assets.IsEmpty())
return;
if (m_ram_used > m_max_ram_available)
return;
const u64 allowed_memory = m_max_ram_available - m_ram_used;
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
}
void CustomAssetCache::ProcessLoadedAssets()
{
const auto load_results = m_asset_loader.TakeLoadResults();
// Update the ram with the change in memory from the loader
//
// Note: Assets with outstanding reload requests will have
// two copies in memory temporarily (the old data stored in
// the asset shared_ptr that the resource manager owns, and
// the new data loaded from the loader in the asset's shared_ptr)
// This temporary duplication will not be reflected in the
// resource manager's ram used
m_ram_used += load_results.change_in_memory;
for (const auto& [handle, load_successful] : load_results.asset_handles)
{
AssetData& asset_data = m_asset_handle_to_data[handle];
// If we have a reload request that is newer than our loaded time
// we need to wait for another reload.
if (asset_data.load_request_time > asset_data.asset->GetLastLoadedTime())
continue;
m_pending_assets.RemoveAsset(handle);
asset_data.load_request_time = {};
if (!load_successful)
{
asset_data.has_load_error = true;
}
else
{
m_active_assets.InsertAsset(handle, asset_data.asset.get());
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
}
for (const auto& listener : asset_data.listeners)
{
if (load_successful)
listener->NotifyAssetLoadSuccess();
else
listener->NotifyAssetLoadFailed();
}
}
}
void CustomAssetCache::RemoveAssetsUntilBelowMemoryLimit()
{
const u64 threshold_ram = m_max_ram_available * 8 / 10;
if (m_ram_used > threshold_ram)
{
INFO_LOG_FMT(VIDEO, "Memory usage over threshold: {}", UICommon::FormatSize(m_ram_used));
}
// Clear out least recently used resources until
// we get safely in our threshold
while (m_ram_used > threshold_ram && m_active_assets.Size() > 0)
{
auto* const asset = m_active_assets.RemoveLowestPriorityAsset();
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
for (const auto& listener : asset_data.listeners)
{
listener->AssetUnloaded();
}
// Remove the asset's copy
const std::size_t bytes_unloaded = asset_data.asset->Unload();
m_ram_used -= bytes_unloaded;
asset_data.load_status = AssetData::LoadStatus::Unloaded;
asset_data.load_request_time = {};
INFO_LOG_FMT(VIDEO, "Unloading asset: {} ({})", asset_data.asset->GetAssetId(),
UICommon::FormatSize(bytes_unloaded));
}
}
} // namespace VideoCommon