mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
The largest new feature is that it is now possible to include other files into the RML by using the "include" tag. This is useful for making markup files more modular. Also added some more options for existing things (e.g. built-in detail options).
177 lines
4.9 KiB
C++
177 lines
4.9 KiB
C++
//
|
|
//
|
|
|
|
#include "OptionsManager.h"
|
|
#include "Option.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "osapi/osregistry.h"
|
|
|
|
namespace {
|
|
std::pair<SCP_string, SCP_string> parse_key(const SCP_string& key)
|
|
{
|
|
auto point_pos = key.find('.');
|
|
if (point_pos == SCP_string::npos) {
|
|
// Invalid key
|
|
return std::pair<SCP_string, SCP_string>();
|
|
}
|
|
|
|
auto section = key.substr(0, point_pos);
|
|
auto section_key = key.substr(point_pos + 1);
|
|
|
|
return std::make_pair(section, section_key);
|
|
}
|
|
} // namespace
|
|
|
|
namespace options {
|
|
|
|
OptionsManager::OptionsManager() = default;
|
|
OptionsManager::~OptionsManager() = default;
|
|
|
|
OptionsManager* options::OptionsManager::instance()
|
|
{
|
|
static OptionsManager instance;
|
|
return &instance;
|
|
}
|
|
std::unique_ptr<json_t> OptionsManager::getValueFromConfig(const SCP_string& key) const
|
|
{
|
|
auto override_iter = _config_overrides.find(key);
|
|
if (override_iter != _config_overrides.end()) {
|
|
// We return a reference to an existing object so we need to increment the reference count
|
|
json_incref(override_iter->second.get());
|
|
return std::unique_ptr<json_t>(override_iter->second.get());
|
|
}
|
|
|
|
auto changed_iter = _changed_values.find(key);
|
|
if (changed_iter != _changed_values.end()) {
|
|
json_incref(changed_iter->second.get());
|
|
return std::unique_ptr<json_t>(changed_iter->second.get());
|
|
}
|
|
|
|
auto parts = parse_key(key);
|
|
if (parts.first.empty()) {
|
|
// Invalid key
|
|
throw std::runtime_error("Invalid key");
|
|
}
|
|
|
|
auto value = os_config_read_string(parts.first.c_str(), parts.second.c_str());
|
|
|
|
if (value == nullptr) {
|
|
// TODO: This is not really an error but I would like to avoid return nullptr here...
|
|
throw std::runtime_error("No value available");
|
|
}
|
|
|
|
json_error_t err;
|
|
auto el = json_loads(value, JSON_DECODE_ANY, &err);
|
|
if (el == nullptr) {
|
|
throw json_exception(err);
|
|
}
|
|
|
|
return std::unique_ptr<json_t>(el);
|
|
}
|
|
void OptionsManager::setConfigValue(const SCP_string& key,std::unique_ptr<json_t>&& value)
|
|
{
|
|
_changed_values[key] = std::move(value);
|
|
}
|
|
void OptionsManager::setOverride(const SCP_string& key, const SCP_string& json)
|
|
{
|
|
json_error_t err;
|
|
auto el = json_loads(json.c_str(), JSON_DECODE_ANY, &err);
|
|
if (el == nullptr) {
|
|
return;
|
|
}
|
|
_config_overrides.emplace(key, std::unique_ptr<json_t>(el));
|
|
}
|
|
const OptionBase* OptionsManager::addOption(std::unique_ptr<const OptionBase>&& option)
|
|
{
|
|
_options.emplace_back(std::move(option));
|
|
_optionsSorted = false; // Order got invalidated by adding a new option
|
|
|
|
auto ptr = _options.back().get();
|
|
_optionsMapping.emplace(ptr->getConfigKey(), ptr);
|
|
return ptr;
|
|
}
|
|
void OptionsManager::removeOption(const OptionBase* option)
|
|
{
|
|
_optionsMapping.erase(option->getConfigKey());
|
|
_options.erase(
|
|
std::remove_if(_options.begin(), _options.end(),
|
|
[option](const std::unique_ptr<const OptionBase>& ptr) { return ptr.get() == option; }));
|
|
}
|
|
const SCP_vector<std::unique_ptr<const options::OptionBase>>& OptionsManager::getOptions()
|
|
{
|
|
if (!_optionsSorted) {
|
|
// Keep options sorted by only sorting them when necessary
|
|
|
|
std::sort(_options.begin(), _options.end(),
|
|
[](const std::unique_ptr<const OptionBase>& left, const std::unique_ptr<const OptionBase>& right) {
|
|
return *left < *right;
|
|
});
|
|
|
|
_optionsSorted = true;
|
|
}
|
|
return _options;
|
|
}
|
|
bool OptionsManager::persistOptionChanges(const options::OptionBase* option)
|
|
{
|
|
auto iter = _changed_values.find(option->getConfigKey());
|
|
|
|
if (iter == _changed_values.end()) {
|
|
// No changes for this option
|
|
return true;
|
|
}
|
|
|
|
auto parts = parse_key(iter->first);
|
|
if (parts.first.empty()) {
|
|
// Invalid key
|
|
return false;
|
|
}
|
|
|
|
auto val = json_dump_string(iter->second.get(), JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY);
|
|
|
|
os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str());
|
|
|
|
auto changed = option->valueChanged(iter->second.get());
|
|
|
|
// Always erase even if it wasn't actually changed so later persistChanges() calls don't try to save the value again
|
|
_changed_values.erase(iter);
|
|
|
|
return changed;
|
|
}
|
|
SCP_vector<const options::OptionBase*> OptionsManager::persistChanges()
|
|
{
|
|
for (auto& entry : _changed_values) {
|
|
auto parts = parse_key(entry.first);
|
|
if (parts.first.empty()) {
|
|
// Invalid key
|
|
continue;
|
|
}
|
|
|
|
auto val = json_dump_string(entry.second.get(), JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY);
|
|
|
|
os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str());
|
|
}
|
|
SCP_vector<const options::OptionBase*> unchanged;
|
|
|
|
// Only notify options once all values have been written to config
|
|
for (auto& entry : _changed_values) {
|
|
auto iter = _optionsMapping.find(entry.first);
|
|
if (iter != _optionsMapping.end()) {
|
|
if (!iter->second->valueChanged(entry.second.get())) {
|
|
unchanged.push_back(iter->second);
|
|
}
|
|
}
|
|
}
|
|
_changed_values.clear();
|
|
|
|
return unchanged;
|
|
}
|
|
void OptionsManager::discardChanges() { _changed_values.clear(); }
|
|
void OptionsManager::loadInitialValues()
|
|
{
|
|
for (auto& opt : _options) {
|
|
opt->loadInitial();
|
|
}
|
|
}
|
|
|
|
} // namespace options
|