Config: renaming & add change subscriptions

This commit is contained in:
Luke Street
2026-07-06 23:48:21 -06:00
parent 074b43a089
commit d40a30ee13
17 changed files with 495 additions and 232 deletions
+70 -7
View File
@@ -1,9 +1,11 @@
#ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP
#include <concepts>
#include <functional>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include "nlohmann/json.hpp"
#include "config_var.hpp"
namespace dusk::config {
@@ -40,7 +42,7 @@ public:
[[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0;
};
template<ConfigValue T>
template <ConfigValue T>
class ConfigImpl : public ConfigImplBase {
// Just downcasting the references...
void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final {
@@ -90,20 +92,28 @@ public:
void Register(ConfigVarBase& configVar);
/**
* \brief Indicate that all registrations have happened and everything should lock in.
* \brief Unregister a CVar, detaching it from the config system.
*
* If the CVar carries a user-set value (Value or Speedrun layer), it is stashed as an
* unregistered key: Save() keeps writing it, and a later Register() of the same name restores
* it through the normal back-fill path. The CVar may be destroyed after this returns.
*/
void FinishRegistration();
void unregister(ConfigVarBase& configVar);
/**
* \brief Load config from the standard user preferences location.
*/
void LoadFromUserPreferences();
void LoadFromFileName(const char* path);
void load_from_user_preferences();
void load_from_file_name(const char* path);
void load_arg_override(std::string_view name, std::string_view value);
void shutdown();
/**
* \brief Save the config to file.
*/
void Save();
void save();
/**
* \brief Get a registered CVar by name.
@@ -124,6 +134,59 @@ void ClearAllActionBindings(int port);
*/
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
/**
* \brief Type-erased change callback. previousValue points at the value before the mutation
* (a `const T*` for a `ConfigVar<T>`) and is valid only for the duration of the call.
*/
using ChangeCallback = std::function<void(ConfigVarBase& cVar, const void* previousValue)>;
/**
* \brief Token identifying a change subscription. 0 is never a valid token.
*/
using Subscription = u64;
/**
* \brief Subscribe to changes of the named CVar (registered or not yet registered).
*
* Fired synchronously on the mutating thread (in practice the game thread) whenever the CVar's
* effective value changes at runtime: setValue, override/speedrun setters and clears. Values
* applied by config load or launch arguments do *not* notify: loads happen during startup
* before the subsystems callbacks push values into are initialized, and each subsystem reads
* its initial value itself at its own init. Callbacks may mutate other CVars; a nested
* mutation of the same CVar applies but does not re-notify.
*/
Subscription subscribe(std::string_view name, ChangeCallback callback);
/**
* \brief Typed convenience overload: the callback receives the current and previous values.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription subscribe(
ConfigVar<T>& cVar, Callback&& callback) {
return subscribe(cVar.getName(),
[&cVar, cb = std::forward<Callback>(callback)](ConfigVarBase&, const void* previousValue) {
cb(cVar.getValue(), *static_cast<const T*>(previousValue));
});
}
void unsubscribe(Subscription token);
/**
* \brief Register a CVar and attach a change callback in one step.
*
* Useful for pushing settings into external systems (e.g. aurora) from one place instead of
* every UI setter. The callback fires only for runtime changes (see subscribe); the value
* loaded from config or launch arguments does not fire it — the external system reads its
* initial value itself at its own initialization.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription Register(
ConfigVar<T>& cVar, Callback&& onChange) {
auto subscription = subscribe(cVar, std::forward<Callback>(onChange));
Register(static_cast<ConfigVarBase&>(cVar));
return subscription;
}
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config;
+82 -6
View File
@@ -2,10 +2,12 @@
#define DUSK_CONFIG_VAR_HPP
#include "dolphin/types.h"
#include <type_traits>
#include <concepts>
#include <cstdlib>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
/**
* The configuration system.
@@ -69,7 +71,7 @@ protected:
/**
* The name of this CVar, used in the configuration file.
*/
const char* name;
std::string name;
/**
* Whether this CVar has been registered with the global managing logic.
@@ -87,8 +89,8 @@ protected:
*/
const ConfigImplBase* impl;
ConfigVarBase(const char* name, const ConfigImplBase* impl);
virtual ~ConfigVarBase() = default;
ConfigVarBase(const ConfigVarBase&) = delete;
ConfigVarBase(std::string name, const ConfigImplBase* impl);
/**
* Check that the CVar is registered, aborting if this is not the case.
@@ -98,7 +100,22 @@ protected:
abort();
}
/**
* Whether any change subscriber (see config::subscribe) is attached to this CVar's name.
*/
[[nodiscard]] bool has_subscribers() const;
/**
* Notify change subscribers (see config::subscribe) that the effective value of this CVar
* changed. Called by mutators after the change has been applied; previousValue points at
* the old value (a `const T*` for a `ConfigVar<T>`), valid only for the duration of the
* call.
*/
void notify_changed(const void* previousValue);
public:
virtual ~ConfigVarBase();
/**
* Get the name of this CVar, used in the configuration file.
*/
@@ -121,6 +138,7 @@ public:
* This is necessary to make it legal to access.
*/
void markRegistered();
void unmarkRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
@@ -155,6 +173,7 @@ template <typename T>
concept ConfigValue =
!std::is_const_v<T>
&& !std::is_volatile_v<T>
&& std::equality_comparable<T>
&& (std::is_same_v<T, bool>
|| ConfigValueInteger<T>
|| std::is_same_v<T, f32>
@@ -166,6 +185,9 @@ concept ConfigValue =
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl();
template <ConfigValue T>
class ConfigImpl;
template <typename T>
struct ConfigEnumRange {
static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min();
@@ -192,10 +214,12 @@ public:
* @param arg Arguments to forward to construct the default value.
*/
template <typename... Args>
ConfigVar(const char* name, Args&&... arg)
: ConfigVarBase(name, GetConfigImpl<T>()), defaultValue(std::forward<Args>(arg)...),
ConfigVar(std::string name, Args&&... arg)
: ConfigVarBase(std::move(name), GetConfigImpl<T>()), defaultValue(std::forward<Args>(arg)...),
value(), overrideValue() {}
ConfigVar(ConfigVar const&) = delete;
/**
* \brief Get the current value of the CVar.
*
@@ -234,6 +258,7 @@ public:
*/
void setValue(T newValue, bool replaceOverride = true) {
checkRegistered();
const auto previous = previous_for_notify();
value = std::move(newValue);
if (replaceOverride) {
@@ -242,6 +267,7 @@ public:
} else if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
notify_if_changed(previous);
}
operator const T&() {
@@ -258,8 +284,10 @@ public:
*/
void setOverrideValue(T newValue) {
checkRegistered();
const auto previous = previous_for_notify();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
notify_if_changed(previous);
}
/**
@@ -273,25 +301,31 @@ public:
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
priorLayer = layer;
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun;
notify_if_changed(previous);
}
}
void clearOverride() {
checkRegistered();
if (layer == ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = ConfigVarLayer::Value;
notify_if_changed(previous);
}
}
void clearSpeedrunOverride() override {
checkRegistered();
if (layer == ConfigVarLayer::Speedrun) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = priorLayer;
notify_if_changed(previous);
}
}
@@ -305,6 +339,48 @@ public:
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
}
private:
// The config loader applies values through the silent load_* methods below.
friend class ConfigImpl<T>;
/**
* Copy of the effective value before a mutation, taken only when someone is subscribed.
*/
[[nodiscard]] std::optional<T> previous_for_notify() const {
return has_subscribers() ? std::optional<T>{getValue()} : std::nullopt;
}
/**
* Notify subscribers if the effective value actually changed across a mutation.
*/
void notify_if_changed(const std::optional<T>& previous) {
if (previous.has_value() && !(getValue() == *previous)) {
notify_changed(&*previous);
}
}
/**
* setValue(newValue, false) without notifying change subscribers. Used when loading config:
* loads happen during startup before the subsystems change callbacks push values into are
* initialized, and each subsystem applies the loaded value itself at its own init.
*/
void load_value(T newValue) {
checkRegistered();
value = std::move(newValue);
if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
}
/**
* setOverrideValue without notifying change subscribers (see load_value).
*/
void load_override_value(T newValue) {
checkRegistered();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
}
};
using ActionBindConfigVar = ConfigVar<int>;
+2 -1
View File
@@ -7,7 +7,8 @@
namespace dusk {
using namespace config;
using config::ConfigVar;
using config::ActionBindConfigVar;
enum class BloomMode : int {
Off = 0,
+280 -151
View File
@@ -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
+4 -4
View File
@@ -9,7 +9,7 @@ namespace dusk::config {
bool copy = var.getValue();
if (ImGui::Checkbox(title, &copy)) {
var.setValue(copy);
Save();
save();
return true;
}
@@ -20,7 +20,7 @@ namespace dusk::config {
float val = var;
if (ImGui::SliderFloat(label, &val, v_min, v_max, format, flags)) {
var.setValue(val);
Save();
save();
return true;
}
@@ -31,7 +31,7 @@ namespace dusk::config {
int val = var;
if (ImGui::SliderInt(label, &val, v_min, v_max, format, flags)) {
var.setValue(val);
Save();
save();
return true;
}
@@ -42,7 +42,7 @@ namespace dusk::config {
bool copy = p_selected.getValue();
if (ImGui::MenuItem(label, shortcut, &copy, enabled)) {
p_selected.setValue(copy);
Save();
save();
return true;
}
+2 -2
View File
@@ -254,7 +254,7 @@ namespace dusk {
if (ImGui::IsKeyPressed(ImGuiKey_F11)) {
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
config::save();
}
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
@@ -336,7 +336,7 @@ namespace dusk {
if constexpr (SupportsProcessRestart) {
if (ImGui::Button("Retry (Auto backend)")) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
config::save();
RestartRequested = true;
IsRunning = false;
}
+1 -1
View File
@@ -73,7 +73,7 @@ namespace dusk {
bool disableWaterRefraction = getSettings().game.disableWaterRefraction;
if (ImGui::Checkbox("Disable Water Refraction", &disableWaterRefraction)) {
getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction);
config::Save();
config::save();
}
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
ImGui::EndMenu();
+3 -1
View File
@@ -1,5 +1,6 @@
#include "dusk/settings.h"
#include "dusk/config.hpp"
#include <aurora/aurora.h>
namespace dusk {
@@ -267,7 +268,8 @@ void registerSettings() {
Register(g_userSettings.game.touchCameraYSensitivity);
Register(g_userSettings.game.minimalHUD);
Register(g_userSettings.game.hudScale);
Register(g_userSettings.game.pauseOnFocusLost);
Register(g_userSettings.game.pauseOnFocusLost,
[](const bool& value, const bool&) { aurora_set_pause_on_focus_lost(value); });
Register(g_userSettings.game.enableDiscordPresence);
Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier);
-1
View File
@@ -37,7 +37,6 @@ void resetForSpeedrunMode() {
getSettings().game.invincibleEnemies.setSpeedrunValue(false);
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
aurora_set_pause_on_focus_lost(false);
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
getSettings().game.recordingMode.setSpeedrunValue(false);
+5 -5
View File
@@ -278,7 +278,7 @@ ControllerConfigWindow::ControllerConfigWindow(bool prelaunch) {
void ControllerConfigWindow::hide(bool close) {
stop_rumble_test();
cancel_pending_binding();
config::Save();
config::save();
Window::hide(close);
}
@@ -403,7 +403,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings();
ClearAllActionBindings(port);
config::ClearAllActionBindings(port);
refresh_controller_page();
});
@@ -417,7 +417,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings();
ClearAllActionBindings(port);
config::ClearAllActionBindings(port);
});
const u32 controllerCount = PADCount();
@@ -439,7 +439,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port);
PADSerializeMappings();
ClearAllActionBindings(port);
config::ClearAllActionBindings(port);
});
}
break;
@@ -1125,7 +1125,7 @@ void ControllerConfigWindow::poll_pending_binding() {
return;
}
mPendingActionBinding->setValue(button);
config::Save();
config::save();
finish_pending_binding(completedPort);
}
return;
+4
View File
@@ -51,6 +51,8 @@ struct ControlProps {
float h = 0.0f;
float scale = 1.0f;
ControlAnchor anchor = ControlAnchor::None;
bool operator==(const ControlProps&) const = default;
};
struct ControlRect {
@@ -76,6 +78,8 @@ struct ControlLayout {
int version = Version;
std::map<std::string, ControlProps, std::less<> > controls;
bool operator==(const ControlLayout&) const = default;
};
constexpr std::array<std::string_view, 9> kControlLayoutIds = {
+1 -1
View File
@@ -300,7 +300,7 @@ void GraphicsTuner::show() {
}
void GraphicsTuner::hide(bool close) {
config::Save();
config::save();
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
+1 -1
View File
@@ -309,7 +309,7 @@ void persist_disc_choice(const std::string& path, iso::ValidationError validatio
getSettings().backend.isoPath.setValue(path);
getSettings().backend.isoVerification.setValue(verification);
config::Save();
config::save();
if (previousPath != path || previousVerification != verification) {
iso::log_verification_state(path, verification);
+1 -1
View File
@@ -106,7 +106,7 @@ PresetWindow::PresetWindow() : WindowSmall("modal", "modal-dialog") {
if (cmd == NavCommand::Confirm) {
apply();
getSettings().backend.wasPresetChosen.setValue(true);
config::Save();
config::save();
hide(true);
return true;
}
+26 -31
View File
@@ -248,7 +248,6 @@ void reset_for_speedrun_mode() {
getSettings().game.invincibleEnemies.setSpeedrunValue(false);
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
aurora_set_pause_on_focus_lost(false);
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
getSettings().game.recordingMode.setSpeedrunValue(false);
@@ -263,7 +262,6 @@ void clear_speedrun_overrides() {
void restore_from_speedrun_mode() {
clear_speedrun_overrides();
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
}
std::filesystem::path normalized_display_path(const std::filesystem::path& path) {
@@ -447,7 +445,7 @@ SelectButton& config_bool_select(
return;
}
var.setValue(value);
config::Save();
config::save();
if (callback) {
callback(value);
}
@@ -468,7 +466,7 @@ void add_speedrun_disabled_option(Pane& leftPane, Pane& rightPane, ConfigVar<boo
config_bool_select(leftPane, rightPane, var, {
.key = key,
.helpText = helpText,
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
});
}
@@ -481,7 +479,7 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
.setValue =
[&var, min, max](int value) {
var.setValue(std::clamp(value, min, max) / 100.0f);
config::Save();
config::save();
},
.isDisabled = std::move(isDisabled),
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
@@ -503,12 +501,12 @@ SelectButton& config_int_select(Pane& leftPane, Pane& rightPane, ConfigVar<int>&
std::string suffix = "") {
auto& button = leftPane.add_child<NumberButton>(NumberButton::Props{
.key = std::move(key),
.getValue = [&var] { return var; },
.getValue = [&var] { return var.getValue(); },
.setValue =
[&var, min, max, callback = std::move(onChange)](int value) {
const int clampedValue = std::clamp(value, min, max);
var.setValue(clampedValue);
config::Save();
config::save();
if (callback) {
callback(clampedValue);
}
@@ -680,7 +678,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
config::save();
});
}
pane.add_rml("<br/>Changes require a restart.");
@@ -707,7 +705,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.graphicsBackend.setValue(
std::string{backend_id(backend)});
config::Save();
config::save();
});
}
pane.add_rml("<br/>Changes require a restart.");
@@ -738,7 +736,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.cardFileType.setValue(i);
config::Save();
config::save();
});
}
});
@@ -755,7 +753,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
config::save();
}),
rightPane, [](Pane& pane) { pane.clear(); });
leftPane.register_control(leftPane.add_button("Restore Default Window Size").on_pressed([] {
@@ -786,7 +784,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
{
.key = "Pause on Focus Lost",
.helpText = "Pause the game when window focus is lost.",
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
.isDisabled = [] { return IsMobile || getSettings().game.speedrunMode; },
});
leftPane.register_control(
@@ -818,7 +815,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFpsOverlay.setValue(false);
config::Save();
config::save();
});
for (int i = 0; i < static_cast<int>(kFpsOverlayCornerNames.size()); ++i) {
pane.add_button(
@@ -834,7 +831,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFpsOverlay.setValue(true);
getSettings().video.fpsOverlayCorner.setValue(i);
config::Save();
config::save();
});
}
pane.add_rml(
@@ -850,7 +847,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
const auto windowSize = aurora::window::get_window_size();
dusk::getSettings().video.lastWindowWidth.setValue(windowSize.width);
dusk::getSettings().video.lastWindowHeight.setValue(windowSize.height);
dusk::config::Save();
dusk::config::save();
}
},
.isDisabled = [] { return IsMobile; },
@@ -956,7 +953,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableFrameInterpolation.setValue(static_cast<FrameInterpMode>(i));
android::update_surface_frame_rate();
config::Save();
config::save();
});
}
pane.add_rml(kUnlockFramerateHelpText);
@@ -1151,7 +1148,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
leftPane.add_section("Tools");
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
"Hold Tab to increase game speed by up to 4x.",
[] { return getSettings().game.speedrunMode; });
[] { return getSettings().game.speedrunMode.getValue(); });
addOption("Reset Key (" + Rml::String{hotkeys::DO_RESET} + ")",
getSettings().game.enableResetKeybind,
"Press " + Rml::String{hotkeys::DO_RESET} + " to reset the game.");
@@ -1170,7 +1167,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.setValue =
[](int value) {
getSettings().audio.masterVolume.setValue(value);
config::Save();
config::save();
audio::SetMasterVolume(audio::MasterVolumeToLinear(value / 100.0f));
},
.isModified =
@@ -1262,9 +1259,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.setValue =
[](int value) {
getSettings().game.damageMultiplier.setValue(value);
config::Save();
config::save();
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
.isModified =
[] {
return getSettings().game.damageMultiplier.getValue() !=
@@ -1408,7 +1405,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
[] {
return kMagicArmorModes[static_cast<u8>(getSettings().game.armorRupeeDrain.getValue())];
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
.isModified =
[] {
return getSettings().game.armorRupeeDrain.getValue() !=
@@ -1427,7 +1424,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.armorRupeeDrain.setValue(static_cast<MagicArmorMode>(i));
config::Save();
config::save();
});
}
pane.add_rml(
@@ -1480,13 +1477,13 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableAchievementToasts.setValue(true);
getSettings().game.enableControllerToasts.setValue(true);
config::Save();
config::save();
});
pane.add_button("Select None").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableAchievementToasts.setValue(false);
getSettings().game.enableControllerToasts.setValue(false);
config::Save();
config::save();
});
pane.add_section("Types");
@@ -1502,7 +1499,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
auto& v = getSettings().game.enableAchievementToasts;
v.setValue(!v.getValue());
config::Save();
config::save();
});
pane.add_button(
{
@@ -1514,7 +1511,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
auto& v = getSettings().game.enableControllerToasts;
v.setValue(!v.getValue());
config::Save();
config::save();
});
pane.add_rml("<br/>Choose which notifications can be displayed.");
});
@@ -1580,7 +1577,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
}
}
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
});
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
{
@@ -1617,15 +1614,13 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
[i] {
return getSettings().game.menuScalingMode.getValue() ==
static_cast<MenuScaling>(i);
;
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.menuScalingMode.setValue(
static_cast<MenuScaling>(i));
;
config::Save();
config::save();
});
}
pane.add_rml("<br/>Changes how the Collection and File Select menus scale to your "
@@ -1651,7 +1646,7 @@ void SettingsWindow::update() {
}
void SettingsWindow::hide(bool close) {
config::Save();
config::save();
Window::hide(close);
}
+1 -1
View File
@@ -585,7 +585,7 @@ bool TouchControlsEditor::handle_nav_command(Rml::Event& event, NavCommand cmd)
void TouchControlsEditor::save_layout() {
mWorkingLayout.version = ControlLayout::Version;
getSettings().game.touchControlsLayout.setValue(mWorkingLayout);
config::Save();
config::save();
mDoAud_seStartMenu(kSoundItemChange);
pop();
}
+12 -18
View File
@@ -265,7 +265,7 @@ void main01(void) {
if (dusk::getSettings().video.rememberWindowSize && !dusk::getSettings().video.enableFullscreen) {
dusk::getSettings().video.lastWindowWidth.setValue(event->windowSize.width);
dusk::getSettings().video.lastWindowHeight.setValue(event->windowSize.height);
dusk::config::Save();
dusk::config::save();
}
break;
case AURORA_DISPLAY_SCALE_CHANGED:
@@ -430,16 +430,7 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
const auto name = std::string_view(cvarArg).substr(0, sep);
const auto value = std::string_view(cvarArg).substr(sep + 1);
const auto cVar = dusk::config::GetConfigVar(name);
if (!cVar) {
DuskLog.fatal("Unknown --cvar name: '{}'", name);
}
try {
cVar->getImpl()->loadFromArg(*cVar, value);
} catch (const std::exception& e) {
DuskLog.fatal("Unable to parse: '{}': {}", value, e.what());
}
dusk::config::load_arg_override(name, value);
}
}
@@ -510,7 +501,6 @@ int game_main(int argc, char* argv[]) {
mainCalled = true;
dusk::registerSettings();
dusk::config::FinishRegistration();
cxxopts::ParseResult parsed_arg_options;
@@ -551,10 +541,7 @@ int game_main(int argc, char* argv[]) {
log_build_info();
dusk::config::LoadFromUserPreferences();
if (dusk::getSettings().game.speedrunMode) {
dusk::resetForSpeedrunMode();
}
dusk::config::load_from_user_preferences();
ApplyCVarOverrides(parsed_arg_options["cvar"]);
dusk::android::update_surface_frame_rate();
dusk::crash_reporting::initialize();
@@ -618,6 +605,12 @@ int game_main(int argc, char* argv[]) {
auroraInfo = aurora_initialize(argc, argv, &config);
}
// Apply after aurora_initialize: speedrun mode mutates cvars whose change callbacks push
// values into aurora.
if (dusk::getSettings().game.speedrunMode) {
dusk::resetForSpeedrunMode();
}
#ifdef DUSK_DISCORD
if (dusk::getSettings().game.enableDiscordPresence) {
dusk::discord::initialize();
@@ -700,7 +693,7 @@ int game_main(int argc, char* argv[]) {
dusk::getSettings().backend.isoPath.setValue(dvd_path);
dusk::getSettings().backend.isoVerification.setValue(
dusk::DiscVerificationState::Unknown);
dusk::config::Save();
dusk::config::save();
dusk::IsGameLaunched = true;
}
} else {
@@ -723,7 +716,7 @@ int game_main(int argc, char* argv[]) {
saveConfigBeforePrelaunch = true;
}
if (saveConfigBeforePrelaunch) {
dusk::config::Save();
dusk::config::save();
}
if (!dusk::getSettings().backend.skipPreLaunchUI) {
@@ -814,6 +807,7 @@ int game_main(int argc, char* argv[]) {
#endif
dusk::ui::shutdown();
dusk::texture_replacements::shutdown();
dusk::config::shutdown();
aurora_shutdown();
return 0;