mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 11:00:19 +02:00
Serializes every option, even ones that are set to default to ensure validation that if any config option value is added or changed that they are captured. Skips a handful of options that are either meta options, environment options that don't matter, or HostFeatures which is handled elsewhere. (HostFeatures will be controlling bucketing rather than the remaining options). This serialization is currently 1413 bytes and generates in 17580ns on my A1A. So it's not the fastest, definitely don't want to be generating it constantly per process.
324 lines
11 KiB
C++
324 lines
11 KiB
C++
// SPDX-License-Identifier: MIT
|
|
#pragma once
|
|
|
|
#include <FEXCore/Utils/CompilerDefs.h>
|
|
#include <FEXCore/Utils/EnumOperators.h>
|
|
#include <FEXCore/Utils/EnumUtils.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXCore/fextl/fmt.h>
|
|
#include <FEXCore/fextl/memory.h>
|
|
#include <FEXCore/fextl/list.h>
|
|
#include <FEXCore/fextl/string.h>
|
|
#include <FEXCore/fextl/unordered_map.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <charconv>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
#include <variant>
|
|
|
|
namespace FEXCore::Config {
|
|
namespace Handler {
|
|
static inline std::optional<fextl::string> SMCCheckHandler(std::string_view Value) {
|
|
if (Value == "none") {
|
|
return "0";
|
|
} else if (Value == "mtrack") {
|
|
return "1";
|
|
} else if (Value == "full") {
|
|
return "2";
|
|
}
|
|
return "0";
|
|
}
|
|
} // namespace Handler
|
|
|
|
enum ConfigOption {
|
|
#define OPT_BASE(type, group, enum, json, default) CONFIG_##enum,
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
CONFIG_MAX,
|
|
};
|
|
|
|
#define ENUMDEFINES
|
|
#include <FEXCore/Config/ConfigOptions.inl>
|
|
|
|
enum ConfigSMCChecks {
|
|
CONFIG_SMC_NONE,
|
|
CONFIG_SMC_MTRACK,
|
|
CONFIG_SMC_FULL,
|
|
};
|
|
|
|
enum class LayerType {
|
|
LAYER_GLOBAL_MAIN, ///< /usr/share/fex-emu/Config.json by default
|
|
LAYER_MAIN,
|
|
LAYER_ARGUMENTS,
|
|
LAYER_GLOBAL_STEAM_APP,
|
|
LAYER_GLOBAL_APP,
|
|
LAYER_LOCAL_STEAM_APP,
|
|
LAYER_LOCAL_APP,
|
|
LAYER_USER_OVERRIDE,
|
|
LAYER_ENVIRONMENT,
|
|
LAYER_TOP,
|
|
};
|
|
|
|
template<typename PairTypes, typename ArrayPairType>
|
|
static inline std::optional<fextl::string> EnumParser(const ArrayPairType& EnumPairs, const std::string_view View) {
|
|
uint64_t EnumMask {};
|
|
auto Results = std::from_chars(View.data(), View.data() + View.size(), EnumMask);
|
|
if (Results.ec == std::errc()) {
|
|
// If the data is a valid number, just pass it through.
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto Begin = 0;
|
|
auto End = View.find_first_of(',');
|
|
std::string_view Option = View.substr(Begin, End);
|
|
while (Option.size() != 0) {
|
|
auto EnumValue =
|
|
std::find_if(EnumPairs.begin(), EnumPairs.end(), [Option](const PairTypes& Value) -> bool { return Value.first == Option; });
|
|
|
|
if (EnumValue == EnumPairs.end()) {
|
|
LogMan::Msg::IFmt("Skipping Unknown option: {}", Option);
|
|
} else {
|
|
EnumMask |= FEXCore::ToUnderlying(EnumValue->second);
|
|
}
|
|
|
|
if (End == std::string::npos) {
|
|
break;
|
|
}
|
|
Begin = End + 1;
|
|
End = View.find_first_of(',', Begin);
|
|
Option = View.substr(Begin, End - Begin);
|
|
}
|
|
|
|
return fextl::fmt::format("{}", EnumMask);
|
|
}
|
|
|
|
using StringArrayType = fextl::list<fextl::string>;
|
|
|
|
namespace detail {
|
|
template<ConfigOption Option>
|
|
struct ConfigOptionInfo;
|
|
#define DEFINE_METAINFO(type, enum, default) \
|
|
template<> \
|
|
struct ConfigOptionInfo<ConfigOption::CONFIG_##enum> { \
|
|
using Type = type; \
|
|
static auto Default() { \
|
|
extern default; \
|
|
return enum; \
|
|
} \
|
|
};
|
|
#define OPT_BASE(type, group, enum, json, default) DEFINE_METAINFO(type, enum, const type enum)
|
|
#define OPT_STR(group, enum, json, default) DEFINE_METAINFO(fextl::string, enum, const std::string_view enum)
|
|
#define OPT_STRARRAY(group, enum, json, default) DEFINE_METAINFO(StringArrayType, enum, const std::string_view enum)
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
} // namespace detail
|
|
|
|
FEX_DEFAULT_VISIBILITY void SetDataDirectory(std::string_view Path, bool Global);
|
|
FEX_DEFAULT_VISIBILITY void SetConfigDirectory(const std::string_view Path, bool Global);
|
|
FEX_DEFAULT_VISIBILITY void SetConfigFileLocation(std::string_view Path, bool Global);
|
|
FEX_DEFAULT_VISIBILITY void SetCacheDirectory(const std::string_view Path);
|
|
|
|
FEX_DEFAULT_VISIBILITY const fextl::string& GetDataDirectory(bool Global = false);
|
|
FEX_DEFAULT_VISIBILITY const fextl::string& GetConfigDirectory(bool Global);
|
|
FEX_DEFAULT_VISIBILITY const fextl::string& GetConfigFileLocation(bool Global = false);
|
|
FEX_DEFAULT_VISIBILITY fextl::string GetApplicationConfig(const std::string_view Program, bool Global);
|
|
FEX_DEFAULT_VISIBILITY const fextl::string& GetCacheDirectory();
|
|
|
|
using LayerValue = std::variant< fextl::string, StringArrayType, uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, bool >;
|
|
|
|
using LayerOptions = fextl::unordered_map<ConfigOption, LayerValue>;
|
|
|
|
class FEX_DEFAULT_VISIBILITY Layer {
|
|
public:
|
|
explicit Layer(const LayerType _Type);
|
|
virtual ~Layer();
|
|
|
|
virtual void Load() = 0;
|
|
|
|
bool OptionExists(ConfigOption Option) const {
|
|
return OptionMap.find(Option) != OptionMap.end();
|
|
}
|
|
|
|
std::optional<StringArrayType*> All(ConfigOption Option) {
|
|
const auto it = OptionMap.find(Option);
|
|
if (it == OptionMap.end()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& Value = it->second;
|
|
LOGMAN_THROW_A_FMT(std::holds_alternative<StringArrayType>(Value), "Tried to get config of invalid type!");
|
|
|
|
return &std::get<StringArrayType>(Value);
|
|
}
|
|
|
|
std::optional<fextl::string*> Get(ConfigOption Option) {
|
|
const auto it = OptionMap.find(Option);
|
|
if (it == OptionMap.end()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& Value = it->second;
|
|
LOGMAN_THROW_A_FMT(std::holds_alternative<fextl::string>(Value), "Tried to get config of invalid type!");
|
|
|
|
return &std::get<fextl::string>(Value);
|
|
}
|
|
|
|
// Set will overwrite the object with a fextl::string without tests.
|
|
void Set(ConfigOption Option, const char* Data) {
|
|
LOGMAN_THROW_A_FMT(Data != nullptr, "Data can't be null");
|
|
OptionMap[Option].emplace<fextl::string>(fextl::string(Data));
|
|
}
|
|
|
|
void Set(ConfigOption Option, std::string_view Data) {
|
|
OptionMap[Option].emplace<fextl::string>(fextl::string(Data));
|
|
}
|
|
|
|
void Set(ConfigOption Option, fextl::string Data) {
|
|
OptionMap[Option].emplace<fextl::string>(std::move(Data));
|
|
}
|
|
|
|
void Set(ConfigOption Option, std::optional<fextl::string> Data) {
|
|
if (Data) {
|
|
OptionMap[Option].emplace<fextl::string>(std::move(*Data));
|
|
}
|
|
}
|
|
|
|
// AppendStrArrayValue will append strings to its StringArrayType.
|
|
// If the value was previously a different type, then throw an assert.
|
|
void AppendStrArrayValue(ConfigOption Option, std::string_view Data) {
|
|
auto it = OptionMap.find(Option);
|
|
if (it == OptionMap.end()) {
|
|
// If the option didn't exist as a StringArrayType yet, emplace it.
|
|
it = OptionMap.emplace(Option, StringArrayType {}).first;
|
|
}
|
|
|
|
auto& Value = it->second;
|
|
LOGMAN_THROW_A_FMT(std::holds_alternative<StringArrayType>(Value), "Tried to get config of invalid type!");
|
|
std::get<StringArrayType>(Value).emplace_back(Data);
|
|
}
|
|
|
|
void Erase(ConfigOption Option) {
|
|
OptionMap.erase(Option);
|
|
}
|
|
|
|
LayerType GetLayerType() const {
|
|
return Type;
|
|
}
|
|
const LayerOptions& GetOptionMap() const {
|
|
return OptionMap;
|
|
}
|
|
|
|
protected:
|
|
const LayerType Type;
|
|
LayerOptions OptionMap;
|
|
};
|
|
|
|
FEX_DEFAULT_VISIBILITY void Initialize();
|
|
FEX_DEFAULT_VISIBILITY void Shutdown();
|
|
|
|
FEX_DEFAULT_VISIBILITY void Load();
|
|
FEX_DEFAULT_VISIBILITY void ReloadMetaLayer();
|
|
FEX_DEFAULT_VISIBILITY fextl::string FindContainer();
|
|
FEX_DEFAULT_VISIBILITY fextl::string FindContainerPrefix();
|
|
|
|
FEX_DEFAULT_VISIBILITY void AddLayer(fextl::unique_ptr<FEXCore::Config::Layer> _Layer);
|
|
|
|
FEX_DEFAULT_VISIBILITY bool Exists(ConfigOption Option);
|
|
FEX_DEFAULT_VISIBILITY std::optional<StringArrayType*> All(ConfigOption Option);
|
|
template<typename T>
|
|
FEX_DEFAULT_VISIBILITY std::optional<T> GetConv(ConfigOption Option);
|
|
FEX_DEFAULT_VISIBILITY std::optional<fextl::string*> Get(ConfigOption Option);
|
|
FEX_DEFAULT_VISIBILITY void Set(ConfigOption Option, std::string_view Data);
|
|
FEX_DEFAULT_VISIBILITY void Erase(ConfigOption Option);
|
|
FEX_DEFAULT_VISIBILITY fextl::string SerializeForCache();
|
|
FEX_DEFAULT_VISIBILITY bool CheckConfigMatches(std::string_view Config);
|
|
|
|
template<typename T>
|
|
class FEX_DEFAULT_VISIBILITY Value {
|
|
public:
|
|
// Single value type.
|
|
template<typename TT = T>
|
|
requires (std::is_fundamental_v<TT> || std::is_same_v<TT, fextl::string>)
|
|
Value(FEXCore::Config::ConfigOption Option, TT Default) {
|
|
ValueData = GetIfExists(Option, Default);
|
|
}
|
|
|
|
template<typename TT = T>
|
|
requires (std::is_fundamental_v<TT> || std::is_same_v<TT, fextl::string>)
|
|
Value(FEXCore::Config::ConfigOption Option, std::string_view Default) {
|
|
ValueData = GetIfExists(Option, Default);
|
|
}
|
|
|
|
operator T() const {
|
|
return ValueData;
|
|
}
|
|
|
|
T operator()() const requires (std::is_fundamental_v<T>)
|
|
{
|
|
return ValueData;
|
|
}
|
|
|
|
const fextl::string& operator()() const requires (std::is_same_v<T, fextl::string>)
|
|
{
|
|
return ValueData;
|
|
}
|
|
|
|
Value(T Value) requires (!std::is_same_v<T, StringArrayType>)
|
|
{
|
|
ValueData = std::move(Value);
|
|
}
|
|
|
|
// Array value types.
|
|
Value(FEXCore::Config::ConfigOption Option, std::string_view) requires (std::is_same_v<T, StringArrayType>)
|
|
{
|
|
GetListIfExists(Option, &ValueData);
|
|
}
|
|
|
|
StringArrayType& All() requires (std::is_same_v<T, StringArrayType>)
|
|
{
|
|
return ValueData;
|
|
}
|
|
|
|
private:
|
|
T ValueData {};
|
|
|
|
static T GetIfExists(FEXCore::Config::ConfigOption Option, T Default);
|
|
static T GetIfExists(FEXCore::Config::ConfigOption Option, std::string_view Default);
|
|
|
|
static void GetListIfExists(FEXCore::Config::ConfigOption Option, StringArrayType* List);
|
|
};
|
|
|
|
/**
|
|
* Wrapper around Value that automatically picks the default for the given ConfigOption
|
|
*/
|
|
template<ConfigOption Option>
|
|
struct FEX_DEFAULT_VISIBILITY Getter : public Value<typename detail::ConfigOptionInfo<Option>::Type> {
|
|
using OptionInfo = detail::ConfigOptionInfo<Option>;
|
|
Getter()
|
|
: Value<typename OptionInfo::Type> {Option, OptionInfo::Default()} {}
|
|
};
|
|
|
|
/**
|
|
* Helper for reading a config value with caching.
|
|
*
|
|
* Typically this is used to declare class members so that the value is read
|
|
* on construction of the parent.
|
|
*/
|
|
#define FEX_CONFIG_OPT(name, enum) FEXCore::Config::Getter<FEXCore::Config::ConfigOption::CONFIG_##enum> name {}
|
|
|
|
#define OPT_BASE(type, group, enum, json, default) \
|
|
/** \
|
|
* Helper for reading a config value. \
|
|
* \
|
|
* In contrast to FEX_CONFIG_OPT, this can be used in arbitrary expressions, \
|
|
* at the expense of not caching the value. Use Getter instead if the value \
|
|
* is read frequently. \
|
|
*/ \
|
|
inline auto Get_##enum() { \
|
|
return Getter<FEXCore::Config::ConfigOption::CONFIG_##enum> {}; \
|
|
}
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
|
|
} // namespace FEXCore::Config
|