mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 17:48:46 -10:00
* use std::move() for local variables that are copied but not reused Addresses ~140 Coverity "Variable copied when it could be moved" defects across 74 files. Every std::move() targets a local variable whose value is consumed by push_back, emplace, assignment, lambda capture, or a function call and is never read again afterward. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * clang: use std::make_shared * clang: use std::make_unique * address feedback --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
629 lines
19 KiB
C++
629 lines
19 KiB
C++
#pragma once
|
|
|
|
#include "globalincs/flagset.h"
|
|
#include "globalincs/pstypes.h"
|
|
#include "libs/jansson.h"
|
|
#include "options/OptionsManager.h"
|
|
#include "localization/localize.h"
|
|
#include "parse/parselo.h"
|
|
|
|
#include <functional>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <optional>
|
|
|
|
namespace options {
|
|
|
|
enum class OptionType { Range, Selection };
|
|
|
|
enum class ExpertLevel { Beginner, Advanced, Expert };
|
|
|
|
enum class PresetKind { Basic, Low, Medium, High, Ultra };
|
|
|
|
// clang-format off
|
|
FLAG_LIST(OptionFlags) {
|
|
/**
|
|
* @brief Force the option menu to display this as a multi value selection.
|
|
*
|
|
* This can be useful if an option typically has multiple values but it is also possible for
|
|
* this option to have exactly two values which may cause the option menu to display it as a
|
|
* boolean option even though the option is not designed for that.
|
|
*/
|
|
ForceMultiValueSelection = 0,
|
|
/**
|
|
* @brief Whether or not the option is built-in to FSO's retail Options UI
|
|
*
|
|
* This can be useful if an option should be ignored in cases like the SCP Options UI where
|
|
* it's only appropriate to display options that are not otherwise accessible to retail players.
|
|
*/
|
|
RetailBuiltinOption = 1,
|
|
/**
|
|
* @brief Forces the range type slider to use an integer instead of a float
|
|
*
|
|
* This can be useful if an option cannot accept float values.
|
|
*/
|
|
RangeTypeInteger = 2,
|
|
|
|
NUM_VALUES
|
|
};
|
|
// clang-format on
|
|
|
|
struct ValueDescription {
|
|
const SCP_string display;
|
|
const SCP_string serialized;
|
|
|
|
ValueDescription(SCP_string _display, SCP_string _serialized);
|
|
};
|
|
|
|
class OptionBase {
|
|
template <typename T>
|
|
friend class OptionBuilder;
|
|
|
|
protected:
|
|
OptionsManager* _parent = nullptr;
|
|
ExpertLevel _expert_level = ExpertLevel::Expert;
|
|
|
|
SCP_string _config_key;
|
|
|
|
std::pair<const char*, int> _category = {"Other", 1829};
|
|
|
|
SCP_string _title;
|
|
SCP_string _description;
|
|
|
|
int _importance = 0;
|
|
|
|
float _min = 0;
|
|
float _max = 1;
|
|
|
|
bool _is_once = false;
|
|
|
|
SCP_unordered_map<PresetKind, SCP_string> _preset_values;
|
|
|
|
flagset<OptionFlags> _flags;
|
|
|
|
std::optional<std::unique_ptr<json_t>> getConfigValue() const;
|
|
|
|
OptionBase(SCP_string config_key, SCP_string title, SCP_string description);
|
|
|
|
public:
|
|
virtual ~OptionBase() = 0;
|
|
|
|
ExpertLevel getExpertLevel() const;
|
|
void setExpertLevel(ExpertLevel expert_level);
|
|
|
|
const SCP_string getCategory() const;
|
|
void setCategory(const std::pair<const char*, int>& category);
|
|
|
|
int getImportance() const;
|
|
void setImportance(int importance);
|
|
|
|
std::pair<float, float> getRangeValues() const;
|
|
void setRangeValues(float min, float max);
|
|
|
|
const flagset<OptionFlags>& getFlags() const;
|
|
void setFlags(const flagset<OptionFlags>& flags);
|
|
|
|
bool getIsOnce() const;
|
|
void setIsOnce(bool is_once);
|
|
|
|
const SCP_string& getConfigKey() const;
|
|
const SCP_string& getTitle() const;
|
|
const SCP_string& getDescription() const;
|
|
|
|
void setPreset(PresetKind preset, const SCP_string& value);
|
|
|
|
bool persistChanges() const;
|
|
|
|
virtual bool valueChanged(const json_t* val) const = 0;
|
|
|
|
virtual ValueDescription getCurrentValueDescription() const = 0;
|
|
|
|
virtual void setValueDescription(const ValueDescription& desc) const = 0;
|
|
|
|
virtual OptionType getType() const = 0;
|
|
|
|
virtual void parseDefaultSetting() const = 0;
|
|
|
|
virtual SCP_vector<ValueDescription> getValidValues() const = 0;
|
|
|
|
virtual ValueDescription getValueFromRange(float interpolant) const = 0;
|
|
|
|
virtual float getInterpolantFromValue(const ValueDescription& val) const = 0;
|
|
|
|
virtual void loadInitial() const = 0;
|
|
|
|
friend bool operator<(const OptionBase& lhs, const OptionBase& rhs);
|
|
friend bool operator>(const OptionBase& lhs, const OptionBase& rhs);
|
|
friend bool operator<=(const OptionBase& lhs, const OptionBase& rhs);
|
|
friend bool operator>=(const OptionBase& lhs, const OptionBase& rhs);
|
|
};
|
|
|
|
template <typename T>
|
|
using ValueEnumerator = std::function<SCP_vector<T>()>;
|
|
template <typename T>
|
|
using ValueDeserializer = std::function<T(const json_t*)>;
|
|
template <typename T>
|
|
using ValueSerializer = std::function<json_t*(const T&)>;
|
|
template <typename T>
|
|
using ValueDisplay = std::function<SCP_string(const T&)>;
|
|
template <typename T>
|
|
using DefaultValueFunctor = std::function<T()>;
|
|
template <typename T>
|
|
using ValueChangeListener = std::function<bool(const T&, bool initial)>;
|
|
template <typename T>
|
|
using ValueInterpolator = std::function<T(float)>;
|
|
template <typename T>
|
|
using ValueDeinterpolator = std::function<float(const T&)>;
|
|
using ValueParser = std::function<void()>;
|
|
|
|
template <typename T>
|
|
class ValueFunctor {
|
|
T _value;
|
|
|
|
public:
|
|
ValueFunctor() = default;
|
|
explicit ValueFunctor(T value) : _value(value) {}
|
|
|
|
T operator()() { return _value; }
|
|
};
|
|
|
|
template <typename T>
|
|
class Option : public OptionBase {
|
|
protected:
|
|
ValueEnumerator<T> _valueEnumerator;
|
|
ValueDeserializer<T> _deserializer;
|
|
ValueSerializer<T> _serializer;
|
|
ValueDisplay<T> _displayFunc;
|
|
DefaultValueFunctor<T> _defaultValueFunc = ValueFunctor<T>();
|
|
ValueChangeListener<T> _changeListener;
|
|
ValueInterpolator<T> _interpolator;
|
|
ValueDeinterpolator<T> _deinterpolator;
|
|
ValueParser _parser;
|
|
|
|
bool _hasCustomDefaultFunc = false;
|
|
|
|
OptionType _type = OptionType::Selection; // By default the generic type is always a selection
|
|
|
|
ValueDescription toDescription(const T& val) const
|
|
{
|
|
auto json = _serializer(val);
|
|
auto json_str = json_dump_string_new(json, JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY);
|
|
if (_displayFunc) {
|
|
try {
|
|
return ValueDescription(_displayFunc(val), json_str);
|
|
} catch (const std::exception& e) {
|
|
static bool rel_warning_triggered = false;
|
|
if (!rel_warning_triggered) {
|
|
rel_warning_triggered = true;
|
|
ReleaseWarning(LOCATION, "Option %s has an invalid value %s! Please change the value!", _title.c_str(), json_str.c_str());
|
|
}
|
|
//This will logspam once a frame, but it's super rare, so it should be fine
|
|
mprintf(("Tried to show an invalid options value %s for option %s! Likely due to a malformed ini: %s\n", json_str.c_str(), _title.c_str(), e.what()));
|
|
return ValueDescription(json_str, json_str);
|
|
}
|
|
} else {
|
|
return ValueDescription(json_str, json_str);
|
|
}
|
|
}
|
|
std::unique_ptr<json_t> fromDescription(const ValueDescription& desc) const
|
|
{
|
|
json_error_t err;
|
|
std::unique_ptr<json_t> el(json_loads(desc.serialized.c_str(), JSON_DECODE_ANY, &err));
|
|
if (el == nullptr) {
|
|
// Shouldn't really happen...
|
|
throw json_exception(err);
|
|
}
|
|
return el;
|
|
}
|
|
|
|
public:
|
|
Option(const SCP_string& config_key, const SCP_string& title, const SCP_string& description)
|
|
: OptionBase(config_key, title, description)
|
|
{
|
|
}
|
|
~Option() override = default;
|
|
|
|
OptionType getType() const override { return _type; }
|
|
void setType(OptionType type) { _type = type; }
|
|
|
|
void parseDefaultSetting() const override
|
|
{
|
|
if (!_parser) {
|
|
Warning(LOCATION, "%s cannot set default values", _config_key.c_str());
|
|
skip_to_start_of_string_either("$Option Key:", "#END");
|
|
} else {
|
|
Assertion(_hasCustomDefaultFunc, "Option must have a default value function in order to use parser!");
|
|
_parser();
|
|
}
|
|
}
|
|
|
|
SCP_vector<ValueDescription> getValidValues() const override
|
|
{
|
|
Assertion(_valueEnumerator, "Invalid value enumerator detected!");
|
|
Assertion(_type == OptionType::Selection, "getValidValues may only be called on selection option!");
|
|
|
|
auto values = _valueEnumerator();
|
|
SCP_vector<ValueDescription> descriptions;
|
|
descriptions.reserve(values.size());
|
|
|
|
for (const auto& val : values) {
|
|
descriptions.emplace_back(toDescription(val));
|
|
}
|
|
|
|
return descriptions;
|
|
}
|
|
|
|
ValueDescription getValueFromRange(float interpolant) const override
|
|
{
|
|
Assertion(_interpolator, "Need a valid interpolator!");
|
|
Assertion(_type == OptionType::Range, "getValidValues may only be called on range option!");
|
|
try {
|
|
return toDescription(_interpolator(interpolant));
|
|
} catch (const std::exception&) {
|
|
return toDescription(_defaultValueFunc());
|
|
}
|
|
}
|
|
float getInterpolantFromValue(const ValueDescription& val) const override
|
|
{
|
|
Assertion(_deinterpolator, "Need a valid deinterpolator!");
|
|
Assertion(_type == OptionType::Range, "getInterpolantFromValue may only be called on range option!");
|
|
try {
|
|
auto el = fromDescription(val);
|
|
return _deinterpolator(_deserializer(el.get()));
|
|
} catch (const std::exception&) {
|
|
return 0.0f;
|
|
}
|
|
}
|
|
T getValue() const
|
|
{
|
|
try {
|
|
std::optional<std::unique_ptr<json_t>> config_value = getConfigValue();
|
|
//missing keys are signalled via the optional
|
|
if (!config_value.has_value()) {
|
|
return _defaultValueFunc();
|
|
}
|
|
return _deserializer(config_value->get());
|
|
} catch (const std::exception&) {
|
|
// deserializers are allowed to throw on error
|
|
return _defaultValueFunc();
|
|
}
|
|
}
|
|
ValueDescription getCurrentValueDescription() const override
|
|
{
|
|
auto val = getValue();
|
|
return toDescription(val);
|
|
}
|
|
void setValueDescription(const ValueDescription& desc) const override
|
|
{
|
|
std::unique_ptr<json_t> el;
|
|
try {
|
|
el = fromDescription(desc);
|
|
} catch (const std::exception&) {
|
|
// Functions are allowed to throw
|
|
}
|
|
if (el) {
|
|
_parent->setConfigValue(_config_key, std::move(el));
|
|
}
|
|
}
|
|
bool valueChanged(const json_t* val) const override
|
|
{
|
|
try {
|
|
if (_changeListener) {
|
|
return _changeListener(_deserializer(val), false);
|
|
}
|
|
return false; // Value not changed, requires restart
|
|
} catch (const std::exception&) {
|
|
// Functions are allowed to throw
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void loadInitial() const override
|
|
{
|
|
if (!_changeListener) {
|
|
// This requires a change listener
|
|
return;
|
|
}
|
|
|
|
try {
|
|
_changeListener(getValue(), true);
|
|
} catch (const std::exception&) {
|
|
}
|
|
}
|
|
|
|
const DefaultValueFunctor<T>& getDefaultValueFunc() const { return _defaultValueFunc; }
|
|
void setDefaultValueFunc(const DefaultValueFunctor<T>& defaultValueFunc) { _defaultValueFunc = defaultValueFunc; }
|
|
|
|
void setHasCustomDefaultFunc(bool value) { _hasCustomDefaultFunc = value; }
|
|
|
|
const ValueDeserializer<T>& getDeserializer() const { return _deserializer; }
|
|
void setDeserializer(const ValueDeserializer<T>& converter) { _deserializer = converter; }
|
|
|
|
const ValueSerializer<T>& getSerializer() const { return _serializer; }
|
|
void setSerializer(const ValueSerializer<T>& serializer) { _serializer = serializer; }
|
|
|
|
const ValueDisplay<T>& getDisplayFunc() const { return _displayFunc; }
|
|
void setDisplayFunc(const ValueDisplay<T>& displayFunc) { _displayFunc = displayFunc; }
|
|
|
|
const ValueEnumerator<T>& getValueEnumerator() const { return _valueEnumerator; }
|
|
void setValueEnumerator(const ValueEnumerator<T>& valueEnumerator)
|
|
{
|
|
_valueEnumerator = valueEnumerator;
|
|
_type = OptionType::Selection;
|
|
}
|
|
|
|
const ValueChangeListener<T>& getChangeListener() const { return _changeListener; }
|
|
void setChangeListener(const ValueChangeListener<T>& changeListener) { _changeListener = changeListener; }
|
|
|
|
void setParser(ValueParser parser) { _parser = std::move(parser); }
|
|
|
|
const ValueInterpolator<T>& getInterpolator() const { return _interpolator; }
|
|
const ValueDeinterpolator<T>& getDeinterpolator() const { return _deinterpolator; }
|
|
void setInterpolator(const ValueInterpolator<T>& interpolator, const ValueDeinterpolator<T>& deinterpolator)
|
|
{
|
|
_interpolator = interpolator;
|
|
_deinterpolator = deinterpolator;
|
|
_type = OptionType::Range;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class VectorEnumerator {
|
|
SCP_vector<T> _values;
|
|
|
|
public:
|
|
explicit VectorEnumerator(SCP_vector<T> values) : _values(std::move(values)) {}
|
|
|
|
SCP_vector<T> operator()() { return _values; }
|
|
};
|
|
|
|
template <typename T>
|
|
class MapValueDisplay {
|
|
SCP_unordered_map<T, std::pair<const char*, int>> _mapping;
|
|
|
|
public:
|
|
explicit MapValueDisplay(SCP_unordered_map<T, std::pair<const char*, int>> mapping)
|
|
: _mapping(std::move(mapping))
|
|
{
|
|
}
|
|
|
|
SCP_string operator()(const T& value) const
|
|
{
|
|
auto iter = _mapping.find(value);
|
|
if (iter == _mapping.end()) {
|
|
throw std::runtime_error("Display called with invalid value!");
|
|
}
|
|
return XSTR(iter->second.first, iter->second.second);
|
|
}
|
|
};
|
|
|
|
namespace internal {
|
|
// This would be so much easier with Concepts...
|
|
template <typename T>
|
|
typename std::enable_if<!std::is_enum<T>::value, void>::type set_defaults(Option<T>& /*opt*/)
|
|
{
|
|
// No defaults for the generic case since we don't know anything about this
|
|
}
|
|
|
|
template <>
|
|
void set_defaults<SCP_string>(Option<SCP_string>& opt);
|
|
|
|
template <>
|
|
void set_defaults<float>(Option<float>& opt);
|
|
|
|
template <>
|
|
void set_defaults<int>(Option<int>& opt);
|
|
|
|
template <>
|
|
void set_defaults<bool>(Option<bool>& opt);
|
|
|
|
template <typename T>
|
|
typename std::enable_if<std::is_enum<T>::value, void>::type set_defaults(Option<T>& opt)
|
|
{
|
|
opt.setSerializer([](T val) { return json_pack("I", static_cast<json_int_t>(val)); });
|
|
opt.setDeserializer([](const json_t* value) {
|
|
json_int_t i;
|
|
|
|
json_error_t err;
|
|
if (json_unpack_ex((json_t*)value, &err, 0, "I", &i) != 0) {
|
|
throw json_exception(err);
|
|
}
|
|
|
|
return static_cast<T>(i);
|
|
});
|
|
}
|
|
|
|
template <typename T, size_t N>
|
|
void set_defaults(Option<flagset<T, N>>& opt)
|
|
{
|
|
opt.setSerializer([](const flagset<T, N>& val) { return json_pack("I", static_cast<json_int_t>(val.to_u64())); });
|
|
opt.setDeserializer([](const json_t* value) {
|
|
json_int_t i;
|
|
|
|
json_error_t err;
|
|
if (json_unpack_ex((json_t*)value, &err, 0, "I", &i) != 0) {
|
|
throw json_exception(err);
|
|
}
|
|
|
|
flagset<T, N> out;
|
|
out.from_u64((std::uint64_t)i);
|
|
return out;
|
|
});
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
template <typename T>
|
|
class OptionBuilder {
|
|
Option<T> _instance;
|
|
|
|
SCP_unordered_map<PresetKind, T> _preset_values;
|
|
const std::variant<SCP_string, std::pair<const char*, int>>&_title, &_description;
|
|
|
|
public:
|
|
OptionBuilder(const SCP_string& config_key, const std::variant<SCP_string, std::pair<const char*, int>>& title, const std::variant<SCP_string, std::pair<const char*, int>>& description)
|
|
: _instance(
|
|
config_key,
|
|
std::holds_alternative<SCP_string>(title) ? std::get<SCP_string>(title) : std::get<std::pair<const char*, int>>(title).first,
|
|
std::holds_alternative<SCP_string>(description) ? std::get<SCP_string>(description) : std::get<std::pair<const char*, int>>(description).first),
|
|
_title(title),
|
|
_description(description)
|
|
{
|
|
internal::set_defaults(_instance);
|
|
}
|
|
|
|
OptionBuilder(const OptionBuilder&) = delete;
|
|
OptionBuilder& operator=(const OptionBuilder&) = delete;
|
|
|
|
OptionBuilder(OptionBuilder&&) noexcept = delete;
|
|
OptionBuilder& operator=(OptionBuilder&&) noexcept = delete;
|
|
//Set the category of the option
|
|
OptionBuilder& category(const std::pair<const char*, int>& category)
|
|
{
|
|
_instance.setCategory(category);
|
|
return *this;
|
|
}
|
|
//Set the level value of the option. Can be "Beginniner", "Advanced", or "Expert"
|
|
OptionBuilder& level(ExpertLevel level)
|
|
{
|
|
_instance.setExpertLevel(level);
|
|
return *this;
|
|
}
|
|
//Causes the default value of the option to be permanently set to a specific value
|
|
OptionBuilder& default_val(const T& val)
|
|
{
|
|
_instance.setDefaultValueFunc(ValueFunctor<T>(val));
|
|
_instance.setHasCustomDefaultFunc(false);
|
|
return *this;
|
|
}
|
|
//Causes the default value of the option to be set dynamically using a function
|
|
OptionBuilder& default_func(const DefaultValueFunctor<T>& func)
|
|
{
|
|
_instance.setDefaultValueFunc(func);
|
|
_instance.setHasCustomDefaultFunc(true);
|
|
return *this;
|
|
}
|
|
//Deserialize an option for persisting changes
|
|
OptionBuilder& deserializer(const ValueDeserializer<T>& converter)
|
|
{
|
|
_instance.setDeserializer(converter);
|
|
return *this;
|
|
}
|
|
//Serialize an option value for display
|
|
OptionBuilder& serializer(const ValueSerializer<T>& serializer)
|
|
{
|
|
_instance.setSerializer(serializer);
|
|
return *this;
|
|
}
|
|
//Builds an enum list of values for the option
|
|
OptionBuilder& enumerator(const ValueEnumerator<T>& enumerator)
|
|
{
|
|
_instance.setValueEnumerator(enumerator);
|
|
return *this;
|
|
}
|
|
//The valid values for the option
|
|
OptionBuilder& values(const SCP_vector<std::pair<T, std::pair<const char*, int>>>& value_display_pairs)
|
|
{
|
|
SCP_vector<T> values;
|
|
SCP_unordered_map<T, std::pair<const char*, int>> display_mapping;
|
|
|
|
for (auto& p : value_display_pairs) {
|
|
values.push_back(p.first);
|
|
display_mapping.emplace(p.first, p.second);
|
|
}
|
|
|
|
_instance.setValueEnumerator(VectorEnumerator<T>(std::move(values)));
|
|
_instance.setDisplayFunc(MapValueDisplay<T>(std::move(display_mapping)));
|
|
return *this;
|
|
}
|
|
//The string to display for the option values, usually a function that returns a string
|
|
OptionBuilder& display(const ValueDisplay<T>& display)
|
|
{
|
|
_instance.setDisplayFunc(display);
|
|
return *this;
|
|
}
|
|
//The code to run if the option value is changed
|
|
OptionBuilder& change_listener(const ValueChangeListener<T>& listener)
|
|
{
|
|
_instance.setChangeListener(listener);
|
|
return *this;
|
|
}
|
|
//The code to run in order to parse the default setting. Option must use default_func instead of defaul_val!
|
|
OptionBuilder& parser(const ValueParser& parser)
|
|
{
|
|
_instance.setParser(parser);
|
|
return *this;
|
|
}
|
|
//The global variable to bind the option to immediately.
|
|
OptionBuilder& bind_to(T* dest)
|
|
{
|
|
return change_listener([dest](const T& val, bool) {
|
|
*dest = val;
|
|
return true;
|
|
});
|
|
}
|
|
//The global variable to bind the option to once. Will require game restart to persist changes.
|
|
OptionBuilder& bind_to_once(T* dest)
|
|
{
|
|
_instance.setIsOnce(true);
|
|
return change_listener([dest](const T& val, bool initial) {
|
|
if (initial) {
|
|
*dest = val;
|
|
}
|
|
return initial;
|
|
});
|
|
}
|
|
//Set the minimum and maximum range for a "slider" type option
|
|
OptionBuilder& range(T min, T max)
|
|
{
|
|
Assertion(min <= max, "Invalid number range!");
|
|
_instance.setRangeValues(static_cast<float>(min), static_cast<float>(max));
|
|
_instance.setInterpolator(
|
|
[min, max](float f) { return min + static_cast<T>((max - min) * f); },
|
|
[min, max](T f) { return static_cast<float>(f - min) / static_cast<float>(max - min); });
|
|
return *this;
|
|
}
|
|
//Set the preset value for the option
|
|
OptionBuilder& preset(PresetKind kind, const T& value)
|
|
{
|
|
_preset_values.emplace(kind, value);
|
|
return *this;
|
|
}
|
|
//Set the option importance, used for sorting
|
|
OptionBuilder& importance(int value)
|
|
{
|
|
_instance.setImportance(value);
|
|
return *this;
|
|
}
|
|
//Set the option flags
|
|
OptionBuilder& flags(const flagset<OptionFlags>& flags)
|
|
{
|
|
_instance.setFlags(flags);
|
|
return *this;
|
|
}
|
|
//Finishes building the option and returns a pointer to it
|
|
std::shared_ptr<const Option<T>> finish()
|
|
{
|
|
for (auto& val : _preset_values) {
|
|
_instance.setPreset(val.first, json_dump_string_new(_instance.getSerializer()(val.second),
|
|
JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY));
|
|
}
|
|
auto opt_ptr = std::make_shared<Option<T>>(_instance);
|
|
|
|
if (std::holds_alternative<std::pair<const char*, int>>(_title)) {
|
|
const auto& xstr_info = std::get<std::pair<const char*, int>>(_title);
|
|
lcl_delayed_xstr(opt_ptr->_title, xstr_info.first, xstr_info.second);
|
|
}
|
|
|
|
if (std::holds_alternative<std::pair<const char*, int>>(_description)) {
|
|
const auto& xstr_info = std::get<std::pair<const char*, int>>(_description);
|
|
lcl_delayed_xstr(opt_ptr->_description, xstr_info.first, xstr_info.second);
|
|
}
|
|
|
|
OptionsManager::instance()->addOption(opt_ptr);
|
|
return opt_ptr;
|
|
}
|
|
};
|
|
|
|
} // namespace options
|