[App/Base] Rewrite Config and CVar implementation
Adds support for "requires restart" config values [App/Base] Rewrite Config and CVar implementation Adds support for "requires restart" config values
This commit is contained in:
parent
9f789e01b6
commit
29ea7eb28e
|
@ -228,7 +228,7 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
storage_root = std::filesystem::absolute(storage_root);
|
||||
XELOGI("Storage root: {}", xe::path_to_utf8(storage_root));
|
||||
|
||||
config::SetupConfig(storage_root);
|
||||
Config::Instance().SetupConfig(storage_root);
|
||||
|
||||
std::filesystem::path content_root = cvars::content_root;
|
||||
if (content_root.empty()) {
|
||||
|
|
|
@ -18,70 +18,9 @@ namespace utfcpp = utf8;
|
|||
|
||||
using u8_citer = utfcpp::iterator<std::string_view::const_iterator>;
|
||||
|
||||
namespace xe {
|
||||
namespace cvar {
|
||||
|
||||
cxxopts::Options options("xenia", "Xbox 360 Emulator");
|
||||
std::map<std::string, ICommandVar*>* CmdVars;
|
||||
std::map<std::string, IConfigVar*>* ConfigVars;
|
||||
|
||||
void PrintHelpAndExit() {
|
||||
std::cout << options.help({""}) << std::endl;
|
||||
std::cout << "For the full list of command line arguments, see xenia.cfg."
|
||||
<< std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void ParseLaunchArguments(int& argc, char**& argv,
|
||||
const std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options) {
|
||||
options.add_options()("help", "Prints help and exit.");
|
||||
|
||||
if (!CmdVars) {
|
||||
CmdVars = new std::map<std::string, ICommandVar*>();
|
||||
}
|
||||
|
||||
if (!ConfigVars) {
|
||||
ConfigVars = new std::map<std::string, IConfigVar*>();
|
||||
}
|
||||
|
||||
for (auto& it : *CmdVars) {
|
||||
auto cmdVar = it.second;
|
||||
cmdVar->AddToLaunchOptions(&options);
|
||||
}
|
||||
|
||||
for (const auto& it : *ConfigVars) {
|
||||
auto configVar = it.second;
|
||||
configVar->AddToLaunchOptions(&options);
|
||||
}
|
||||
|
||||
try {
|
||||
options.positional_help(std::string(positional_help));
|
||||
options.parse_positional(positional_options);
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
if (result.count("help")) {
|
||||
PrintHelpAndExit();
|
||||
}
|
||||
|
||||
for (auto& it : *CmdVars) {
|
||||
auto cmdVar = static_cast<ICommandVar*>(it.second);
|
||||
if (result.count(cmdVar->name())) {
|
||||
cmdVar->LoadFromLaunchOptions(&result);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& it : *ConfigVars) {
|
||||
auto configVar = static_cast<IConfigVar*>(it.second);
|
||||
if (result.count(configVar->name())) {
|
||||
configVar->LoadFromLaunchOptions(&result);
|
||||
}
|
||||
}
|
||||
} catch (const cxxopts::OptionException& e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
PrintHelpAndExit();
|
||||
}
|
||||
}
|
||||
|
||||
namespace toml {
|
||||
|
||||
std::string EscapeBasicString(const std::string_view view) {
|
||||
|
@ -199,3 +138,4 @@ std::string EscapeString(const std::string_view view) {
|
|||
} // namespace toml
|
||||
|
||||
} // namespace cvar
|
||||
} // namespace xe
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
|
||||
#include "cpptoml/include/cpptoml.h"
|
||||
#include "cxxopts/include/cxxopts.hpp"
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/config.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cvar {
|
||||
|
||||
namespace toml {
|
||||
std::string EscapeString(const std::string_view str);
|
||||
std::string EscapeString(const std::string_view view);
|
||||
}
|
||||
|
||||
class ICommandVar {
|
||||
|
@ -40,6 +43,7 @@ class IConfigVar : virtual public ICommandVar {
|
|||
public:
|
||||
virtual const std::string& category() const = 0;
|
||||
virtual bool is_transient() const = 0;
|
||||
virtual bool requires_restart() const = 0;
|
||||
virtual std::string config_value() const = 0;
|
||||
virtual void LoadConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
|
||||
virtual void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
|
||||
|
@ -48,282 +52,173 @@ class IConfigVar : virtual public ICommandVar {
|
|||
template <class T>
|
||||
class CommandVar : virtual public ICommandVar {
|
||||
public:
|
||||
CommandVar<T>(const char* name, T* default_value, const char* description);
|
||||
const std::string& name() const override;
|
||||
const std::string& description() const override;
|
||||
CommandVar<T>(std::string_view name, T* default_value,
|
||||
std::string_view description);
|
||||
|
||||
const std::string& name() const override { return name_; }
|
||||
const std::string& description() const override { return description_; }
|
||||
|
||||
void AddToLaunchOptions(cxxopts::Options* options) override;
|
||||
void LoadFromLaunchOptions(cxxopts::ParseResult* result) override;
|
||||
|
||||
T* current_value() { return current_value_; }
|
||||
T* command_line_value() { return commandline_value_.get(); }
|
||||
const T& default_value() const { return default_value_; }
|
||||
|
||||
protected:
|
||||
void set_current_value(const T& val) { *current_value_ = val; }
|
||||
|
||||
void set_command_line_value(const T& val) {
|
||||
commandline_value_ = std::make_unique<T>(val);
|
||||
}
|
||||
|
||||
T Convert(std::string_view val) const {
|
||||
return xe::string_util::from_string<T>(val);
|
||||
}
|
||||
|
||||
static std::string ToString(const T& val) { return std::to_string(val); }
|
||||
void UpdateValue() override;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
T default_value_;
|
||||
T* current_value_;
|
||||
std::unique_ptr<T> commandline_value_;
|
||||
std::string description_;
|
||||
T Convert(std::string val);
|
||||
static std::string ToString(T val);
|
||||
void SetValue(T val);
|
||||
void SetCommandLineValue(T val);
|
||||
void UpdateValue() override;
|
||||
};
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4250)
|
||||
|
||||
template <class T>
|
||||
class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
|
||||
class ConfigVar final : public CommandVar<T>, virtual public IConfigVar {
|
||||
public:
|
||||
ConfigVar<T>(const char* name, T* default_value, const char* description,
|
||||
const char* category, bool is_transient);
|
||||
std::string config_value() const override;
|
||||
const std::string& category() const override;
|
||||
bool is_transient() const override;
|
||||
ConfigVar<T>(std::string_view name, T* default_value,
|
||||
std::string_view description, std::string_view category,
|
||||
bool is_transient, bool requires_restart);
|
||||
|
||||
std::string config_value() const override {
|
||||
if (config_value_) return this->ToString(*config_value_);
|
||||
return this->ToString(this->default_value());
|
||||
}
|
||||
|
||||
const std::string& category() const override { return category_; }
|
||||
bool is_transient() const override { return is_transient_; }
|
||||
bool requires_restart() const override { return requires_restart_; }
|
||||
|
||||
void set_config_value(const T& val) {
|
||||
config_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
void set_game_config_value(const T& val) {
|
||||
game_config_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
void AddToLaunchOptions(cxxopts::Options* options) override;
|
||||
void LoadConfigValue(std::shared_ptr<cpptoml::base> result) override;
|
||||
void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) override;
|
||||
void SetConfigValue(T val);
|
||||
void SetGameConfigValue(T val);
|
||||
|
||||
private:
|
||||
void UpdateValue() override;
|
||||
|
||||
std::string category_;
|
||||
bool is_transient_;
|
||||
bool requires_restart_;
|
||||
std::unique_ptr<T> config_value_ = nullptr;
|
||||
std::unique_ptr<T> game_config_value_ = nullptr;
|
||||
void UpdateValue() override;
|
||||
};
|
||||
|
||||
#pragma warning(pop)
|
||||
template <class T>
|
||||
const std::string& CommandVar<T>::name() const {
|
||||
return name_;
|
||||
}
|
||||
template <class T>
|
||||
const std::string& CommandVar<T>::description() const {
|
||||
return description_;
|
||||
}
|
||||
template <class T>
|
||||
void CommandVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
|
||||
options->add_options()(name_, description_, cxxopts::value<T>());
|
||||
}
|
||||
template <>
|
||||
inline void CommandVar<std::filesystem::path>::AddToLaunchOptions(
|
||||
cxxopts::Options* options) {
|
||||
options->add_options()(name_, description_, cxxopts::value<std::string>());
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
|
||||
options->add_options(category_)(this->name_, this->description_,
|
||||
cxxopts::value<T>());
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::AddToLaunchOptions(
|
||||
cxxopts::Options* options) {
|
||||
options->add_options(category_)(this->name_, this->description_,
|
||||
cxxopts::value<std::string>());
|
||||
}
|
||||
template <class T>
|
||||
void CommandVar<T>::LoadFromLaunchOptions(cxxopts::ParseResult* result) {
|
||||
T value = (*result)[name_].template as<T>();
|
||||
SetCommandLineValue(value);
|
||||
}
|
||||
template <>
|
||||
inline void CommandVar<std::filesystem::path>::LoadFromLaunchOptions(
|
||||
cxxopts::ParseResult* result) {
|
||||
std::string value = (*result)[name_].template as<std::string>();
|
||||
SetCommandLineValue(value);
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::LoadConfigValue(std::shared_ptr<cpptoml::base> result) {
|
||||
SetConfigValue(*cpptoml::get_impl<T>(result));
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::LoadConfigValue(
|
||||
std::shared_ptr<cpptoml::base> result) {
|
||||
SetConfigValue(
|
||||
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) {
|
||||
SetGameConfigValue(*cpptoml::get_impl<T>(result));
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::LoadGameConfigValue(
|
||||
std::shared_ptr<cpptoml::base> result) {
|
||||
SetGameConfigValue(
|
||||
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
|
||||
}
|
||||
template <class T>
|
||||
CommandVar<T>::CommandVar(const char* name, T* default_value,
|
||||
const char* description)
|
||||
: name_(name),
|
||||
default_value_(*default_value),
|
||||
description_(description),
|
||||
current_value_(default_value) {}
|
||||
|
||||
template <class T>
|
||||
ConfigVar<T>::ConfigVar(const char* name, T* default_value,
|
||||
const char* description, const char* category,
|
||||
bool is_transient)
|
||||
: CommandVar<T>(name, default_value, description),
|
||||
category_(category),
|
||||
is_transient_(is_transient) {}
|
||||
|
||||
template <class T>
|
||||
void CommandVar<T>::UpdateValue() {
|
||||
if (commandline_value_) return SetValue(*commandline_value_);
|
||||
return SetValue(default_value_);
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::UpdateValue() {
|
||||
if (this->commandline_value_) {
|
||||
return this->SetValue(*this->commandline_value_);
|
||||
}
|
||||
if (game_config_value_) return this->SetValue(*game_config_value_);
|
||||
if (config_value_) return this->SetValue(*config_value_);
|
||||
return this->SetValue(this->default_value_);
|
||||
}
|
||||
template <class T>
|
||||
T CommandVar<T>::Convert(std::string val) {
|
||||
return xe::string_util::from_string<T>(val);
|
||||
}
|
||||
template <>
|
||||
inline std::string CommandVar<std::string>::Convert(std::string val) {
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
inline std::filesystem::path CommandVar<std::filesystem::path>::Convert(
|
||||
std::string val) {
|
||||
return xe::to_path(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string CommandVar<bool>::ToString(bool val) {
|
||||
return val ? "true" : "false";
|
||||
}
|
||||
template <>
|
||||
inline std::string CommandVar<std::string>::ToString(std::string val) {
|
||||
return toml::EscapeString(val);
|
||||
}
|
||||
template <>
|
||||
inline std::string CommandVar<std::filesystem::path>::ToString(
|
||||
std::filesystem::path val) {
|
||||
return toml::EscapeString(
|
||||
xe::utf8::fix_path_separators(xe::path_to_utf8(val), '/'));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::string CommandVar<T>::ToString(T val) {
|
||||
return std::to_string(val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void CommandVar<T>::SetValue(T val) {
|
||||
*current_value_ = val;
|
||||
}
|
||||
template <class T>
|
||||
const std::string& ConfigVar<T>::category() const {
|
||||
return category_;
|
||||
}
|
||||
template <class T>
|
||||
bool ConfigVar<T>::is_transient() const {
|
||||
return is_transient_;
|
||||
}
|
||||
template <class T>
|
||||
std::string ConfigVar<T>::config_value() const {
|
||||
if (config_value_) return this->ToString(*config_value_);
|
||||
return this->ToString(this->default_value_);
|
||||
}
|
||||
template <class T>
|
||||
void CommandVar<T>::SetCommandLineValue(const T val) {
|
||||
commandline_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::SetConfigValue(T val) {
|
||||
config_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::SetGameConfigValue(T val) {
|
||||
game_config_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
extern std::map<std::string, ICommandVar*>* CmdVars;
|
||||
extern std::map<std::string, IConfigVar*>* ConfigVars;
|
||||
|
||||
inline void AddConfigVar(IConfigVar* cv) {
|
||||
if (!ConfigVars) ConfigVars = new std::map<std::string, IConfigVar*>();
|
||||
ConfigVars->insert(std::pair<std::string, IConfigVar*>(cv->name(), cv));
|
||||
}
|
||||
inline void AddCommandVar(ICommandVar* cv) {
|
||||
if (!CmdVars) CmdVars = new std::map<std::string, ICommandVar*>();
|
||||
CmdVars->insert(std::pair<std::string, ICommandVar*>(cv->name(), cv));
|
||||
}
|
||||
void ParseLaunchArguments(int& argc, char**& argv,
|
||||
const std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options);
|
||||
|
||||
template <typename T>
|
||||
T* define_configvar(const char* name, T* default_value, const char* description,
|
||||
const char* category, bool is_transient) {
|
||||
IConfigVar* cfgVar = new ConfigVar<T>(name, default_value, description,
|
||||
category, is_transient);
|
||||
AddConfigVar(cfgVar);
|
||||
return default_value;
|
||||
ConfigVar<T>* register_configvar(std::string_view name, T* default_value,
|
||||
std::string_view description,
|
||||
std::string_view category, bool is_transient,
|
||||
bool requires_restart) {
|
||||
// config instance claims ownership of this pointer
|
||||
ConfigVar<T>* var =
|
||||
new ConfigVar<T>{name, default_value, description,
|
||||
category, is_transient, requires_restart};
|
||||
return Config::Instance().RegisterConfigVar<T>(var);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
ICommandVar* cmdVar = new CommandVar<T>(name, default_value, description);
|
||||
AddCommandVar(cmdVar);
|
||||
return default_value;
|
||||
CommandVar<T>* register_commandvar(std::string_view name, T* default_value,
|
||||
std::string_view description) {
|
||||
// config instance claims ownership of this pointer
|
||||
CommandVar<T>* var = new CommandVar<T>{name, default_value, description};
|
||||
return Config::Instance().RegisterCommandVar<T>(var);
|
||||
}
|
||||
|
||||
#define DEFINE_bool(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, bool)
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, bool)
|
||||
|
||||
#define DEFINE_int32(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, int32_t)
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, int32_t)
|
||||
|
||||
#define DEFINE_uint64(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, uint64_t)
|
||||
#define DEFINE_uint64(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, \
|
||||
uint64_t)
|
||||
|
||||
#define DEFINE_double(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, double)
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, double)
|
||||
|
||||
#define DEFINE_string(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, std::string)
|
||||
#define DEFINE_string(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, \
|
||||
std::string)
|
||||
|
||||
#define DEFINE_path(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, \
|
||||
#define DEFINE_path(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, false, \
|
||||
std::filesystem::path)
|
||||
|
||||
#define DEFINE_unrestartable_bool(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, bool)
|
||||
|
||||
#define DEFINE_unrestartable_int32(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, int32_t)
|
||||
|
||||
#define DEFINE_unrestartable_uint64(name, default_value, description, \
|
||||
category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, uint64_t)
|
||||
|
||||
#define DEFINE_unrestartable_double(name, default_value, description, \
|
||||
category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, double)
|
||||
|
||||
#define DEFINE_unrestartable_string(name, default_value, description, \
|
||||
category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, \
|
||||
std::string)
|
||||
|
||||
#define DEFINE_unrestartable_path(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, true, \
|
||||
std::filesystem::path)
|
||||
|
||||
#define DEFINE_transient_string(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, true, std::string)
|
||||
DEFINE_CVar(name, default_value, description, category, true, false, \
|
||||
std::string)
|
||||
|
||||
#define DEFINE_transient_path(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, true, \
|
||||
DEFINE_CVar(name, default_value, description, category, true, false, \
|
||||
std::filesystem::path)
|
||||
|
||||
#define DEFINE_CVar(name, default_value, description, category, is_transient, \
|
||||
type) \
|
||||
namespace cvars { \
|
||||
requires_restart, type) \
|
||||
namespace xe::cvars { \
|
||||
type name = default_value; \
|
||||
} \
|
||||
namespace cv { \
|
||||
static auto cv_##name = cvar::define_configvar( \
|
||||
#name, &cvars::name, description, category, is_transient); \
|
||||
}
|
||||
static auto cv_##name = xe::cvar::register_configvar<type>( \
|
||||
#name, &cvars::name, description, category, is_transient, \
|
||||
requires_restart); \
|
||||
} // namespace cvars
|
||||
|
||||
// CmdVars can only be strings for now, we don't need any others
|
||||
#define CmdVar(name, default_value, description) \
|
||||
namespace cvars { \
|
||||
std::string name = default_value; \
|
||||
} \
|
||||
namespace cv { \
|
||||
static auto cv_##name = \
|
||||
cvar::define_cmdvar(#name, &cvars::name, description); \
|
||||
}
|
||||
#define CmdVar(name, default_value, description) \
|
||||
namespace xe::cvars { \
|
||||
std::string name = default_value; \
|
||||
static auto cmdvar_##name = xe::cvar::register_commandvar<std::string>( \
|
||||
#name, &cvars::name, description); \
|
||||
} // namespace cvars
|
||||
|
||||
#define DECLARE_bool(name) DECLARE_CVar(name, bool)
|
||||
|
||||
|
@ -338,10 +233,14 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
|||
#define DECLARE_path(name) DECLARE_CVar(name, std::filesystem::path)
|
||||
|
||||
#define DECLARE_CVar(name, type) \
|
||||
namespace cvars { \
|
||||
namespace xe::cvars { \
|
||||
extern type name; \
|
||||
}
|
||||
|
||||
// load template implementations
|
||||
#include "cvar.inc"
|
||||
|
||||
} // namespace cvar
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CVAR_H_
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
void CommandVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
|
||||
options->add_options()(name_, description_, cxxopts::value<T>());
|
||||
}
|
||||
template <>
|
||||
inline void CommandVar<std::filesystem::path>::AddToLaunchOptions(
|
||||
cxxopts::Options* options) {
|
||||
options->add_options()(name_, description_, cxxopts::value<std::string>());
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
|
||||
options->add_options(category_)(this->name(), this->description(),
|
||||
cxxopts::value<T>());
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::AddToLaunchOptions(
|
||||
cxxopts::Options* options) {
|
||||
options->add_options(category_)(this->name(), this->description(),
|
||||
cxxopts::value<std::string>());
|
||||
}
|
||||
template <class T>
|
||||
void CommandVar<T>::LoadFromLaunchOptions(cxxopts::ParseResult* result) {
|
||||
T value = (*result)[name_].as<T>();
|
||||
set_command_line_value(value);
|
||||
}
|
||||
template <>
|
||||
inline void CommandVar<std::filesystem::path>::LoadFromLaunchOptions(
|
||||
cxxopts::ParseResult* result) {
|
||||
std::string value = (*result)[name_].as<std::string>();
|
||||
set_command_line_value(value);
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::LoadConfigValue(std::shared_ptr<cpptoml::base> result) {
|
||||
set_config_value(*cpptoml::get_impl<T>(result));
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::LoadConfigValue(
|
||||
std::shared_ptr<cpptoml::base> result) {
|
||||
set_config_value(
|
||||
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) {
|
||||
set_game_config_value(*cpptoml::get_impl<T>(result));
|
||||
}
|
||||
template <>
|
||||
inline void ConfigVar<std::filesystem::path>::LoadGameConfigValue(
|
||||
std::shared_ptr<cpptoml::base> result) {
|
||||
set_game_config_value(
|
||||
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
|
||||
}
|
||||
template <class T>
|
||||
CommandVar<T>::CommandVar(std::string_view name, T* default_value,
|
||||
std::string_view description)
|
||||
: name_(name),
|
||||
default_value_(*default_value),
|
||||
description_(description),
|
||||
current_value_(default_value) {}
|
||||
|
||||
template <class T>
|
||||
ConfigVar<T>::ConfigVar(std::string_view name, T* default_value,
|
||||
std::string_view description, std::string_view category,
|
||||
bool is_transient, bool requires_restart)
|
||||
: CommandVar<T>(name, default_value, description),
|
||||
category_(category),
|
||||
is_transient_(is_transient),
|
||||
requires_restart_(requires_restart) {}
|
||||
|
||||
template <class T>
|
||||
void CommandVar<T>::UpdateValue() {
|
||||
if (commandline_value_) return set_current_value(*commandline_value_);
|
||||
return set_current_value(default_value_);
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::UpdateValue() {
|
||||
if (this->command_line_value()) {
|
||||
return this->set_config_value(*this->command_line_value());
|
||||
}
|
||||
if (game_config_value_) return this->set_current_value(*game_config_value_);
|
||||
if (config_value_) return this->set_current_value(*config_value_);
|
||||
return this->set_current_value(this->default_value());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string CommandVar<std::string>::Convert(std::string_view val) const {
|
||||
return std::string(val);
|
||||
}
|
||||
template <>
|
||||
inline std::filesystem::path CommandVar<std::filesystem::path>::Convert(
|
||||
std::string_view val) const {
|
||||
return xe::to_path(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string CommandVar<bool>::ToString(const bool& val) {
|
||||
return val ? "true" : "false";
|
||||
}
|
||||
template <>
|
||||
inline std::string CommandVar<std::string>::ToString(const std::string& val) {
|
||||
return toml::EscapeString(val);
|
||||
}
|
||||
template <>
|
||||
inline std::string CommandVar<std::filesystem::path>::ToString(const
|
||||
std::filesystem::path& val) {
|
||||
return toml::EscapeString(
|
||||
xe::utf8::fix_path_separators(xe::path_to_utf8(val), '/'));
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/config.h"
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
@ -23,8 +24,8 @@ bool has_console_attached() { return true; }
|
|||
extern "C" int main(int argc, char** argv) {
|
||||
auto entry_info = xe::GetEntryInfo();
|
||||
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||
entry_info.positional_options);
|
||||
Config::Instance().ParseLaunchArguments(
|
||||
argc, argv, entry_info.positional_usage, entry_info.positional_options);
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
// Includes Windows headers, so it goes here.
|
||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
||||
#include "xenia/config.h"
|
||||
|
||||
DEFINE_bool(win32_high_freq, true,
|
||||
"Requests high performance from the NT kernel", "Kernel");
|
||||
|
@ -106,7 +107,8 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
|||
|
||||
LocalFree(wargv);
|
||||
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||
Config::Instance().ParseLaunchArguments(
|
||||
argc, argv, entry_info.positional_usage,
|
||||
entry_info.positional_options);
|
||||
|
||||
args.clear();
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
||||
CmdVar(config, "", "Specifies the target config to load.");
|
||||
|
||||
std::shared_ptr<cpptoml::table> ParseFile(
|
||||
const std::filesystem::path& filename) {
|
||||
std::ifstream file(filename);
|
||||
|
@ -28,54 +30,61 @@ std::shared_ptr<cpptoml::table> ParseFile(
|
|||
return p.parse();
|
||||
}
|
||||
|
||||
CmdVar(config, "", "Specifies the target config to load.");
|
||||
namespace config {
|
||||
std::string config_name = "xenia.config.toml";
|
||||
std::filesystem::path config_folder;
|
||||
std::filesystem::path config_path;
|
||||
std::string game_config_suffix = ".config.toml";
|
||||
namespace xe {
|
||||
|
||||
Config& Config::Instance() {
|
||||
static Config config;
|
||||
return config;
|
||||
}
|
||||
|
||||
Config::Config() : options_("xenia", "Xbox 30 Emulator") {
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<cpptoml::table> ParseConfig(
|
||||
const std::filesystem::path& config_path) {
|
||||
try {
|
||||
return ParseFile(config_path);
|
||||
} catch (cpptoml::parse_exception e) {
|
||||
} catch (const cpptoml::parse_exception& e) {
|
||||
xe::FatalError(fmt::format("Failed to parse config file '{}':\n\n{}",
|
||||
xe::path_to_utf8(config_path), e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadConfig(const std::filesystem::path& file_path) {
|
||||
const auto config = ParseConfig(file_path);
|
||||
for (auto& it : *cvar::ConfigVars) {
|
||||
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
|
||||
auto config_key = config_var->category() + "." + config_var->name();
|
||||
if (config->contains_qualified(config_key)) {
|
||||
config_var->LoadConfigValue(config->get_qualified(config_key));
|
||||
void Config::SetupConfig(const std::filesystem::path& config_folder) {
|
||||
Config::config_folder_ = config_folder;
|
||||
// check if the user specified a specific config to load
|
||||
if (!cvars::config.empty()) {
|
||||
config_path_ = xe::to_path(cvars::config);
|
||||
if (std::filesystem::exists(config_path_)) {
|
||||
ReadConfig(config_path_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
XELOGI("Loaded config: {}", xe::path_to_utf8(file_path));
|
||||
}
|
||||
|
||||
void ReadGameConfig(const std::filesystem::path& file_path) {
|
||||
const auto config = ParseConfig(file_path);
|
||||
for (auto& it : *cvar::ConfigVars) {
|
||||
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
|
||||
auto config_key = config_var->category() + "." + config_var->name();
|
||||
if (config->contains_qualified(config_key)) {
|
||||
config_var->LoadGameConfigValue(config->get_qualified(config_key));
|
||||
// if the user specified a --config argument, but the file doesn't exist,
|
||||
// let's also load the default config
|
||||
if (!config_folder.empty()) {
|
||||
config_path_ = config_folder / config_name_;
|
||||
if (std::filesystem::exists(config_path_)) {
|
||||
ReadConfig(config_path_);
|
||||
}
|
||||
// we only want to save the config if the user is using the default
|
||||
// config, we don't want to override a user created specific config
|
||||
SaveConfig();
|
||||
}
|
||||
XELOGI("Loaded game config: {}", xe::path_to_utf8(file_path));
|
||||
}
|
||||
|
||||
void SaveConfig() {
|
||||
SaveStatus Config::SaveConfig() {
|
||||
std::vector<cvar::IConfigVar*> vars;
|
||||
for (const auto& s : *cvar::ConfigVars) {
|
||||
vars.push_back(s.second);
|
||||
|
||||
SaveStatus status = SaveStatus::Saved;
|
||||
|
||||
for (const auto& s : config_vars_) {
|
||||
vars.push_back(s.second.get());
|
||||
}
|
||||
std::sort(vars.begin(), vars.end(), [](auto a, auto b) {
|
||||
std::sort(vars.begin(), vars.end(), [](auto a, auto b)
|
||||
{
|
||||
if (a->category() < b->category()) return true;
|
||||
if (a->category() > b->category()) return false;
|
||||
if (a->name() < b->name()) return true;
|
||||
|
@ -87,11 +96,15 @@ void SaveConfig() {
|
|||
std::string last_category;
|
||||
bool last_multiline_description = false;
|
||||
xe::StringBuffer sb;
|
||||
for (auto config_var : vars) {
|
||||
for (const cvar::IConfigVar* config_var : vars) {
|
||||
if (config_var->is_transient()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config_var->requires_restart()) {
|
||||
status = SaveStatus::RequiresRestart;
|
||||
}
|
||||
|
||||
if (last_category != config_var->category()) {
|
||||
if (!last_category.empty()) {
|
||||
sb.Append('\n', 2);
|
||||
|
@ -150,47 +163,120 @@ void SaveConfig() {
|
|||
}
|
||||
|
||||
// save the config file
|
||||
xe::filesystem::CreateParentFolder(config_path);
|
||||
xe::filesystem::CreateParentFolder(config_path_);
|
||||
|
||||
auto handle = xe::filesystem::OpenFile(config_path, "wb");
|
||||
auto handle = xe::filesystem::OpenFile(config_path_, "wb");
|
||||
if (!handle) {
|
||||
XELOGE("Failed to open '{}' for writing.", xe::path_to_utf8(config_path));
|
||||
} else {
|
||||
fwrite(sb.buffer(), 1, sb.length(), handle);
|
||||
fclose(handle);
|
||||
XELOGE("Failed to open '{}' for writing.", xe::path_to_utf8(config_path_));
|
||||
return SaveStatus::CouldNotSave;
|
||||
}
|
||||
fwrite(sb.buffer(), 1, sb.length(), handle);
|
||||
fclose(handle);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void SetupConfig(const std::filesystem::path& config_folder) {
|
||||
config::config_folder = config_folder;
|
||||
// check if the user specified a specific config to load
|
||||
if (!cvars::config.empty()) {
|
||||
config_path = xe::to_path(cvars::config);
|
||||
if (std::filesystem::exists(config_path)) {
|
||||
ReadConfig(config_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if the user specified a --config argument, but the file doesn't exist,
|
||||
// let's also load the default config
|
||||
if (!config_folder.empty()) {
|
||||
config_path = config_folder / config_name;
|
||||
if (std::filesystem::exists(config_path)) {
|
||||
ReadConfig(config_path);
|
||||
}
|
||||
// we only want to save the config if the user is using the default
|
||||
// config, we don't want to override a user created specific config
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadGameConfig(const std::string_view title_id) {
|
||||
const auto game_config_folder = config_folder / "config";
|
||||
void Config::LoadGameConfig(const std::string_view title_id) {
|
||||
const auto game_config_folder = Config::config_folder_ / "config";
|
||||
const auto game_config_path =
|
||||
game_config_folder / (std::string(title_id) + game_config_suffix);
|
||||
game_config_folder / (std::string(title_id) + Config::game_config_suffix_);
|
||||
if (std::filesystem::exists(game_config_path)) {
|
||||
ReadGameConfig(game_config_path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
cvar::IConfigVar* Config::FindConfigVar(const std::string& name) {
|
||||
const auto it = config_vars_.find(name);
|
||||
if (it != config_vars_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cvar::ICommandVar* Config::FindCommandVar(const std::string& name) {
|
||||
const auto it = command_vars_.find(name);
|
||||
if (it != command_vars_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Config::ParseLaunchArguments(int& argc, char**& argv,
|
||||
const std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options) {
|
||||
options_.add_options()("help", "Prints help and exit.");
|
||||
|
||||
for (auto& it : command_vars_) {
|
||||
const auto& cmdVar = it.second;
|
||||
cmdVar->AddToLaunchOptions(&options_);
|
||||
}
|
||||
|
||||
for (const auto& it : config_vars_) {
|
||||
const auto& configVar = it.second;
|
||||
configVar->AddToLaunchOptions(&options_);
|
||||
}
|
||||
|
||||
try {
|
||||
options_.positional_help(std::string(positional_help));
|
||||
options_.parse_positional(positional_options);
|
||||
|
||||
auto result = options_.parse(argc, argv);
|
||||
if (result.count("help")) {
|
||||
PrintHelpAndExit();
|
||||
}
|
||||
|
||||
for (auto& it : command_vars_) {
|
||||
const auto& cmdVar = it.second;
|
||||
if (result.count(cmdVar->name())) {
|
||||
cmdVar->LoadFromLaunchOptions(&result);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& it : config_vars_) {
|
||||
const auto& configVar = it.second;
|
||||
if (result.count(configVar->name())) {
|
||||
configVar->LoadFromLaunchOptions(&result);
|
||||
}
|
||||
}
|
||||
} catch (const cxxopts::OptionException& e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
PrintHelpAndExit();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::ReadConfig(const std::filesystem::path& file_path) {
|
||||
const auto config = ParseConfig(file_path);
|
||||
|
||||
for (auto& it : config_vars_) {
|
||||
const auto& config_var = it.second;
|
||||
auto config_key = config_var->category() + "." + config_var->name();
|
||||
|
||||
if (config->contains_qualified(config_key)) {
|
||||
config_var->LoadConfigValue(config->get_qualified(config_key));
|
||||
}
|
||||
}
|
||||
XELOGI("Loaded config: {}", xe::path_to_utf8(file_path));
|
||||
}
|
||||
|
||||
void Config::ReadGameConfig(const std::filesystem::path& file_path) {
|
||||
const auto config = ParseConfig(file_path);
|
||||
|
||||
for (auto& it : config_vars_) {
|
||||
const auto& config_var = it.second;
|
||||
auto config_key = config_var->category() + "." + config_var->name();
|
||||
|
||||
if (config->contains_qualified(config_key)) {
|
||||
config_var->LoadGameConfigValue(config->get_qualified(config_key));
|
||||
}
|
||||
}
|
||||
XELOGI("Loaded game config: {}", xe::path_to_utf8(file_path));
|
||||
}
|
||||
|
||||
void Config::PrintHelpAndExit() {
|
||||
std::cout << options_.help({""}) << std::endl;
|
||||
std::cout << "For the full list of command line arguments, see xenia.cfg."
|
||||
<< std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -11,10 +11,113 @@
|
|||
#define XENIA_CONFIG_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include "cxxopts/include/cxxopts.hpp"
|
||||
|
||||
namespace config {
|
||||
void SetupConfig(const std::filesystem::path& config_folder);
|
||||
void LoadGameConfig(const std::string_view title_id);
|
||||
} // namespace config
|
||||
namespace xe {
|
||||
|
||||
namespace cvar {
|
||||
|
||||
class IConfigVar;
|
||||
class ICommandVar;
|
||||
|
||||
template <typename T>
|
||||
class ConfigVar;
|
||||
template <typename T>
|
||||
class CommandVar;
|
||||
|
||||
} // namespace cvar
|
||||
|
||||
enum class SaveStatus : int {
|
||||
Saved = 1 << 0,
|
||||
RequiresRestart = 1 << 0 | 1 << 1,
|
||||
CouldNotSave = 1 << 2
|
||||
};
|
||||
|
||||
using IConfigVarRef = std::unique_ptr<cvar::IConfigVar>;
|
||||
using ICommandVarRef = std::unique_ptr<cvar::ICommandVar>;
|
||||
|
||||
class Config {
|
||||
public:
|
||||
static Config& Instance();
|
||||
/**
|
||||
* Populates the global config variables from a config file
|
||||
*/
|
||||
void SetupConfig(const std::filesystem::path& config_folder);
|
||||
|
||||
/**
|
||||
* Save the current config variable state to the config file
|
||||
*/
|
||||
SaveStatus SaveConfig();
|
||||
|
||||
/**
|
||||
* Overrides config variables with those from a game-specific config file (if
|
||||
* any)
|
||||
*/
|
||||
void LoadGameConfig(std::string_view title_id);
|
||||
|
||||
/**
|
||||
* Find a config variable for a given config field
|
||||
*/
|
||||
cvar::IConfigVar* FindConfigVar(const std::string& name);
|
||||
|
||||
/**
|
||||
* Find a command variable for a given name
|
||||
*/
|
||||
cvar::ICommandVar* FindCommandVar(const std::string& name);
|
||||
|
||||
/**
|
||||
* Parse launch arguments and update cvar values based on provided launch
|
||||
* parameters
|
||||
*/
|
||||
void ParseLaunchArguments(int& argc, char**& argv,
|
||||
std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options);
|
||||
|
||||
/**
|
||||
* Register a config var to the config system
|
||||
* Claims ownership of the provided pointer
|
||||
*/
|
||||
template <typename T>
|
||||
cvar::ConfigVar<T>* RegisterConfigVar(cvar::ConfigVar<T>* var);
|
||||
|
||||
/**
|
||||
* Register a command var to the config system
|
||||
* Claims ownership of the provided pointer
|
||||
*/
|
||||
template <typename T>
|
||||
cvar::CommandVar<T>* RegisterCommandVar(cvar::CommandVar<T>* var);
|
||||
|
||||
private:
|
||||
Config();
|
||||
|
||||
void ReadConfig(const std::filesystem::path& file_path);
|
||||
void ReadGameConfig(const std::filesystem::path& file_path);
|
||||
void PrintHelpAndExit();
|
||||
|
||||
std::unordered_map<std::string, IConfigVarRef> config_vars_;
|
||||
std::unordered_map<std::string, ICommandVarRef> command_vars_;
|
||||
std::filesystem::path config_folder_;
|
||||
std::filesystem::path config_path_;
|
||||
std::string config_name_ = "xenia.config.toml";
|
||||
std::string game_config_suffix_ = ".config.toml";
|
||||
cxxopts::Options options_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
cvar::ConfigVar<T>* Config::RegisterConfigVar(cvar::ConfigVar<T>* var) {
|
||||
const std::string& name = var->name();
|
||||
config_vars_[name] = std::unique_ptr<cvar::ConfigVar<T>>(var);
|
||||
return dynamic_cast<cvar::ConfigVar<T>*>(config_vars_[name].get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
cvar::CommandVar<T>* Config::RegisterCommandVar(cvar::CommandVar<T>* var) {
|
||||
const std::string& name = var->name();
|
||||
command_vars_[name] = std::unique_ptr<cvar::CommandVar<T>>(var);
|
||||
return dynamic_cast<cvar::CommandVar<T>*>(command_vars_[name].get());
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CONFIG_H_
|
||||
|
|
|
@ -664,7 +664,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
|||
// Try and load the resource database (xex only).
|
||||
if (module->title_id()) {
|
||||
auto title_id = fmt::format("{:08X}", module->title_id());
|
||||
config::LoadGameConfig(title_id);
|
||||
Config::Instance().LoadGameConfig(title_id);
|
||||
uint32_t resource_data = 0;
|
||||
uint32_t resource_size = 0;
|
||||
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
#include <vector>
|
||||
|
||||
#define CATCH_CONFIG_RUNNER
|
||||
|
||||
#include "third_party/catch/include/catch.hpp"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/config.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
|
@ -23,7 +25,8 @@ bool has_console_attached() { return true; }
|
|||
|
||||
// Used in console mode apps; automatically picked based on subsystem.
|
||||
int Main(int argc, char* argv[]) {
|
||||
cvar::ParseLaunchArguments(argc, argv, "", std::vector<std::string>());
|
||||
Config::Instance().ParseLaunchArguments(argc, argv, "",
|
||||
std::vector<std::string>());
|
||||
|
||||
// Run Catch.
|
||||
int result = Catch::Session().run(argc, argv);
|
||||
|
|
Loading…
Reference in New Issue