#ifndef DUSK_CONFIG_VAR_HPP #define DUSK_CONFIG_VAR_HPP #include "dolphin/types.h" #include #include #include /** * The configuration system. * * Configuration works via "configuration variables" aka "CVars". Each stores a single value that * may be individually written to/from a configuration file. * * CVars, like ogres, have layers. Higher layers (e.g. a set value) override lower layers * (e.g. the default value). * * To define a CVar, simply make a global variable of type ConfigVar, * and make sure Register() is called on it during program startup. * * config_var.hpp contains the simplest "just the configuration vars themselves". * This should be safe to include for files that need to *access* configuration, * without blowing up compile times on implementation details. * * config.hpp on the other hand contains far more calls for mutating, loading, and defining CVars. */ namespace dusk::config { /** * \brief Layers that a configuration variable can currently be at. * * A configuration variable can be on one of multiple *layers*, which determines where * the current value is coming from. */ enum class ConfigVarLayer : u8 { /** * The CVar is at the default value defined by the application code. */ Default, /** * The CVar has been modified by the user and may be saved to config. */ Value, /** * The CVar is modified by launch argument, overruling the normal config value. * Will not get saved to config. */ Override, /** * The CVar is temporarily overridden by speedrun mode. * Will not get saved to config. Cleared when speedrun mode is disabled. * Lower priority than Override, so launch args still win. */ Speedrun, }; class ConfigImplBase; /** * Base class that all CVars inherit from. * You want the templated ConfigVar instead for actual usage. */ class ConfigVarBase { protected: /** * The name of this CVar, used in the configuration file. */ const char* name; /** * Whether this CVar has been registered with the global managing logic. * If this is not done, it is not functional. */ bool registered; /** * The layer this CVar is at. */ ConfigVarLayer layer; /** * Pointer to an implementation struct for various load/save calls. */ const ConfigImplBase* impl; ConfigVarBase(const char* name, const ConfigImplBase* impl); virtual ~ConfigVarBase() = default; /** * Check that the CVar is registered, aborting if this is not the case. */ void checkRegistered() const { if (!registered) abort(); } public: /** * Get the name of this CVar, used in the configuration file. */ [[nodiscard]] const char* getName() const noexcept; /** * Get the pointer to the implementation struct. */ [[nodiscard]] const ConfigImplBase* getImpl() const noexcept; /** * Get the layer this CVar is currently at. */ [[nodiscard]] constexpr ConfigVarLayer getLayer() const noexcept { return layer; } /** * Mark this CVar as being registered with the central save/load logic. * This is necessary to make it legal to access. */ void markRegistered(); /** * Clear a speedrun-mode override if one is active on this CVar. * Safe to call on any CVar, no-op if not at the Speedrun layer. */ virtual void clearSpeedrunOverride() {} }; template concept ConfigValueInteger = std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; /** * \brief Concept that defines the legal set of types that can be used for CVar values. * * Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool), * strings, or enums of the basic primitives. */ template concept ConfigValue = !std::is_const_v && !std::is_volatile_v && (std::is_same_v || ConfigValueInteger || std::is_same_v || std::is_same_v || std::is_same_v || (std::is_enum_v && ConfigValueInteger>)); template const ConfigImplBase* GetConfigImpl(); template struct ConfigEnumRange { static constexpr auto min = std::numeric_limits>::min(); static constexpr auto max = std::numeric_limits>::max(); }; /** * \brief A CVar storing values. * * @tparam T The type of value stored in the CVar. */ template class ConfigVar : public ConfigVarBase { T defaultValue; T value; T overrideValue; ConfigVarLayer priorLayer = ConfigVarLayer::Default; public: /** * \brief Construct a CVar. * * @param name The name of this CVar. Must be unique. * @param arg Arguments to forward to construct the default value. */ template ConfigVar(const char* name, Args&&... arg) : ConfigVarBase(name, GetConfigImpl()), defaultValue(std::forward(arg)...), value(), overrideValue() {} /** * \brief Get the current value of the CVar. * * This reference is not guaranteed to remain up-to-date after modification of the CVar. * It will, however, remain sound to access. */ [[nodiscard]] constexpr const T& getValue() const noexcept { checkRegistered(); switch (layer) { case ConfigVarLayer::Default: return defaultValue; case ConfigVarLayer::Value: return value; case ConfigVarLayer::Override: case ConfigVarLayer::Speedrun: return overrideValue; default: abort(); } } [[nodiscard]] constexpr const T& getDefaultValue() const noexcept { checkRegistered(); return defaultValue; } /** * \brief Change the value of a CVar. * * The new value is always stored at the Value layer. * * @param newValue The new value the CVar will get. * @param replaceOverride If true, clear an existing override layer if there is one. * If this is false and there is an override layer, * the result of getValue() will not change immediately. */ void setValue(T newValue, bool replaceOverride = true) { checkRegistered(); value = std::move(newValue); if (replaceOverride) { overrideValue = {}; layer = ConfigVarLayer::Value; } else if (layer != ConfigVarLayer::Override) { layer = ConfigVarLayer::Value; } } operator const T&() { return getValue(); } /** * \brief Give a CVar an override value. * * This overrides (but does not replace) the apparent set value of this CVar. * The overriden value will not get saved to config. * * @param newValue The new value the CVar will get. */ void setOverrideValue(T newValue) { checkRegistered(); overrideValue = std::move(newValue); layer = ConfigVarLayer::Override; } /** * \brief Give a CVar a speedrun-mode override value. * * Lower priority than a launch-arg override. Cleared when speedrun mode is disabled. * The overridden value will not get saved to config. * * @param newValue The new value the CVar will get. */ void setSpeedrunValue(T newValue) { checkRegistered(); if (layer != ConfigVarLayer::Override) { priorLayer = layer; overrideValue = std::move(newValue); layer = ConfigVarLayer::Speedrun; } } void clearOverride() { checkRegistered(); if (layer == ConfigVarLayer::Override) { overrideValue = {}; layer = ConfigVarLayer::Value; } } void clearSpeedrunOverride() override { checkRegistered(); if (layer == ConfigVarLayer::Speedrun) { overrideValue = {}; layer = priorLayer; } } /** * \brief Get the user-persisted value, ignoring any temporary overrides. * * Used by Save() to write the correct value even when a speedrun override is active. */ [[nodiscard]] constexpr const T& getValueForSave() const noexcept { checkRegistered(); const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer; return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value; } }; using ActionBindConfigVar = ConfigVar; } #endif // DUSK_CONFIG_VAR_HPP