mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-04-24 11:22:04 +00:00
refactor(cleanup): write configuration serializer
This commit is contained in:
parent
70a1bf3092
commit
781cde93bd
9 changed files with 272 additions and 320 deletions
|
|
@ -40,36 +40,69 @@ namespace ls {
|
|||
Pacing pacing{Pacing::None};
|
||||
};
|
||||
|
||||
/// automatically updating configuration
|
||||
class Configuration {
|
||||
/// parsed configuration file
|
||||
class ConfigFile {
|
||||
public:
|
||||
/// create a new configuration
|
||||
/// create a default configuration file at the given path
|
||||
/// @param path path to configuration file
|
||||
/// @throws ls::error on failure
|
||||
Configuration();
|
||||
static void createDefaultConfigFile(const std::filesystem::path& path);
|
||||
|
||||
/// check if the configuration is out of date
|
||||
/// load the default configuration
|
||||
/// @throws ls::error on failure
|
||||
/// @return true if the configuration is out of date
|
||||
bool isUpToDate();
|
||||
|
||||
/// reload the configuration from disk
|
||||
ConfigFile();
|
||||
/// load configuration from file
|
||||
/// @param path path to configuration file
|
||||
/// @throws ls::error on failure
|
||||
void reload();
|
||||
ConfigFile(const std::filesystem::path& path);
|
||||
|
||||
/// get the global configuration
|
||||
/// @return global configuration
|
||||
[[nodiscard]] const GlobalConf& getGlobalConf() const { return global; }
|
||||
|
||||
[[nodiscard]] auto& global() { return this->globalConf; }
|
||||
/// get the game profiles
|
||||
/// @return list of game profiles
|
||||
[[nodiscard]] const std::vector<GameConf>& getProfiles() const { return profiles; }
|
||||
private:
|
||||
std::filesystem::path path;
|
||||
std::chrono::time_point<std::chrono::file_clock> timestamp;
|
||||
bool from_env{};
|
||||
[[nodiscard]] auto& profiles() { return this->profileConfs; }
|
||||
|
||||
GlobalConf global;
|
||||
std::vector<GameConf> profiles;
|
||||
/// get the global configuration
|
||||
/// @return global configuration
|
||||
[[nodiscard]] const auto& global() const { return this->globalConf; }
|
||||
/// get the game profiles
|
||||
/// @return list of game profiles
|
||||
[[nodiscard]] const auto& profiles() const { return this->profileConfs; }
|
||||
|
||||
/// write the configuration back to file
|
||||
/// @param path path to configuration file
|
||||
/// @throws ls::error on failure
|
||||
void write(const std::filesystem::path& path) const;
|
||||
private:
|
||||
GlobalConf globalConf{};
|
||||
std::vector<GameConf> profileConfs;
|
||||
};
|
||||
|
||||
/// configuration watcher with additional environment support
|
||||
class WatchedConfig {
|
||||
public:
|
||||
/// create a new configuration watcher
|
||||
/// @throws ls::error on failure
|
||||
WatchedConfig();
|
||||
|
||||
/// reload the configuration from disk if it has changed
|
||||
/// @throws ls::error on failure
|
||||
/// @return true if the configuration was reloaded
|
||||
bool update();
|
||||
|
||||
/// access the underlying configuration file
|
||||
/// @return configuration file
|
||||
[[nodiscard]] const auto& get() const { return this->configFile; }
|
||||
private:
|
||||
ConfigFile configFile;
|
||||
|
||||
std::filesystem::path path;
|
||||
std::chrono::time_point<std::chrono::file_clock> last_timestamp;
|
||||
};
|
||||
|
||||
/// find the configuration file in the most common locations
|
||||
/// @return path to configuration file
|
||||
std::filesystem::path findConfigurationFile();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,5 +36,5 @@ namespace ls {
|
|||
/// @param id identification data
|
||||
/// @return ident pair if found
|
||||
std::optional<std::pair<IdentType, GameConf>> findProfile(
|
||||
const Configuration& config, const Identification& id);
|
||||
const ConfigFile& config, const Identification& id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#include "lsfg-vk-common/configuration/config.hpp"
|
||||
#include "lsfg-vk-common/helpers/errors.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
|
@ -9,37 +11,76 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define TOML_ENABLE_FORMATTERS 0
|
||||
#include <toml.hpp>
|
||||
|
||||
using namespace ls;
|
||||
|
||||
void ConfigFile::createDefaultConfigFile(const std::filesystem::path& path) {
|
||||
try {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
if (!std::filesystem::exists(path.parent_path()))
|
||||
throw ls::error("unable to create configuration directory");
|
||||
|
||||
std::ofstream ofs(path);
|
||||
if (!ofs.is_open())
|
||||
throw ls::error("unable to create default configuration file");
|
||||
|
||||
ofs << R"(version = 2
|
||||
|
||||
[global]
|
||||
# dll = '/media/games/Lossless Scaling/Lossless.dll' # if you don't have LS in the default location
|
||||
allow_fp16 = true # this will improve give a MASSIVE performance boost on AMD, but be super slow on older (!) NVIDIA GPUs
|
||||
|
||||
[[profile]]
|
||||
name = "4x FG / 85% [Performance]"
|
||||
active_in = [ # see the wiki for more info
|
||||
'vkcube',
|
||||
'vkcubepp'
|
||||
]
|
||||
# gpu = 'NVIDIA GeForce RTX 5080' # see the wiki for more info
|
||||
multiplier = 4
|
||||
flow_scale = 0.85
|
||||
performance_mode = true
|
||||
pacing = 'none' # see the wiki for more info
|
||||
|
||||
[[profile]]
|
||||
name = "2x FG / 100%"
|
||||
active_in = 'GenshinImpact.exe'
|
||||
gpu = 'NVIDIA GeForce RTX 5080'
|
||||
multiplier = 2
|
||||
)";
|
||||
ofs.close();
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
throw ls::error("unable to create default configuration file", e);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigFile::ConfigFile() {
|
||||
this->globalConf = {
|
||||
.allow_fp16 = true
|
||||
};
|
||||
this->profileConfs.emplace_back(GameConf {
|
||||
.name = "4x FG / 85% [Performance]",
|
||||
.active_in = {
|
||||
"vkcube",
|
||||
"vkcubepp"
|
||||
},
|
||||
.multiplier = 4,
|
||||
.flow_scale = 0.85F,
|
||||
.performance_mode = true,
|
||||
.pacing = Pacing::None
|
||||
});
|
||||
this->profileConfs.emplace_back(GameConf {
|
||||
.name = "2x FG / 100%",
|
||||
.active_in = {
|
||||
"GenshinImpact.exe"
|
||||
},
|
||||
.gpu = "NVIDIA GeForce RTX 5080",
|
||||
.multiplier = 2
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr char const* DEFAULT_CONFIG = R"(version = 2
|
||||
|
||||
[global]
|
||||
# dll = '/media/games/Lossless Scaling/Lossless.dll' # if you don't have LS in the default location
|
||||
allow_fp16 = true # this will improve give a MASSIVE performance boost on AMD, but be super slow on older (!) NVIDIA GPUs
|
||||
|
||||
[[profile]]
|
||||
name = "4x FG / 85% [Performance]"
|
||||
active_in = [ # see the wiki for more info
|
||||
'vkcube',
|
||||
'vkcubepp'
|
||||
]
|
||||
# gpu = 'NVIDIA GeForce RTX 5080' # see the wiki for more info
|
||||
multiplier = 4
|
||||
flow_scale = 0.85
|
||||
performance_mode = true
|
||||
pacing = 'none' # see the wiki for more info
|
||||
|
||||
[[profile]]
|
||||
name = "2x FG / 100%"
|
||||
active_in = 'GenshinImpact.exe'
|
||||
gpu = 'NVIDIA GeForce RTX 5080'
|
||||
multiplier = 2
|
||||
)";
|
||||
|
||||
/// parse an activity array from toml value
|
||||
std::vector<std::string> activityFromString(const toml::node_view<const toml::node>& val) {
|
||||
std::vector<std::string> active_in{};
|
||||
|
|
@ -63,28 +104,6 @@ multiplier = 2
|
|||
return Pacing::None;
|
||||
throw ls::error("unknown pacing method: " + str);
|
||||
}
|
||||
/// try to find the config
|
||||
std::filesystem::path findPath() {
|
||||
// always honor LSFGVK_CONFIG if set
|
||||
const char* envPath = std::getenv("LSFGVK_CONFIG");
|
||||
if (envPath && *envPath != '\0')
|
||||
return{envPath};
|
||||
|
||||
// then check the XDG overriden location
|
||||
const char* xdgPath = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdgPath && *xdgPath != '\0')
|
||||
return std::filesystem::path(xdgPath)
|
||||
/ "lsfg-vk" / "conf.toml";
|
||||
|
||||
// fallback to typical user home
|
||||
const char* homePath = std::getenv("HOME");
|
||||
if (homePath && *homePath != '\0')
|
||||
return std::filesystem::path(homePath)
|
||||
/ ".config" / "lsfg-vk" / "conf.toml";
|
||||
|
||||
// finally, use system-wide config
|
||||
return "/etc/lsfg-vk/conf.toml";
|
||||
}
|
||||
/// parse the global configuration
|
||||
GlobalConf parseGlobalConf(const toml::table& tbl) {
|
||||
const GlobalConf conf{
|
||||
|
|
@ -168,77 +187,131 @@ multiplier = 2
|
|||
}
|
||||
}
|
||||
|
||||
Configuration::Configuration() :
|
||||
path(findPath()),
|
||||
from_env(std::getenv("LSFGVK_ENV") != nullptr) {
|
||||
if (this->from_env) {
|
||||
this->global = parseGlobalConfFromEnv();
|
||||
this->profiles.push_back(parseGameConfFromEnv());
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(this->path))
|
||||
return;
|
||||
|
||||
ConfigFile::ConfigFile(const std::filesystem::path& path) {
|
||||
toml::table table;
|
||||
try {
|
||||
std::filesystem::create_directories(this->path.parent_path());
|
||||
if (!std::filesystem::exists(this->path.parent_path()))
|
||||
throw ls::error("unable to create configuration directory");
|
||||
|
||||
std::ofstream ofs(this->path);
|
||||
if (!ofs.is_open())
|
||||
throw ls::error("unable to create default configuration file");
|
||||
|
||||
ofs << DEFAULT_CONFIG;
|
||||
ofs.close();
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
throw ls::error("unable to create default configuration file", e);
|
||||
}
|
||||
}
|
||||
|
||||
bool Configuration::isUpToDate() {
|
||||
if (this->from_env)
|
||||
return true;
|
||||
|
||||
try {
|
||||
return std::filesystem::last_write_time(this->path) == this->timestamp;
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
throw ls::error("unable to access configuration file", e);
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::reload() {
|
||||
try {
|
||||
this->timestamp = std::filesystem::last_write_time(this->path);
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
throw ls::error("unable to access configuration file", e);
|
||||
}
|
||||
|
||||
GlobalConf global{};
|
||||
std::vector<GameConf> profiles{};
|
||||
|
||||
toml::table tbl;
|
||||
try {
|
||||
tbl = toml::parse_file(this->path.string());
|
||||
table = toml::parse_file(path.string());
|
||||
} catch (const toml::parse_error& e) {
|
||||
throw ls::error("unable to parse configuration", e);
|
||||
}
|
||||
|
||||
auto vrs = tbl["version"];
|
||||
if (!vrs || !vrs.is_integer() || *vrs.as_integer() != 2)
|
||||
auto version = table["version"];
|
||||
if (!version || !version.is_integer() || *version.as_integer() != 2)
|
||||
throw ls::error("unsupported configuration version");
|
||||
|
||||
auto gbl = tbl["global"];
|
||||
if (gbl && gbl.is_table()) {
|
||||
global = parseGlobalConf(*gbl.as_table());
|
||||
auto global = table["global"];
|
||||
if (global && global.is_table()) {
|
||||
this->globalConf = parseGlobalConf(*global.as_table());
|
||||
}
|
||||
|
||||
auto pfls = tbl["profile"];
|
||||
if (pfls && pfls.is_array_of_tables()) {
|
||||
for (const auto& pfl : *pfls.as_array())
|
||||
profiles.push_back(parseGameConf(*pfl.as_table()));
|
||||
}
|
||||
|
||||
this->global = std::move(global);
|
||||
this->profiles = std::move(profiles);
|
||||
auto profiles = table["profile"];
|
||||
if (profiles && profiles.is_array_of_tables())
|
||||
for (const auto& profile : *profiles.as_array())
|
||||
this->profileConfs.push_back(parseGameConf(*profile.as_table()));
|
||||
}
|
||||
|
||||
void ConfigFile::write(const std::filesystem::path& path) const {
|
||||
toml::table table;
|
||||
table.insert("version", 2);
|
||||
|
||||
toml::table global;
|
||||
if (this->globalConf.dll)
|
||||
global.insert("dll", *this->globalConf.dll);
|
||||
global.insert("allow_fp16", this->globalConf.allow_fp16);
|
||||
table.insert("global", global);
|
||||
|
||||
toml::array profiles;
|
||||
for (const auto& conf : this->profileConfs) {
|
||||
toml::table profile;
|
||||
profile.insert("name", conf.name);
|
||||
|
||||
if (!conf.active_in.empty()) {
|
||||
if (conf.active_in.size() == 1) {
|
||||
profile.insert("active_in", conf.active_in.front());
|
||||
} else {
|
||||
toml::array active_in;
|
||||
for (const auto& entry : conf.active_in)
|
||||
active_in.push_back(entry);
|
||||
profile.insert("active_in", active_in);
|
||||
}
|
||||
}
|
||||
if (conf.gpu)
|
||||
profile.insert("gpu", conf.gpu.value_or(""));
|
||||
profile.insert("multiplier", static_cast<int64_t>(conf.multiplier));
|
||||
profile.insert("flow_scale", conf.flow_scale);
|
||||
profile.insert("performance_mode", conf.performance_mode);
|
||||
switch (conf.pacing) {
|
||||
case Pacing::None:
|
||||
profile.insert("pacing", "none");
|
||||
break;
|
||||
}
|
||||
|
||||
profiles.push_back(profile);
|
||||
}
|
||||
table.insert("profile", profiles);
|
||||
|
||||
try {
|
||||
std::ofstream ofs(path);
|
||||
if (!ofs.is_open())
|
||||
throw ls::error("unable to open configuration file for writing");
|
||||
|
||||
ofs << toml::toml_formatter {
|
||||
table,
|
||||
toml::v3::format_flags::relaxed_float_precision
|
||||
} << '\n';
|
||||
ofs.close();
|
||||
} catch (const std::exception& e) {
|
||||
throw ls::error("unable to write configuration file", e);
|
||||
}
|
||||
}
|
||||
|
||||
WatchedConfig::WatchedConfig() : path(findConfigurationFile()) {
|
||||
if (std::getenv("LSFGVK_ENV")) {
|
||||
auto& config = this->configFile;
|
||||
config.global() = parseGlobalConfFromEnv();
|
||||
config.profiles().push_back(parseGameConfFromEnv());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(this->path))
|
||||
ConfigFile::createDefaultConfigFile(this->path);
|
||||
|
||||
this->configFile = ConfigFile(this->path);
|
||||
}
|
||||
|
||||
bool WatchedConfig::update() {
|
||||
if (std::getenv("LSFGVK_ENV"))
|
||||
return false;
|
||||
|
||||
const auto now = std::filesystem::last_write_time(this->path);
|
||||
if (now == this->last_timestamp)
|
||||
return false;
|
||||
|
||||
ConfigFile new_config{this->path};
|
||||
this->last_timestamp = now;
|
||||
this->configFile = std::move(new_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path ls::findConfigurationFile() {
|
||||
// always honor LSFGVK_CONFIG if set
|
||||
const char* envPath = std::getenv("LSFGVK_CONFIG");
|
||||
if (envPath && *envPath != '\0')
|
||||
return{envPath};
|
||||
|
||||
// then check the XDG overriden location
|
||||
const char* xdgPath = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdgPath && *xdgPath != '\0')
|
||||
return std::filesystem::path(xdgPath)
|
||||
/ "lsfg-vk" / "conf.toml";
|
||||
|
||||
// fallback to typical user home
|
||||
const char* homePath = std::getenv("HOME");
|
||||
if (homePath && *homePath != '\0')
|
||||
return std::filesystem::path(homePath)
|
||||
/ ".config" / "lsfg-vk" / "conf.toml";
|
||||
|
||||
// finally, use system-wide config
|
||||
return "/etc/lsfg-vk/conf.toml";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,12 +83,12 @@ Identification ls::identify() {
|
|||
}
|
||||
|
||||
std::optional<std::pair<IdentType, GameConf>> ls::findProfile(
|
||||
const Configuration& config, const Identification& id) {
|
||||
const auto& profiles = config.getProfiles();
|
||||
const ConfigFile& config, const Identification& id) {
|
||||
const auto& profiles = config.profiles();
|
||||
|
||||
// check for the environment option first
|
||||
if (std::getenv("LSFGVK_ENV") != nullptr)
|
||||
return std::make_pair(IdentType::OVERRIDE, config.getProfiles().front());
|
||||
return std::make_pair(IdentType::OVERRIDE, profiles.front());
|
||||
|
||||
// then override first
|
||||
if (id.override.has_value()) {
|
||||
|
|
|
|||
|
|
@ -84,8 +84,7 @@ namespace {
|
|||
|
||||
Root::Root() {
|
||||
// find active profile
|
||||
this->config.reload();
|
||||
const auto& profile = findProfile(this->config, ls::identify());
|
||||
const auto& profile = findProfile(this->config.get(), ls::identify());
|
||||
if (!profile.has_value())
|
||||
return;
|
||||
|
||||
|
|
@ -109,8 +108,7 @@ Root::Root() {
|
|||
}
|
||||
|
||||
void Root::update() {
|
||||
if (!this->config.isUpToDate())
|
||||
this->config.reload();
|
||||
this->config.update();
|
||||
}
|
||||
|
||||
void Root::modifyInstanceCreateInfo(VkInstanceCreateInfo& createInfo,
|
||||
|
|
@ -202,7 +200,7 @@ void Root::createSwapchainContext(const vk::Vulkan& vk,
|
|||
const auto& profile = *this->active_profile;
|
||||
|
||||
if (!this->backend.has_value()) { // emplace backend late, due to loader bug
|
||||
const auto& global = this->config.getGlobalConf();
|
||||
const auto& global = this->config.get().global();
|
||||
|
||||
setenv("DISABLE_LSFGVK", "1", 1); // NOLINT (c++-include)
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace lsfgvk::layer {
|
|||
/// @param swapchain swapchain handle
|
||||
void removeSwapchainContext(VkSwapchainKHR swapchain);
|
||||
private:
|
||||
ls::Configuration config;
|
||||
ls::WatchedConfig config;
|
||||
std::optional<ls::GameConf> active_profile;
|
||||
|
||||
ls::lazy<lsfgvk::backend::Instance> backend;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ set_target_properties(lsfg-vk-ui PROPERTIES
|
|||
target_compile_options(lsfg-vk-ui PRIVATE
|
||||
-Wno-ctad-maybe-unsupported
|
||||
-Wno-unsafe-buffer-usage-in-libc-call
|
||||
-Wno-global-constructors)
|
||||
-Wno-global-constructors
|
||||
-Wno-unsafe-buffer-usage)
|
||||
|
||||
target_link_libraries(lsfg-vk-ui
|
||||
PUBLIC lsfg-vk-common
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <QStringList>
|
||||
#include <QString>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -12,11 +14,20 @@ using namespace lsfgvk::ui;
|
|||
|
||||
Backend::Backend() {
|
||||
// load configuration
|
||||
ls::Configuration config;
|
||||
config.reload();
|
||||
ls::ConfigFile config{};
|
||||
|
||||
this->m_global = config.getGlobalConf();
|
||||
this->m_profiles = config.getProfiles();
|
||||
auto path = ls::findConfigurationFile();
|
||||
if (std::filesystem::exists(path)) {
|
||||
try {
|
||||
config = ls::ConfigFile(path);
|
||||
} catch (const std::exception&) {
|
||||
std::cerr << "the configuration file is invalid, it has been backed up to '.old'\n";
|
||||
std::filesystem::rename(path, path.string() + ".old");
|
||||
}
|
||||
}
|
||||
|
||||
this->m_global = config.global();
|
||||
this->m_profiles = config.profiles();
|
||||
|
||||
// create profile list model
|
||||
QStringList profiles; // NOLINT (IWYU)
|
||||
|
|
@ -30,14 +41,22 @@ Backend::Backend() {
|
|||
this->m_profile_index = 0;
|
||||
|
||||
// spawn saving thread
|
||||
std::thread([this]() {
|
||||
std::thread([this, path]() {
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
if (!this->m_dirty.exchange(false))
|
||||
continue;
|
||||
|
||||
std::cerr << "configuration updated >:3" << '\n';
|
||||
ls::ConfigFile config{};
|
||||
config.global() = this->m_global;
|
||||
config.profiles() = this->m_profiles;
|
||||
|
||||
try {
|
||||
config.write(path);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "unable to write configuration:\n- " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
|
|
|||
172
ui/src/config.rs
172
ui/src/config.rs
|
|
@ -1,172 +0,0 @@
|
|||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub mod structs;
|
||||
pub use structs::*;
|
||||
|
||||
/// Find the configuration file path based on environment variables
|
||||
fn find_config_file() -> String {
|
||||
if let Some(path) = std::env::var("LSFG_CONFIG").ok() {
|
||||
return path;
|
||||
}
|
||||
|
||||
if let Some(xdg) = std::env::var("XDG_CONFIG_HOME").ok() {
|
||||
return format!("{}/lsfg-vk/conf.toml", xdg);
|
||||
}
|
||||
|
||||
if let Some(home) = std::env::var("HOME").ok() {
|
||||
return format!("{}/.config/lsfg-vk/conf.toml", home);
|
||||
}
|
||||
|
||||
"conf.toml".to_string()
|
||||
}
|
||||
|
||||
static CONFIG: OnceLock<Arc<RwLock<TomlConfig>>> = OnceLock::new();
|
||||
static CONFIG_WRITER: OnceLock<std::sync::mpsc::Sender<()>> = OnceLock::new();
|
||||
|
||||
pub fn default_config() -> TomlConfig {
|
||||
TomlConfig {
|
||||
version: 1,
|
||||
global: TomlGlobal {
|
||||
dll: None,
|
||||
no_fp16: false
|
||||
},
|
||||
game: vec![
|
||||
TomlGame {
|
||||
exe: String::from("vkcube"),
|
||||
multiplier: Multiplier::from(4),
|
||||
flow_scale: FlowScale::from(0.7),
|
||||
performance_mode: true,
|
||||
hdr_mode: false,
|
||||
experimental_present_mode: PresentMode::Vsync,
|
||||
},
|
||||
TomlGame {
|
||||
exe: String::from("benchmark"),
|
||||
multiplier: Multiplier::from(4),
|
||||
flow_scale: FlowScale::from(1.0),
|
||||
performance_mode: true,
|
||||
hdr_mode: false,
|
||||
experimental_present_mode: PresentMode::Vsync,
|
||||
},
|
||||
TomlGame {
|
||||
exe: String::from("GenshinImpact.exe"),
|
||||
multiplier: Multiplier::from(3),
|
||||
flow_scale: FlowScale::from(1.0),
|
||||
performance_mode: false,
|
||||
hdr_mode: false,
|
||||
experimental_present_mode: PresentMode::Vsync,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Load the configuration from the file and create a writer.
|
||||
///
|
||||
pub fn load_config() -> Result<(), anyhow::Error> {
|
||||
// load the configuration file
|
||||
let path = find_config_file();
|
||||
if !std::path::Path::new(&path).exists() {
|
||||
let conf = default_config();
|
||||
save_config(&conf)
|
||||
.context("Failed to create default configuration")?;
|
||||
}
|
||||
let data = std::fs::read(path)
|
||||
.context("Failed to read conf.toml")?;
|
||||
let mut config: TomlConfig = toml::from_slice(&data)
|
||||
.context("Failed to parse conf.toml")?;
|
||||
|
||||
// remove duplicate entries
|
||||
config.game.sort_by_key(|e| e.exe.clone());
|
||||
config.game.dedup_by_key(|e| e.exe.clone());
|
||||
config.game.retain(|e| !e.exe.is_empty());
|
||||
|
||||
// create the configuration writer thread
|
||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||
CONFIG.set(Arc::new(RwLock::new(config)))
|
||||
.ok().context("Failed to set configuration state")?;
|
||||
CONFIG_WRITER.set(tx)
|
||||
.ok().context("Failed to set configuration writer")?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let config = CONFIG.get().unwrap();
|
||||
loop {
|
||||
// wait for a signal to write the configuration
|
||||
if let Err(_) = rx.recv() {
|
||||
break;
|
||||
}
|
||||
|
||||
// wait a bit to avoid excessive writes
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
|
||||
// empty the channel
|
||||
while rx.try_recv().is_ok() {}
|
||||
|
||||
// write the configuration
|
||||
if let Ok(config) = config.try_read() {
|
||||
if let Err(e) = save_config(&config) {
|
||||
eprintln!("Failed to save configuration: {}", e);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to read configuration state");
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Get a snapshot of the current configuration
|
||||
///
|
||||
pub fn get_config() -> Result<TomlConfig, anyhow::Error> {
|
||||
let conf = CONFIG.get()
|
||||
.expect("Configuration not loaded")
|
||||
.try_read()
|
||||
.map(|config| config.clone());
|
||||
if let Ok(config) = conf {
|
||||
return Ok(config)
|
||||
}
|
||||
|
||||
anyhow::bail!("Failed to read configuration state")
|
||||
}
|
||||
|
||||
///
|
||||
/// Safely edit the configuration.
|
||||
///
|
||||
pub fn edit_config<F>(f: F) -> Result<(), anyhow::Error>
|
||||
where
|
||||
F: FnOnce(&mut TomlConfig)
|
||||
{
|
||||
let mut config = CONFIG.get()
|
||||
.expect("Configuration not loaded")
|
||||
.write()
|
||||
.map_err(|_| anyhow::anyhow!("Failed to acquire write lock on configuration"))?;
|
||||
|
||||
f(&mut config);
|
||||
|
||||
CONFIG_WRITER.get().unwrap().send(())
|
||||
.context("Failed to send configuration update signal")
|
||||
}
|
||||
|
||||
///
|
||||
/// Save the configuration to the file
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `config` - The configuration to save
|
||||
///
|
||||
pub fn save_config(config: &TomlConfig) -> Result<(), anyhow::Error> {
|
||||
let path = find_config_file();
|
||||
|
||||
let parent = std::path::Path::new(&path).parent()
|
||||
.context("Failed to get parent directory of config path")?;
|
||||
std::fs::create_dir_all(parent)
|
||||
.context("Failed to create config directory")?;
|
||||
|
||||
let data = toml::to_string(config)
|
||||
.context("Failed to serialize conf.toml")?;
|
||||
std::fs::write(path, data)
|
||||
.context("Failed to write conf.toml")?;
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue