mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-08 20:24:47 -04:00
Config: renaming & add change subscriptions
This commit is contained in:
+280
-151
@@ -7,6 +7,7 @@
|
||||
#include "dusk/io.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
@@ -15,77 +16,90 @@
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/main.h"
|
||||
|
||||
using namespace dusk::config;
|
||||
|
||||
namespace dusk::config {
|
||||
namespace {
|
||||
constexpr auto ConfigFileName = "config.json";
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
aurora::Module DuskConfigLog("dusk::config");
|
||||
|
||||
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
|
||||
static bool RegistrationDone = false;
|
||||
absl::flat_hash_map<std::string, ConfigVarBase*> RegisteredConfigVars;
|
||||
absl::flat_hash_map<std::string, nlohmann::json> UnregisteredConfigVars;
|
||||
absl::flat_hash_map<std::string, std::string> UnregisteredConfigVarOverrides;
|
||||
|
||||
static std::optional<dusk::ui::ControlAnchor> parse_control_anchor(std::string_view value) {
|
||||
struct ChangeSubscription {
|
||||
Subscription token;
|
||||
ChangeCallback callback;
|
||||
};
|
||||
absl::flat_hash_map<std::string, std::vector<ChangeSubscription> > s_changeSubscriptions;
|
||||
absl::flat_hash_map<Subscription, std::string> s_changeTokenNames;
|
||||
Subscription s_nextChangeToken = 1;
|
||||
// Names currently being notified; guards against a callback re-notifying its own CVar.
|
||||
std::vector<std::string> s_activeChangeNotifications;
|
||||
|
||||
std::optional<ui::ControlAnchor> parse_control_anchor(std::string_view value) {
|
||||
if (value == "none") {
|
||||
return dusk::ui::ControlAnchor::None;
|
||||
return ui::ControlAnchor::None;
|
||||
}
|
||||
if (value == "top") {
|
||||
return dusk::ui::ControlAnchor::Top;
|
||||
return ui::ControlAnchor::Top;
|
||||
}
|
||||
if (value == "left") {
|
||||
return dusk::ui::ControlAnchor::Left;
|
||||
return ui::ControlAnchor::Left;
|
||||
}
|
||||
if (value == "bottom") {
|
||||
return dusk::ui::ControlAnchor::Bottom;
|
||||
return ui::ControlAnchor::Bottom;
|
||||
}
|
||||
if (value == "right") {
|
||||
return dusk::ui::ControlAnchor::Right;
|
||||
return ui::ControlAnchor::Right;
|
||||
}
|
||||
if (value == "topLeft") {
|
||||
return dusk::ui::ControlAnchor::TopLeft;
|
||||
return ui::ControlAnchor::TopLeft;
|
||||
}
|
||||
if (value == "topRight") {
|
||||
return dusk::ui::ControlAnchor::TopRight;
|
||||
return ui::ControlAnchor::TopRight;
|
||||
}
|
||||
if (value == "bottomLeft") {
|
||||
return dusk::ui::ControlAnchor::BottomLeft;
|
||||
return ui::ControlAnchor::BottomLeft;
|
||||
}
|
||||
if (value == "bottomRight") {
|
||||
return dusk::ui::ControlAnchor::BottomRight;
|
||||
return ui::ControlAnchor::BottomRight;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static const char* control_anchor_value(dusk::ui::ControlAnchor anchor) {
|
||||
const char* control_anchor_value(ui::ControlAnchor anchor) {
|
||||
switch (anchor) {
|
||||
case dusk::ui::ControlAnchor::None:
|
||||
case ui::ControlAnchor::None:
|
||||
return "none";
|
||||
case dusk::ui::ControlAnchor::Top:
|
||||
case ui::ControlAnchor::Top:
|
||||
return "top";
|
||||
case dusk::ui::ControlAnchor::Left:
|
||||
case ui::ControlAnchor::Left:
|
||||
return "left";
|
||||
case dusk::ui::ControlAnchor::Bottom:
|
||||
case ui::ControlAnchor::Bottom:
|
||||
return "bottom";
|
||||
case dusk::ui::ControlAnchor::Right:
|
||||
case ui::ControlAnchor::Right:
|
||||
return "right";
|
||||
case dusk::ui::ControlAnchor::TopLeft:
|
||||
case ui::ControlAnchor::TopLeft:
|
||||
return "topLeft";
|
||||
case dusk::ui::ControlAnchor::TopRight:
|
||||
case ui::ControlAnchor::TopRight:
|
||||
return "topRight";
|
||||
case dusk::ui::ControlAnchor::BottomLeft:
|
||||
case ui::ControlAnchor::BottomLeft:
|
||||
return "bottomLeft";
|
||||
case dusk::ui::ControlAnchor::BottomRight:
|
||||
case ui::ControlAnchor::BottomRight:
|
||||
return "bottomRight";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
static std::optional<float> json_finite_float(const json& object, const char* key) {
|
||||
std::optional<float> json_finite_float(const json& object, const char* key) {
|
||||
const auto iter = object.find(key);
|
||||
if (iter == object.end() || !iter->is_number()) {
|
||||
return std::nullopt;
|
||||
@@ -99,7 +113,7 @@ static std::optional<float> json_finite_float(const json& object, const char* ke
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::optional<dusk::ui::ControlProps> parse_control_props(const json& value) {
|
||||
std::optional<ui::ControlProps> parse_control_props(const json& value) {
|
||||
if (!value.is_object()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -118,7 +132,7 @@ static std::optional<dusk::ui::ControlProps> parse_control_props(const json& val
|
||||
if (!anchor || *w <= 0.0f || *h <= 0.0f || *scale <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return dusk::ui::ControlProps{
|
||||
return ui::ControlProps{
|
||||
.x = *x,
|
||||
.y = *y,
|
||||
.w = *w,
|
||||
@@ -128,17 +142,17 @@ static std::optional<dusk::ui::ControlProps> parse_control_props(const json& val
|
||||
};
|
||||
}
|
||||
|
||||
static std::filesystem::path GetConfigJsonPath() {
|
||||
return dusk::ConfigPath / ConfigFileName;
|
||||
std::filesystem::path GetConfigJsonPath() {
|
||||
return ConfigPath / ConfigFileName;
|
||||
}
|
||||
|
||||
static std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) {
|
||||
std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) {
|
||||
auto tempPath = configJsonPath;
|
||||
tempPath.replace_filename(fmt::format(".{}.tmp", configJsonPath.filename().string()));
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
static void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) {
|
||||
void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(source, target, ec);
|
||||
if (ec) {
|
||||
@@ -148,19 +162,8 @@ static void ReplaceFile(const std::filesystem::path& source, const std::filesyst
|
||||
}
|
||||
}
|
||||
|
||||
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl)
|
||||
: name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {}
|
||||
|
||||
const char* ConfigVarBase::getName() const noexcept {
|
||||
return name;
|
||||
}
|
||||
|
||||
const ConfigImplBase* ConfigVarBase::getImpl() const noexcept {
|
||||
return impl;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
|
||||
T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
const Underlying raw = static_cast<Underlying>(value);
|
||||
@@ -174,6 +177,83 @@ static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_signed_v<T> T parse_arg_value(
|
||||
const ConfigVar<T>&, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
|
||||
return static_cast<T>(result);
|
||||
}
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_unsigned_v<T> T parse_arg_value(
|
||||
const ConfigVar<T>&, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoull(str);
|
||||
if (result <= std::numeric_limits<T>::max()) {
|
||||
return static_cast<T>(result);
|
||||
}
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
|
||||
f32 parse_arg_value(const ConfigVar<f32>&, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
return std::stof(str);
|
||||
}
|
||||
|
||||
f64 parse_arg_value(const ConfigVar<f64>&, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
return std::stod(str);
|
||||
}
|
||||
|
||||
std::string parse_arg_value(const ConfigVar<std::string>&, const std::string_view stringValue) {
|
||||
return std::string(stringValue);
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_enum_v<T> T parse_arg_value(
|
||||
const ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
const std::string str(stringValue);
|
||||
|
||||
if constexpr (std::is_signed_v<Underlying>) {
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<Underlying>::min() &&
|
||||
result <= std::numeric_limits<Underlying>::max())
|
||||
{
|
||||
return sanitizeEnumValue(cVar, static_cast<T>(result));
|
||||
}
|
||||
throw std::out_of_range("Value is too large");
|
||||
} else {
|
||||
const auto result = std::stoull(str);
|
||||
if (result <= std::numeric_limits<Underlying>::max()) {
|
||||
return sanitizeEnumValue(cVar, static_cast<T>(result));
|
||||
}
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConfigVarBase::ConfigVarBase(std::string name, const ConfigImplBase* impl)
|
||||
: name(std::move(name)), registered(false), layer(ConfigVarLayer::Default), impl(impl) {}
|
||||
|
||||
const char* ConfigVarBase::getName() const noexcept {
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
const ConfigImplBase* ConfigVarBase::getImpl() const noexcept {
|
||||
return impl;
|
||||
}
|
||||
|
||||
ConfigVarBase::~ConfigVarBase() {
|
||||
if (registered) {
|
||||
DuskLog.fatal("CVar '{}' was destroyed while still registered!", name);
|
||||
}
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
@@ -187,12 +267,12 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
|
||||
|
||||
const Underlying raw = b ? static_cast<Underlying>(1) : static_cast<Underlying>(0);
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, static_cast<T>(raw)), false);
|
||||
cVar.load_value(sanitizeEnumValue(cVar, static_cast<T>(raw)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<T>()), false);
|
||||
cVar.load_value(sanitizeEnumValue(cVar, jsonValue.get<T>()));
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
@@ -200,74 +280,9 @@ nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
|
||||
return cVar.getValueForSave();
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_signed_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
|
||||
cVar.setOverrideValue(result);
|
||||
} else {
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_unsigned_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoull(str);
|
||||
if (result <= std::numeric_limits<T>::max()) {
|
||||
cVar.setOverrideValue(result);
|
||||
} else {
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
}
|
||||
|
||||
static void loadFromArgImpl(ConfigVar<f32>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stof(str);
|
||||
cVar.setOverrideValue(result);
|
||||
}
|
||||
|
||||
static void loadFromArgImpl(ConfigVar<f64>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stod(str);
|
||||
cVar.setOverrideValue(result);
|
||||
}
|
||||
|
||||
static void loadFromArgImpl(ConfigVar<std::string>& cVar, const std::string_view stringValue) {
|
||||
cVar.setOverrideValue(std::string(stringValue));
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
requires std::is_enum_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
const std::string str(stringValue);
|
||||
|
||||
if constexpr (std::is_signed_v<Underlying>) {
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<Underlying>::min() &&
|
||||
result <= std::numeric_limits<Underlying>::max())
|
||||
{
|
||||
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
|
||||
} else {
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
} else {
|
||||
const auto result = std::stoull(str);
|
||||
if (result <= std::numeric_limits<Underlying>::max()) {
|
||||
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
|
||||
} else {
|
||||
throw std::out_of_range("Value is too large");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <ConfigValue T>
|
||||
void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
loadFromArgImpl(cVar, stringValue);
|
||||
cVar.load_override_value(parse_arg_value(cVar, stringValue));
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -275,18 +290,16 @@ void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view
|
||||
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" ||
|
||||
stringValue == "True")
|
||||
{
|
||||
cVar.setOverrideValue(true);
|
||||
cVar.load_override_value(true);
|
||||
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" ||
|
||||
stringValue == "False")
|
||||
{
|
||||
cVar.setOverrideValue(false);
|
||||
cVar.load_override_value(false);
|
||||
} else {
|
||||
throw InvalidConfigError("Value cannot be parsed as boolean");
|
||||
}
|
||||
}
|
||||
|
||||
// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT?
|
||||
namespace dusk::config {
|
||||
template class ConfigImpl<bool>;
|
||||
template class ConfigImpl<s8>;
|
||||
template class ConfigImpl<u8>;
|
||||
@@ -299,10 +312,10 @@ template class ConfigImpl<u64>;
|
||||
template class ConfigImpl<f32>;
|
||||
template class ConfigImpl<f64>;
|
||||
template class ConfigImpl<std::string>;
|
||||
template class ConfigImpl<dusk::BloomMode>;
|
||||
template class ConfigImpl<dusk::DepthOfFieldMode>;
|
||||
template class ConfigImpl<dusk::DiscVerificationState>;
|
||||
template class ConfigImpl<dusk::GameLanguage>;
|
||||
template class ConfigImpl<BloomMode>;
|
||||
template class ConfigImpl<DepthOfFieldMode>;
|
||||
template class ConfigImpl<DiscVerificationState>;
|
||||
template class ConfigImpl<GameLanguage>;
|
||||
|
||||
template <>
|
||||
void ConfigImpl<FrameInterpMode>::loadFromJson(
|
||||
@@ -312,11 +325,11 @@ void ConfigImpl<FrameInterpMode>::loadFromJson(
|
||||
|
||||
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, mode), false);
|
||||
cVar.load_value(sanitizeEnumValue(cVar, mode));
|
||||
return;
|
||||
}
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false);
|
||||
cVar.load_value(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()));
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -347,7 +360,7 @@ void ConfigImpl<ui::ControlLayout>::loadFromJson(
|
||||
}
|
||||
}
|
||||
|
||||
cVar.setValue(std::move(layout), false);
|
||||
cVar.load_value(std::move(layout));
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -377,26 +390,59 @@ nlohmann::json ConfigImpl<ui::ControlLayout>::dumpToJson(const ConfigVar<ui::Con
|
||||
};
|
||||
}
|
||||
|
||||
template class ConfigImpl<dusk::FrameInterpMode>;
|
||||
template class ConfigImpl<dusk::TouchTargeting>;
|
||||
template class ConfigImpl<dusk::MenuScaling>;
|
||||
template class ConfigImpl<dusk::Resampler>;
|
||||
template class ConfigImpl<dusk::MagicArmorMode>;
|
||||
template class ConfigImpl<dusk::ui::ControlLayout>;
|
||||
} // namespace dusk::config
|
||||
|
||||
void dusk::config::Register(ConfigVarBase& configVar) {
|
||||
const auto& name = configVar.getName();
|
||||
if (RegistrationDone) {
|
||||
DuskConfigLog.fatal("Tried to register CVar {} after registrations closed!", name);
|
||||
}
|
||||
template class ConfigImpl<FrameInterpMode>;
|
||||
template class ConfigImpl<TouchTargeting>;
|
||||
template class ConfigImpl<MenuScaling>;
|
||||
template class ConfigImpl<Resampler>;
|
||||
template class ConfigImpl<MagicArmorMode>;
|
||||
template class ConfigImpl<ui::ControlLayout>;
|
||||
|
||||
void Register(ConfigVarBase& configVar) {
|
||||
const std::string_view name = configVar.getName();
|
||||
if (RegisteredConfigVars.contains(name)) {
|
||||
DuskConfigLog.fatal("Tried to register CVar {} twice!", name);
|
||||
}
|
||||
|
||||
RegisteredConfigVars[name] = &configVar;
|
||||
configVar.markRegistered();
|
||||
|
||||
const auto unregPair = UnregisteredConfigVars.find(name);
|
||||
if (unregPair != UnregisteredConfigVars.end()) {
|
||||
const auto value = std::move(unregPair->second);
|
||||
UnregisteredConfigVars.erase(name);
|
||||
|
||||
try {
|
||||
configVar.getImpl()->loadFromJson(configVar, value);
|
||||
} catch (std::exception& e) {
|
||||
DuskConfigLog.error("Failed to load key '{}' from config value: {}", name, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
const auto overridePair = UnregisteredConfigVarOverrides.find(name);
|
||||
if (overridePair != UnregisteredConfigVarOverrides.end()) {
|
||||
try {
|
||||
configVar.getImpl()->loadFromArg(configVar, overridePair->second);
|
||||
} catch (std::exception& e) {
|
||||
DuskConfigLog.error("Failed to load key '{}' from override arg: {}", name, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unregister(ConfigVarBase& configVar) {
|
||||
const std::string_view name = configVar.getName();
|
||||
const auto it = RegisteredConfigVars.find(name);
|
||||
if (it == RegisteredConfigVars.end() || it->second != &configVar) {
|
||||
DuskConfigLog.fatal("Tried to unregister CVar '{}' that is not registered!", name);
|
||||
}
|
||||
|
||||
const auto layer = configVar.getLayer();
|
||||
if (layer == ConfigVarLayer::Value || layer == ConfigVarLayer::Speedrun) {
|
||||
UnregisteredConfigVars.insert_or_assign(
|
||||
std::string{name}, configVar.getImpl()->dumpToJson(configVar));
|
||||
}
|
||||
|
||||
RegisteredConfigVars.erase(it);
|
||||
configVar.unmarkRegistered();
|
||||
}
|
||||
|
||||
void ConfigVarBase::markRegistered() {
|
||||
@@ -406,21 +452,24 @@ void ConfigVarBase::markRegistered() {
|
||||
registered = true;
|
||||
}
|
||||
|
||||
void dusk::config::FinishRegistration() {
|
||||
RegistrationDone = true;
|
||||
void ConfigVarBase::unmarkRegistered() {
|
||||
if (!registered)
|
||||
abort();
|
||||
|
||||
registered = false;
|
||||
}
|
||||
|
||||
void dusk::config::LoadFromUserPreferences() {
|
||||
void load_from_user_preferences() {
|
||||
const auto configJsonPath = GetConfigJsonPath();
|
||||
if (configJsonPath.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto configPathString = io::fs_path_to_string(configJsonPath);
|
||||
LoadFromFileName(configPathString.c_str());
|
||||
load_from_file_name(configPathString.c_str());
|
||||
}
|
||||
|
||||
static void LoadFromPath(const char* path) {
|
||||
auto data = dusk::io::FileStream::ReadAllBytes(path);
|
||||
auto data = io::FileStream::ReadAllBytes(path);
|
||||
|
||||
json j = json::parse(data);
|
||||
if (!j.is_object()) {
|
||||
@@ -428,11 +477,16 @@ static void LoadFromPath(const char* path) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnregisteredConfigVars.clear();
|
||||
|
||||
for (const auto& el : j.items()) {
|
||||
const auto& key = el.key();
|
||||
auto configVar = RegisteredConfigVars.find(key);
|
||||
if (configVar == RegisteredConfigVars.end()) {
|
||||
DuskConfigLog.error("Unknown key '{}' found in config!", key);
|
||||
DuskConfigLog.debug("Unknown key '{}' found in config! If this gets registered later, "
|
||||
"that's acceptable!",
|
||||
key);
|
||||
UnregisteredConfigVars.emplace(key, el.value());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -444,11 +498,7 @@ static void LoadFromPath(const char* path) {
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::config::LoadFromFileName(const char* path) {
|
||||
if (!RegistrationDone) {
|
||||
DuskConfigLog.fatal("Registration not finished yet!");
|
||||
}
|
||||
|
||||
void load_from_file_name(const char* path) {
|
||||
DuskConfigLog.info("Loading config from '{}'", path);
|
||||
|
||||
try {
|
||||
@@ -466,7 +516,21 @@ void dusk::config::LoadFromFileName(const char* path) {
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::config::Save() {
|
||||
void load_arg_override(std::string_view name, std::string_view value) {
|
||||
const auto cVar = GetConfigVar(name);
|
||||
if (!cVar) {
|
||||
UnregisteredConfigVarOverrides.emplace(name, value);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cVar->getImpl()->loadFromArg(*cVar, value);
|
||||
} catch (const std::exception& e) {
|
||||
DuskLog.fatal("Unable to parse: '{}': {}", value, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
const auto configJsonPath = GetConfigJsonPath();
|
||||
if (configJsonPath.empty()) {
|
||||
return;
|
||||
@@ -484,6 +548,10 @@ void dusk::config::Save() {
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pair : UnregisteredConfigVars) {
|
||||
j[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto tempConfigJsonPath = GetTempConfigJsonPath(configJsonPath);
|
||||
io::FileStream::WriteAllText(tempConfigJsonPath, j.dump(4));
|
||||
@@ -493,14 +561,14 @@ void dusk::config::Save() {
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::config::ClearAllActionBindings(int port) {
|
||||
void ClearAllActionBindings(int port) {
|
||||
for (auto& actionBinding : getActionBinds() | std::views::values) {
|
||||
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
|
||||
}
|
||||
Save();
|
||||
save();
|
||||
}
|
||||
|
||||
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||
ConfigVarBase* GetConfigVar(std::string_view name) {
|
||||
const auto configVar = RegisteredConfigVars.find(name);
|
||||
if (configVar != RegisteredConfigVars.end()) {
|
||||
return configVar->second;
|
||||
@@ -509,8 +577,69 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void dusk::config::EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
|
||||
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
|
||||
for (auto& pair : RegisteredConfigVars) {
|
||||
callback(*pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
Subscription subscribe(std::string_view name, ChangeCallback callback) {
|
||||
const auto token = s_nextChangeToken++;
|
||||
s_changeSubscriptions[std::string{name}].push_back({token, std::move(callback)});
|
||||
s_changeTokenNames.emplace(token, std::string{name});
|
||||
return token;
|
||||
}
|
||||
|
||||
void unsubscribe(Subscription token) {
|
||||
const auto nameIt = s_changeTokenNames.find(token);
|
||||
if (nameIt == s_changeTokenNames.end()) {
|
||||
DuskConfigLog.fatal("Tried to unsubscribe unknown change token {}!", token);
|
||||
}
|
||||
|
||||
const auto subsIt = s_changeSubscriptions.find(nameIt->second);
|
||||
auto& subscriptions = subsIt->second;
|
||||
std::erase_if(
|
||||
subscriptions, [token](const ChangeSubscription& sub) { return sub.token == token; });
|
||||
if (subscriptions.empty()) {
|
||||
s_changeSubscriptions.erase(subsIt);
|
||||
}
|
||||
s_changeTokenNames.erase(nameIt);
|
||||
}
|
||||
|
||||
bool ConfigVarBase::has_subscribers() const {
|
||||
return s_changeSubscriptions.contains(name);
|
||||
}
|
||||
|
||||
void ConfigVarBase::notify_changed(const void* previousValue) {
|
||||
const auto subsIt = s_changeSubscriptions.find(name);
|
||||
if (subsIt == s_changeSubscriptions.end()) {
|
||||
return;
|
||||
}
|
||||
if (std::ranges::find(s_activeChangeNotifications, name) != s_activeChangeNotifications.end()) {
|
||||
DuskConfigLog.error("Recursive change notification for CVar '{}' suppressed", name);
|
||||
return;
|
||||
}
|
||||
|
||||
s_activeChangeNotifications.push_back(name);
|
||||
// Copied so callbacks can subscribe/unsubscribe safely.
|
||||
const auto subscriptions = subsIt->second;
|
||||
for (const auto& sub : subscriptions) {
|
||||
sub.callback(*this, previousValue);
|
||||
}
|
||||
s_activeChangeNotifications.pop_back();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
for (auto& pair : RegisteredConfigVars) {
|
||||
pair.second->unmarkRegistered();
|
||||
}
|
||||
|
||||
RegisteredConfigVars.clear();
|
||||
UnregisteredConfigVars.clear();
|
||||
UnregisteredConfigVarOverrides.clear();
|
||||
s_changeSubscriptions.clear();
|
||||
s_changeTokenNames.clear();
|
||||
s_activeChangeNotifications.clear();
|
||||
}
|
||||
|
||||
} // namespace dusk::config
|
||||
Reference in New Issue
Block a user