639 lines
18 KiB
C++
639 lines
18 KiB
C++
/* Copyright (C) 2016 Eric Wasylishen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
|
|
#include "common/settings.hh"
|
|
#include "common/threads.hh"
|
|
#include "common/fs.hh"
|
|
#include <common/log.hh>
|
|
|
|
namespace settings
|
|
{
|
|
// parse_exception
|
|
parse_exception::parse_exception(std::string str) : _what(std::move(str)) { }
|
|
|
|
const char *parse_exception::what() const noexcept { return _what.c_str(); }
|
|
|
|
// nameset
|
|
|
|
nameset::nameset(const char *str) : vector<std::string>({str}) { }
|
|
nameset::nameset(const std::string &str) : vector<std::string>({str}) { }
|
|
nameset::nameset(const std::initializer_list<const char *> &strs) : vector(strs.begin(), strs.end()) { }
|
|
nameset::nameset(const std::initializer_list<std::string> &strs) : vector(strs) { }
|
|
|
|
// setting_base
|
|
|
|
setting_base::setting_base(
|
|
setting_container *dictionary, const nameset &names, const setting_group *group, const char *description)
|
|
: _names(names), _group(group), _description(description)
|
|
{
|
|
Q_assert(_names.size() > 0);
|
|
|
|
if (dictionary) {
|
|
dictionary->registerSetting(this);
|
|
}
|
|
}
|
|
|
|
bool setting_base::changeSource(source newSource)
|
|
{
|
|
if (newSource >= _source) {
|
|
_source = newSource;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// setting_func
|
|
|
|
setting_func::setting_func(setting_container *dictionary, const nameset &names, std::function<void(source)> func,
|
|
const setting_group *group, const char *description)
|
|
: setting_base(dictionary, names, group, description), _func(func)
|
|
{
|
|
}
|
|
|
|
bool setting_func::copyFrom(const setting_base &other) { return true; }
|
|
|
|
void setting_func::reset() { }
|
|
|
|
bool setting_func::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
_func(source);
|
|
return true;
|
|
}
|
|
|
|
std::string setting_func::stringValue() const { return ""; }
|
|
|
|
std::string setting_func::format() const { return ""; }
|
|
|
|
// setting_bool
|
|
|
|
bool setting_bool::parseInternal(parser_base_t &parser, source source, bool truthValue)
|
|
{
|
|
// boolean flags can be just flagged themselves
|
|
if (parser.parse_token(PARSE_PEEK)) {
|
|
// if the token that follows is 1, 0 or -1, we'll handle it
|
|
// as a value, otherwise it's probably part of the next option.
|
|
if (parser.token == "1" || parser.token == "0" || parser.token == "-1") {
|
|
parser.parse_token();
|
|
|
|
int intval = std::stoi(parser.token);
|
|
|
|
const bool f = (intval != 0 && intval != -1) ? truthValue : !truthValue; // treat 0 and -1 as false
|
|
|
|
setValue(f, source);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
setValue(truthValue, source);
|
|
|
|
return true;
|
|
}
|
|
|
|
setting_bool::setting_bool(setting_container *dictionary, const nameset &names, bool v,
|
|
const setting_group *group, const char *description)
|
|
: setting_value(dictionary, names, v, group, description)
|
|
{
|
|
}
|
|
|
|
bool setting_bool::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
return parseInternal(parser, source, true);
|
|
}
|
|
|
|
std::string setting_bool::stringValue() const { return _value ? "1" : "0"; }
|
|
|
|
std::string setting_bool::format() const { return _default ? "[0]" : ""; }
|
|
|
|
// setting_invertible_bool
|
|
|
|
static nameset extendNames(const nameset &names)
|
|
{
|
|
nameset n = names;
|
|
|
|
for (auto &name : names) {
|
|
n.push_back("no" + name);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
setting_invertible_bool::setting_invertible_bool(setting_container *dictionary, const nameset &names, bool v,
|
|
const setting_group *group, const char *description)
|
|
: setting_bool(dictionary, extendNames(names), v, group, description)
|
|
{
|
|
}
|
|
|
|
bool setting_invertible_bool::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
return parseInternal(parser, source, settingName.compare(0, 2, "no") == 0 ? false : true);
|
|
}
|
|
|
|
// setting_redirect
|
|
|
|
setting_redirect::setting_redirect(setting_container *dictionary, const nameset &names,
|
|
const std::initializer_list<setting_base *> &settings, const setting_group *group,
|
|
const char *description)
|
|
: setting_base(dictionary, names, group, description), _settings(settings)
|
|
{
|
|
}
|
|
|
|
bool setting_redirect::copyFrom(const setting_base &other) { return true; }
|
|
|
|
void setting_redirect::reset() { }
|
|
|
|
bool setting_redirect::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
// this is a bit ugly, but we run the parse function for
|
|
// every setting that we redirect from. for every entry
|
|
// except the last, we'll backup & restore the state.
|
|
for (size_t i = 0; i < _settings.size(); i++) {
|
|
if (i != _settings.size() - 1) {
|
|
parser.push_state();
|
|
}
|
|
|
|
if (!_settings[i]->parse(settingName, parser, source)) {
|
|
return false;
|
|
}
|
|
|
|
if (i != _settings.size() - 1) {
|
|
parser.pop_state();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string setting_redirect::stringValue() const { return _settings[0]->stringValue(); }
|
|
|
|
std::string setting_redirect::format() const { return _settings[0]->format(); }
|
|
|
|
// setting_group
|
|
|
|
setting_group performance_group{"Performance", 10};
|
|
setting_group logging_group{"Logging", 5};
|
|
setting_group game_group{"Game", 15};
|
|
|
|
// setting_container
|
|
|
|
setting_container::~setting_container() = default;
|
|
|
|
void setting_container::reset()
|
|
{
|
|
for (auto setting : _settings) {
|
|
setting->reset();
|
|
}
|
|
}
|
|
|
|
const char *setting_base::sourceString() const
|
|
{
|
|
switch (_source) {
|
|
case source::DEFAULT: return "default";
|
|
case source::GAME_TARGET: return "game target";
|
|
case source::MAP: return "map";
|
|
case source::COMMANDLINE: return "command line";
|
|
default: FError("Error: unknown setting source");
|
|
}
|
|
}
|
|
|
|
// setting_string
|
|
|
|
setting_string::setting_string(setting_container *dictionary, const nameset &names, std::string v,
|
|
const std::string_view &format, const setting_group *group, const char *description)
|
|
: setting_value(dictionary, names, v, group, description), _format(format)
|
|
{
|
|
}
|
|
|
|
bool setting_string::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
if (parser.parse_token()) {
|
|
setValue(parser.token, source);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string setting_string::stringValue() const { return _value; }
|
|
|
|
std::string setting_string::format() const { return _format; }
|
|
|
|
// setting_path
|
|
|
|
setting_path::setting_path(setting_container *dictionary, const nameset &names, fs::path v,
|
|
const setting_group *group, const char *description)
|
|
: setting_value(dictionary, names, v, group, description)
|
|
{
|
|
}
|
|
|
|
bool setting_path::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
// make sure we can parse token out
|
|
if (!parser.parse_token()) {
|
|
return false;
|
|
}
|
|
|
|
setValue(parser.token, source);
|
|
return true;
|
|
}
|
|
|
|
std::string setting_path::stringValue() const { return _value.string(); }
|
|
|
|
std::string setting_path::format() const { return "\"relative/path\" or \"C:/absolute/path\""; }
|
|
|
|
// setting_set
|
|
|
|
setting_set::setting_set(setting_container *dictionary, const nameset &names,
|
|
const std::string_view &format, const setting_group *group,
|
|
const char *description)
|
|
: setting_base(dictionary, names, group, description), _format(format)
|
|
{
|
|
}
|
|
|
|
const std::unordered_set<std::string> &setting_set::values() const { return _values; }
|
|
|
|
void setting_set::addValue(const std::string &value, source newSource)
|
|
{
|
|
if (changeSource(newSource)) {
|
|
_values.insert(value);
|
|
}
|
|
}
|
|
|
|
bool setting_set::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
if (!parser.parse_token(PARSE_PEEK))
|
|
return false;
|
|
|
|
parser.parse_token();
|
|
addValue(parser.token, source);
|
|
return true;
|
|
}
|
|
|
|
bool setting_set::copyFrom(const setting_base &other)
|
|
{
|
|
if (auto *casted = dynamic_cast<const setting_set *>(&other)) {
|
|
_values = casted->_values;
|
|
_source = casted->_source;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void setting_set::reset()
|
|
{
|
|
_values.clear();
|
|
_source = source::DEFAULT;
|
|
}
|
|
|
|
std::string setting_set::format() const { return _format; }
|
|
|
|
std::string setting_set::stringValue() const
|
|
{
|
|
std::string result;
|
|
|
|
for (auto &v : _values) {
|
|
if (!result.empty()) {
|
|
result += ' ';
|
|
}
|
|
|
|
result += '\"' + v + '\"';
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// setting_vec3
|
|
|
|
qvec3d setting_vec3::transformVec3Value(const qvec3d &val) const
|
|
{
|
|
return val;
|
|
}
|
|
|
|
setting_vec3::setting_vec3(setting_container *dictionary, const nameset &names, vec_t a, vec_t b, vec_t c,
|
|
const setting_group *group, const char *description)
|
|
: setting_value(dictionary, names, transformVec3Value({a, b, c}), group, description)
|
|
{
|
|
}
|
|
|
|
void setting_vec3::setValue(const qvec3d &f, source newsource)
|
|
{
|
|
setting_value::setValue(transformVec3Value(f), newsource);
|
|
}
|
|
|
|
bool setting_vec3::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
qvec3d vec;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
if (!parser.parse_token()) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
vec[i] = std::stod(parser.token);
|
|
} catch (std::exception &) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setValue(vec, source);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string setting_vec3::stringValue() const { return qv::to_string(_value); }
|
|
|
|
std::string setting_vec3::format() const { return "x y z"; }
|
|
|
|
// setting_mangle
|
|
|
|
qvec3d setting_mangle::transformVec3Value(const qvec3d &val) const { return qv::vec_from_mangle(val); }
|
|
|
|
bool setting_mangle::parse(const std::string &settingName, parser_base_t &parser, source source)
|
|
{
|
|
qvec3d vec {};
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
if (!parser.parse_token(PARSE_PEEK)) {
|
|
break;
|
|
}
|
|
|
|
try {
|
|
vec[i] = std::stod(parser.token);
|
|
} catch (std::exception &) {
|
|
break;
|
|
}
|
|
|
|
parser.parse_token();
|
|
}
|
|
|
|
setValue(vec, source);
|
|
|
|
return true;
|
|
}
|
|
|
|
// setting_color
|
|
|
|
qvec3d setting_color::transformVec3Value(const qvec3d &val) const
|
|
{
|
|
return qv::normalize_color_format(val);
|
|
}
|
|
|
|
// setting_container
|
|
|
|
void setting_container::copyFrom(const setting_container &other)
|
|
{
|
|
for (auto setting : _settings) {
|
|
const std::string &pri_name = setting->primaryName();
|
|
const auto *other_setting = other.findSetting(pri_name);
|
|
|
|
if (other_setting) {
|
|
setting->copyFrom(*other_setting);
|
|
}
|
|
}
|
|
}
|
|
|
|
void setting_container::registerSetting(setting_base *setting)
|
|
{
|
|
for (const auto &name : setting->names()) {
|
|
Q_assert(_settingsmap.find(name) == _settingsmap.end());
|
|
_settingsmap.emplace(name, setting);
|
|
}
|
|
|
|
_settings.emplace(setting);
|
|
_groupedSettings[setting->getGroup()].insert(setting);
|
|
}
|
|
|
|
void setting_container::registerSettings(const std::initializer_list<setting_base *> &settings)
|
|
{
|
|
registerSettings(settings.begin(), settings.end());
|
|
}
|
|
|
|
setting_base *setting_container::findSetting(const std::string &name) const
|
|
{
|
|
// strip off leading underscores
|
|
if (name.find("_") == 0) {
|
|
return findSetting(name.substr(1, name.size() - 1));
|
|
}
|
|
|
|
if (auto it = _settingsmap.find(name); it != _settingsmap.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
setting_error setting_container::setSetting(const std::string &name, const std::string &value, source source)
|
|
{
|
|
setting_base *setting = findSetting(name);
|
|
|
|
if (setting == nullptr) {
|
|
if (source == source::COMMANDLINE) {
|
|
throw parse_exception(fmt::format("Unrecognized command-line option '{}'\n", name));
|
|
}
|
|
return setting_error::MISSING;
|
|
}
|
|
|
|
parser_t p{value, { }};
|
|
return setting->parse(name, p, source) ? setting_error::NONE : setting_error::INVALID;
|
|
}
|
|
|
|
void setting_container::setSettings(const entdict_t &epairs, source source)
|
|
{
|
|
for (const auto &epair : epairs) {
|
|
setSetting(epair.first, epair.second, source);
|
|
}
|
|
}
|
|
|
|
void setting_container::printHelp()
|
|
{
|
|
fmt::print("{}usage: {} [-help/-h/-?] [-options] {}\n\n", programDescription, programName, remainderName);
|
|
|
|
for (auto grouped : grouped()) {
|
|
if (grouped.first) {
|
|
fmt::print("{}:\n", grouped.first->name);
|
|
}
|
|
|
|
for (auto setting : grouped.second) {
|
|
size_t numPadding = max(static_cast<size_t>(0), 28 - (setting->primaryName().size() + 4));
|
|
fmt::print(" -{} {:{}} {}\n", setting->primaryName(), setting->format(), numPadding,
|
|
setting->getDescription());
|
|
|
|
for (int i = 1; i < setting->names().size(); i++) {
|
|
fmt::print(" \\{}\n", setting->names()[i]);
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
throw quit_after_help_exception();
|
|
}
|
|
|
|
void setting_container::printSummary()
|
|
{
|
|
logging::print("\n--- Options Summary ---\n");
|
|
for (auto setting : _settings) {
|
|
if (setting->isChanged()) {
|
|
logging::print(" \"{}\" was set to \"{}\" (from {})\n", setting->primaryName(), setting->stringValue(),
|
|
setting->sourceString());
|
|
}
|
|
}
|
|
logging::print("\n");
|
|
}
|
|
|
|
std::vector<std::string> setting_container::parse(parser_base_t &parser)
|
|
{
|
|
// the settings parser loop will continuously eat tokens as long as
|
|
// it begins with a -; once we have no more settings to consume, we
|
|
// break out of this loop and return the remainder.
|
|
while (true) {
|
|
// end of cmd line
|
|
if (!parser.parse_token(PARSE_PEEK)) {
|
|
break;
|
|
}
|
|
|
|
// end of options
|
|
if (parser.token[0] != '-') {
|
|
break;
|
|
}
|
|
|
|
// actually eat the token since we peeked above
|
|
parser.parse_token();
|
|
|
|
// remove leading hyphens. we support any number of them.
|
|
while (parser.token.front() == '-') {
|
|
parser.token.erase(parser.token.begin());
|
|
}
|
|
|
|
if (parser.token.empty()) {
|
|
throw parse_exception("stray \"-\" in command line; please check your parameters");
|
|
}
|
|
|
|
if (parser.token == "help" || parser.token == "h" || parser.token == "?") {
|
|
printHelp();
|
|
}
|
|
|
|
auto setting = findSetting(parser.token);
|
|
|
|
if (!setting) {
|
|
throw parse_exception(fmt::format("unknown option \"{}\"", parser.token));
|
|
}
|
|
|
|
// pass off to setting to parse; store
|
|
// name for error message below
|
|
std::string token = std::move(parser.token);
|
|
|
|
if (!setting->parse(token, parser, source::COMMANDLINE)) {
|
|
throw parse_exception(
|
|
fmt::format("invalid value for option \"{}\"; should be in format {}", token, setting->format()));
|
|
}
|
|
}
|
|
|
|
// return remainder
|
|
std::vector<std::string> remainder;
|
|
|
|
while (true) {
|
|
if (parser.at_end() || !parser.parse_token()) {
|
|
break;
|
|
}
|
|
|
|
remainder.emplace_back(std::move(parser.token));
|
|
}
|
|
|
|
return remainder;
|
|
}
|
|
|
|
// global settings
|
|
common_settings::common_settings() :
|
|
threads{
|
|
this, "threads", 0, &performance_group, "number of threads to use, maximum; leave 0 for automatic"},
|
|
lowpriority{this, "lowpriority", true, &performance_group,
|
|
"run in a lower priority, to free up headroom for other processes"},
|
|
log{this, "log", true, &logging_group, "whether log files are written or not"},
|
|
verbose{this, {"verbose", "v"}, false, &logging_group, "verbose output"},
|
|
nopercent{this, "nopercent", false, &logging_group, "don't output percentage messages"},
|
|
nostat{this, "nostat", false, &logging_group, "don't output statistic messages"},
|
|
noprogress{this, "noprogress", false, &logging_group, "don't output progress messages"},
|
|
nocolor{this, "nocolor", false, &logging_group, "don't output color codes (for TB, etc)"},
|
|
quiet{this, {"quiet", "noverbose"}, {&nopercent, &nostat, &noprogress}, &logging_group,
|
|
"suppress non-important messages (equivalent to -nopercent -nostat -noprogress)"},
|
|
gamedir{this, "gamedir", "", &game_group,
|
|
"override the default mod base directory. if this is not set, or if it is relative, it will be derived from the input file or the basedir if specified."},
|
|
basedir{this, "basedir", "", &game_group,
|
|
"override the default game base directory. if this is not set, or if it is relative, it will be derived from the input file or the gamedir if specified."},
|
|
filepriority{this, "filepriority", search_priority_t::LOOSE,
|
|
{{"loose", search_priority_t::LOOSE}, {"archive", search_priority_t::ARCHIVE}}, &game_group,
|
|
"which types of archives (folders/loose files or packed archives) are higher priority and chosen first for path searching"},
|
|
paths{this, "path", "\"/path/to/folder\" <multiple allowed>", &game_group,
|
|
"additional paths or archives to add to the search path, mostly for loose files"},
|
|
q2rtx{this, "q2rtx", false, &game_group, "adjust settings to best support Q2RTX"},
|
|
defaultpaths{this, "defaultpaths", true, &game_group,
|
|
"whether the compiler should attempt to automatically derive game/base paths for games that support it"}
|
|
{}
|
|
|
|
void common_settings::setParameters(int argc, const char **argv)
|
|
{
|
|
programName = fs::path(argv[0]).stem().string();
|
|
fmt::print("---- {} / ericw-tools {} ----\n", programName, ERICWTOOLS_VERSION);
|
|
}
|
|
|
|
void common_settings::preinitialize(int argc, const char **argv) { setParameters(argc, argv); }
|
|
|
|
void common_settings::initialize(int argc, const char **argv)
|
|
{
|
|
token_parser_t p(argc, argv, { "command line" });
|
|
parse(p);
|
|
}
|
|
|
|
void common_settings::postinitialize(int argc, const char **argv)
|
|
{
|
|
printSummary();
|
|
|
|
configureTBB(threads.value(), lowpriority.value());
|
|
|
|
if (verbose.value()) {
|
|
logging::mask |= logging::flag::VERBOSE;
|
|
}
|
|
|
|
if (nopercent.value()) {
|
|
logging::mask &= ~(bitflags<logging::flag>(logging::flag::PERCENT) | logging::flag::CLOCK_ELAPSED);
|
|
}
|
|
|
|
if (nostat.value()) {
|
|
logging::mask &= ~bitflags<logging::flag>(logging::flag::STAT);
|
|
}
|
|
|
|
if (noprogress.value()) {
|
|
logging::mask &= ~bitflags<logging::flag>(logging::flag::PROGRESS);
|
|
}
|
|
|
|
if (nocolor.value()) {
|
|
logging::enable_color_codes = false;
|
|
}
|
|
}
|
|
|
|
void common_settings::run(int argc, const char **argv)
|
|
{
|
|
preinitialize(argc, argv);
|
|
initialize(argc, argv);
|
|
postinitialize(argc, argv);
|
|
}
|
|
} // namespace settings
|