mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-07 12:03:24 -04:00
Config: renaming & add change subscriptions
This commit is contained in:
+70
-7
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
namespace dusk {
|
||||
|
||||
using namespace config;
|
||||
using config::ConfigVar;
|
||||
using config::ActionBindConfigVar;
|
||||
|
||||
enum class BloomMode : int {
|
||||
Off = 0,
|
||||
|
||||
+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
|
||||
@@ -9,7 +9,7 @@ namespace dusk::config {
|
||||
bool copy = var.getValue();
|
||||
if (ImGui::Checkbox(title, ©)) {
|
||||
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, ©, enabled)) {
|
||||
p_selected.setValue(copy);
|
||||
Save();
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -300,7 +300,7 @@ void GraphicsTuner::show() {
|
||||
}
|
||||
|
||||
void GraphicsTuner::hide(bool close) {
|
||||
config::Save();
|
||||
config::save();
|
||||
mRoot->RemoveAttribute("open");
|
||||
if (close) {
|
||||
mPendingClose = true;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user