Emu: Cache games.yml and only save when necessary

This commit is contained in:
Megamouse 2023-04-21 15:02:39 +02:00
parent 500f7901ac
commit 2c71d08ea2
8 changed files with 188 additions and 93 deletions

View File

@ -1,5 +1,6 @@
add_library(rpcs3_emu
cache_utils.cpp
games_config.cpp
IdManager.cpp
localized_string.cpp
savestate_utils.cpp

View File

@ -37,7 +37,6 @@
#include "Utilities/StrUtil.h"
#include "../Crypto/unself.h"
#include "util/yaml.hpp"
#include "util/logs.hpp"
#include "util/serialization.hpp"
@ -875,26 +874,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
{
Init(add_only);
// Load game list (maps ABCD12345 IDs to /dev_bdvd/ locations)
YAML::Node games;
if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create})
{
auto [result, error] = yaml_load(f.to_string());
if (!error.empty())
{
sys_log.error("Failed to load games.yml: %s", error);
}
games = result;
}
if (!games.IsMap())
{
games.reset();
}
m_state_inspection_savestate = g_cfg.savestate.state_inspection_mode.get();
m_savestate_extension_flags1 = {};
@ -956,9 +935,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
m_title_id = disc_info;
// Load /dev_bdvd/ from game list if available
if (auto node = games[m_title_id])
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
disc = node.Scalar();
disc = std::move(game_path);
}
else if (!g_cfg.savestate.state_inspection_mode)
{
@ -1070,9 +1049,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
std::string title_path;
// const overload does not create new node on failure
if (auto node = std::as_const(games)[m_title_id])
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
title_path = node.Scalar();
title_path = std::move(game_path);
}
for (std::string test_path :
@ -1113,9 +1092,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
title_id = title_id.substr(0, title_id.find_first_of('/'));
// Try to load game directory from list if available
if (auto node = (title_id.empty() ? YAML::Node{} : games[title_id]))
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
disc = node.Scalar();
disc = std::move(game_path);
m_path = disc + argv[0].substr(game0_path.size() + title_id.size());
}
}
@ -1541,9 +1520,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
if ((is_disc_patch || m_cat == "GD") && bdvd_dir.empty() && disc.empty())
{
// Load /dev_bdvd/ from game list if available
if (auto node = games[m_title_id])
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
bdvd_dir = node.Scalar();
bdvd_dir = std::move(game_path);
}
else
{
@ -1552,35 +1531,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
}
}
auto try_register_game_location = [&](const std::string& key, const std::string& loc)
{
// Access or create node if does not exist
auto node = games[key];
if (node && node.Scalar() == loc)
{
// Nothing to do
return true;
}
// Write to node
node = loc;
YAML::Emitter out;
out << games;
fs::pending_file temp(fs::get_config_dir() + "/games.yml");
// Do not update games.yml when TITLE_ID is empty
if (temp.file && temp.file.write(out.c_str(), out.size()), temp.commit())
{
m_games_yml_invalidated = true;
return true;
}
return false;
};
// Check /dev_bdvd/
if (disc.empty() && !bdvd_dir.empty() && fs::is_dir(bdvd_dir))
{
@ -1617,7 +1567,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
}
// Store /dev_bdvd/ location
if (!try_register_game_location(m_title_id, bdvd_dir))
if (m_games_config.add_game(m_title_id, bdvd_dir))
{
sys_log.notice("Registered BDVD game directory for title '%s': %s", m_title_id, bdvd_dir);
}
else
{
sys_log.error("Failed to save BDVD location of title '%s' (error=%s)", m_title_id, fs::g_tls_error);
}
@ -1675,7 +1629,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
}
// Add HG games not in HDD0 to games.yml
if (!try_register_game_location(m_title_id, game_dir))
if (m_games_config.add_game(m_title_id, game_dir))
{
sys_log.notice("Registered HG game directory for title '%s': %s", m_title_id, game_dir);
}
else
{
sys_log.error("Failed to save HG game location of title '%s' (error=%s)", m_title_id, fs::g_tls_error);
}
@ -3154,15 +3112,12 @@ void Emulator::AddGamesFromDir(const std::string& path)
if (!IsStopped())
return;
m_games_yml_invalidated = false;
m_games_config.set_save_on_dirty(false);
// search dropped path first or else the direct parent to an elf is wrongly skipped
if (const auto error = BootGame(path, "", false, true); error == game_boot_result::no_errors)
{
if (std::exchange(m_games_yml_invalidated, false))
{
sys_log.notice("Registered game directory: %s", path);
}
// Nothing to do
}
// search direct subdirectories, that way we can drop one folder containing all games
@ -3177,12 +3132,16 @@ void Emulator::AddGamesFromDir(const std::string& path)
if (const auto error = BootGame(dir_path, "", false, true); error == game_boot_result::no_errors)
{
if (std::exchange(m_games_yml_invalidated, false))
{
sys_log.notice("Registered game directory: %s", dir_path);
}
// Nothing to do
}
}
m_games_config.set_save_on_dirty(true);
if (m_games_config.is_dirty() && !m_games_config.save())
{
sys_log.error("Failed to save games.yml after adding games");
}
}
bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const

View File

@ -3,6 +3,7 @@
#include "util/types.hpp"
#include "util/atomic.hpp"
#include "Utilities/bit_set.h"
#include "games_config.h"
#include <functional>
#include <memory>
#include <string>
@ -119,7 +120,7 @@ class Emulator final
atomic_t<u64> m_pause_amend_time{0}; // increased when resumed
atomic_t<u64> m_stop_ctr{0}; // Increments when emulation is stopped
bool m_games_yml_invalidated = false;
games_config m_games_config;
video_renderer m_default_renderer;
std::string m_default_graphics_adapter;
@ -287,6 +288,11 @@ public:
return m_usr;
}
const games_config& GetGamesConfig() const
{
return m_games_config;
}
// Get deserialization manager
utils::serial* DeserialManager() const;

112
rpcs3/Emu/games_config.cpp Normal file
View File

@ -0,0 +1,112 @@
#include "stdafx.h"
#include "games_config.h"
#include "util/logs.hpp"
#include "util/yaml.hpp"
#include "Utilities/File.h"
LOG_CHANNEL(cfg_log, "CFG");
games_config::games_config()
{
load();
}
games_config::~games_config()
{
if (m_dirty)
{
save();
}
}
std::string games_config::get_path(const std::string& title_id) const
{
if (title_id.empty())
{
return {};
}
if (const auto it = m_games.find(title_id); it != m_games.cend())
{
return it->second;
}
return {};
}
bool games_config::add_game(const std::string& key, const std::string& path)
{
// Access or create node if does not exist
if (auto it = m_games.find(key); it != m_games.end())
{
if (it->second == path)
{
// Nothing to do
return true;
}
it->second = path;
}
else
{
m_games.emplace(key, path);
}
m_dirty = true;
if (m_save_on_dirty)
{
return save();
}
return true;
}
bool games_config::save()
{
YAML::Emitter out;
out << m_games;
fs::pending_file temp(fs::get_config_dir() + "/games.yml");
if (temp.file && temp.file.write(out.c_str(), out.size()), temp.commit())
{
m_dirty = false;
return true;
}
cfg_log.error("Failed to save games.yml: %s", fs::g_tls_error);
return false;
}
void games_config::load()
{
m_games.clear();
if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create})
{
auto [result, error] = yaml_load(f.to_string());
if (!error.empty())
{
cfg_log.error("Failed to load games.yml: %s", error);
}
if (!result.IsMap())
{
if (!result.IsNull())
{
cfg_log.error("Failed to load games.yml: type %d not a map", result.Type());
}
return;
}
for (const auto& entry : result)
{
if (!entry.first.Scalar().empty() && entry.second.IsScalar() && !entry.second.Scalar().empty())
{
m_games.emplace(entry.first.Scalar(), entry.second.Scalar());
}
}
}
}

28
rpcs3/Emu/games_config.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <map>
class games_config
{
public:
games_config();
virtual ~games_config();
void set_save_on_dirty(bool enabled) { m_save_on_dirty = enabled; }
const std::map<std::string, std::string>& get_games() const { return m_games; }
bool is_dirty() const { return m_dirty; }
std::string get_path(const std::string& title_id) const;
bool add_game(const std::string& key, const std::string& path);
bool save();
private:
void load();
std::map<std::string, std::string> m_games;
bool m_dirty = false;
bool m_save_on_dirty = true;
};

View File

@ -67,6 +67,7 @@
<ClCompile Include="Emu\Cell\Modules\libfs_utility_init.cpp" />
<ClCompile Include="Emu\Cell\Modules\sys_crashdump.cpp" />
<ClCompile Include="Emu\Cell\Modules\HLE_PATCHES.cpp" />
<ClCompile Include="Emu\games_config.cpp" />
<ClCompile Include="Emu\Io\camera_config.cpp" />
<ClCompile Include="Emu\Io\recording_config.cpp" />
<ClCompile Include="Emu\Io\Turntable.cpp" />
@ -497,6 +498,7 @@
<ClInclude Include="Emu\Cell\Modules\libfs_utility_init.h" />
<ClInclude Include="Emu\Cell\Modules\sys_crashdump.h" />
<ClInclude Include="Emu\CPU\sse2neon.h" />
<ClInclude Include="Emu\games_config.h" />
<ClInclude Include="Emu\Io\camera_config.h" />
<ClInclude Include="Emu\Io\camera_handler_base.h" />
<ClInclude Include="Emu\Io\music_handler_base.h" />

View File

@ -1147,6 +1147,9 @@
<ClCompile Include="Emu\RSX\Overlays\overlay_manager.cpp">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClCompile>
<ClCompile Include="Emu\games_config.cpp">
<Filter>Emu</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2311,6 +2314,9 @@
<ClInclude Include="io_buffer.h">
<Filter>Emu\GPU\RSX\Common</Filter>
</ClInclude>
<ClInclude Include="Emu\games_config.h">
<Filter>Emu</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View File

@ -516,28 +516,9 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
add_dir(_hdd + "game/", false);
add_dir(_hdd + "disc/", true); // Deprecated
auto get_games = []() -> YAML::Node
for (const auto& [serial, path] : Emu.GetGamesConfig().get_games())
{
if (const fs::file games = fs::file(fs::get_config_dir() + "/games.yml", fs::read + fs::create))
{
auto [result, error] = yaml_load(games.to_string());
if (!error.empty())
{
game_list_log.error("Failed to load games.yml: %s", error);
return {};
}
return result;
}
game_list_log.error("Failed to load games.yml, check permissions.");
return {};
};
for (auto&& pair : get_games())
{
std::string game_dir = pair.second.Scalar();
std::string game_dir = path;
game_dir.resize(game_dir.find_last_not_of('/') + 1);
@ -569,7 +550,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
// Check if the remaining part is the only path component
if (frag.find_first_of('/') + 1 == 0)
{
game_list_log.trace("Removed duplicate for %s: %s", pair.first.Scalar(), pair.second.Scalar());
game_list_log.trace("Removed duplicate for %s: %s", serial, path);
if (static std::unordered_set<std::string> warn_once_list; warn_once_list.emplace(game_dir).second)
{
@ -589,7 +570,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
}
else
{
game_list_log.trace("Invalid game path registered for %s: %s", pair.first.Scalar(), pair.second.Scalar());
game_list_log.trace("Invalid game path registered for %s: %s", serial, path);
}
}