Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor config, add settings command #295

Merged
merged 44 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
55f5437
Add new type `Settings` & refactor TSettings behavior
jimkoen Feb 18, 2024
89034a6
Add logic for new Settings type
jimkoen Feb 22, 2024
86f0052
Add rudimentary access control to type `Settings`
jimkoen Feb 26, 2024
a357ff8
Add missing options to defaults
jimkoen Feb 26, 2024
3989961
Add `get` and `set` to console command `settings`
jimkoen Feb 26, 2024
8693b8a
Add `list` argument to command `settings`
jimkoen Feb 26, 2024
f5e2f74
Fix `ComposedKey` metadata not printing to console
jimkoen May 7, 2024
d6b78b9
Fix argument parsing at wrong index
jimkoen May 7, 2024
37109ae
Make 'General::Debug' setting r/w from runtime
jimkoen May 7, 2024
c6dac55
Wrap Settings into synchronization primitives
jimkoen May 7, 2024
158875a
Remove debug code for new Settings implementation
jimkoen May 7, 2024
639c46a
Remove additional debug calls
jimkoen May 7, 2024
ff81242
Fix `settings set` not accepting boolean literals
jimkoen May 7, 2024
13e641b
Remove interfering legacy code (http,password,etc)
jimkoen May 15, 2024
8c15b87
Refactor all references to settings to use new `Settings` type
jimkoen May 15, 2024
bcd4b5a
Fix Debug asserts on FreeBSD
jimkoen May 15, 2024
a5e3fc8
Add Sync.h
jimkoen May 15, 2024
3609fd7
Potential fix for compiler issues on Ubuntu
jimkoen May 15, 2024
f567645
Add issue with implicit argument type conversions for `Settings.set()`
jimkoen May 15, 2024
3a8f4de
fix errors in Settings::set
lionkor May 17, 2024
31ce0cc
add unit test + concept constraints for set(string)
lionkor May 17, 2024
4c9fbc2
Change concepts in Settings.set + add test for remaining set overloads
jimkoen May 17, 2024
67db935
Fix concepts related error (for compat with gcc9)
jimkoen May 21, 2024
84f5f95
Refactor: feedback from code review
jimkoen May 21, 2024
d30dccc
Separate settings tests
jimkoen May 21, 2024
04e8d00
fix invalid default initialization in SettingsMap
lionkor May 24, 2024
73ecef1
Move map declarations in Settings.h into .cpp
jimkoen Jun 26, 2024
509225f
Move tests from .h to .cpp
jimkoen Jun 26, 2024
0d3256c
Remove todo in accordance with review
jimkoen Jun 26, 2024
6c0a8d1
remove superflous code
jimkoen Jun 26, 2024
814927d
change log output for consistency
jimkoen Jun 26, 2024
25739cb
Merge branch 'minor' into 158-bug-running-settings-help-returns-nothing
jimkoen Jun 26, 2024
2451e08
update remaining sections of code after merge
jimkoen Jun 26, 2024
26ef398
fix AuthKey being writable from console
jimkoen Jun 26, 2024
7919f81
remove dead code for deprecated config format
jimkoen Jun 26, 2024
8c32d76
fix confusing error when setting wrong key
jimkoen Jun 26, 2024
0748267
remove superflous comments
jimkoen Jun 26, 2024
e7c7f45
fix chrono wrapper
jimkoen Jun 26, 2024
6731b3e
fix typo
jimkoen Jun 26, 2024
461fb5d
improve error messages
jimkoen Jun 26, 2024
5919fc6
improve acl error message consistency
jimkoen Jun 26, 2024
3c80bcb
remove line ChronoWrapper.cpp:13 as discussed in review
jimkoen Jun 26, 2024
29f4d0d
run clang-format
jimkoen Jun 26, 2024
08374b1
deprecate Ubuntu 20.04
jimkoen Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ set(PRJ_HEADERS
include/TServer.h
include/VehicleData.h
include/Env.h
include/Settings.h
include/Profiling.h
include/ChronoWrapper.h
)
Expand All @@ -74,6 +75,7 @@ set(PRJ_SOURCES
src/TServer.cpp
src/VehicleData.cpp
src/Env.cpp
src/Settings.cpp
src/Profiling.cpp
src/ChronoWrapper.cpp
)
Expand Down
39 changes: 7 additions & 32 deletions include/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
namespace fs = std::filesystem;

#include "TConsole.h"
#include "Settings.h"

struct Version {
uint8_t major;
Expand All @@ -59,34 +60,8 @@ using SparseArray = std::unordered_map<size_t, T>;
class Application final {
public:
// types
struct TSettings {
std::string ServerName { "BeamMP Server" };
std::string ServerDesc { "BeamMP Default Description" };
std::string ServerTags { "Freeroam" };
std::string Resource { "Resources" };
std::string MapName { "/levels/gridmap_v2/info.json" };
std::string Key {};
std::string Password {};
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { false };
int MaxPlayers { 8 };
bool Private { true };
int MaxCars { 1 };
bool DebugModeEnabled { false };
int Port { 30814 };
std::string CustomIP {};
bool LogChat { true };
bool AllowGuests { true };
bool SendErrors { true };
bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 };
std::string HTTPServerIP { "127.0.0.1" };
bool HTTPServerUseSSL { false };
bool HideUpdateMessages { false };
std::string UpdateReminderTime { "30s" };
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
};



using TShutdownHandler = std::function<void()>;

Expand All @@ -104,7 +79,7 @@ class Application final {
static std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }

static TSettings Settings;
static inline struct Settings Settings { };

static std::vector<std::string> GetBackendUrlsInOrder() {
return {
Expand Down Expand Up @@ -226,21 +201,21 @@ void RegisterThread(const std::string& str);
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// trace() is a debug-build debug()
#if defined(DEBUG)
#define beammp_trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
Expand Down
7 changes: 4 additions & 3 deletions include/CustomAssert.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <thread>

#include "Common.h"
#include "Compat.h"

static const char* const ANSI_RESET = "\u001b[0m";

Expand All @@ -56,7 +57,7 @@ static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
static const char* const ANSI_BOLD = "\u001b[1m";
static const char* const ANSI_UNDERLINE = "\u001b[4m";

#ifdef DEBUG
#if defined(DEBUG) && !defined(BEAMMP_FREEBSD)
#include <iostream>
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
Expand All @@ -81,8 +82,8 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \
} \
} while (false)
#define beammp_assert_not_reachable() \
do { \
#define beammp_assert_not_reachable() \
do { \
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
} while (false)
#endif // DEBUG
146 changes: 146 additions & 0 deletions include/Settings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#pragma once
#include "Sync.h"
#include <concepts>
#include <cstdint>
#include <doctest/doctest.h>
#include <fmt/core.h>
#include <fmt/format.h>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <variant>

struct ComposedKey {
std::string Category;
std::string Key;

bool operator==(const ComposedKey& rhs) const {
return (this->Category == rhs.Category && this->Key == rhs.Key);
}
};

template <>
struct fmt::formatter<ComposedKey> : formatter<std::string> {
auto format(ComposedKey key, format_context& ctx) const;
};

inline auto fmt::formatter<ComposedKey>::format(ComposedKey key, fmt::format_context& ctx) const {
std::string key_metadata = fmt::format("{}::{}", key.Category, key.Key);
return formatter<std::string>::format(key_metadata, ctx);
}

namespace std {
template <>
class hash<ComposedKey> {
public:
std::uint64_t operator()(const ComposedKey& key) const {
std::hash<std::string> hash_fn;
return hash_fn(key.Category + key.Key);
}
};
}

struct Settings {
using SettingsTypeVariant = std::variant<std::string, bool, int>;

Settings();

enum Key {
// Keys that correspond to the keys set in TOML
// Keys have their TOML section name as prefix

// [Misc]
Misc_SendErrorsShowMessage,
Misc_SendErrors,
Misc_ImScaredOfUpdates,
Misc_UpdateReminderTime,

// [General]
General_Description,
General_Tags,
General_MaxPlayers,
General_Name,
General_Map,
General_AuthKey,
General_Private,
General_Port,
General_MaxCars,
General_LogChat,
General_ResourceFolder,
General_Debug,
General_AllowGuests
};

Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
enum SettingsAccessMask {
READ_ONLY, // Value can be read from console
READ_WRITE, // Value can be read and written to from console
NO_ACCESS // Value is inaccessible from console (no read OR write)
};

using SettingsAccessControl = std::pair<
Key, // The Key's corresponding enum encoding
SettingsAccessMask // Console read/write permissions
>;

Sync<std::unordered_map<ComposedKey, SettingsAccessControl>> InputAccessMapping;
std::string getAsString(Key key);

int getAsInt(Key key);

bool getAsBool(Key key);

SettingsTypeVariant get(Key key);

void set(Key key, const std::string& value);

template <typename Integer, std::enable_if_t<std::is_same_v<Integer, int>, bool> = true>
void set(Key key, Integer value) {
auto map = SettingsMap.synchronize();
if (!map->contains(key)) {
throw std::logic_error { "Undefined setting key accessed in Settings::set(int)" };
}
if (!std::holds_alternative<int>(map->at(key))) {
throw std::logic_error { fmt::format("Wrong value type in Settings::set(int): index {}", map->at(key).index()) };
}
map->at(key) = value;
}
template <typename Boolean, std::enable_if_t<std::is_same_v<bool, Boolean>, bool> = true>
void set(Key key, Boolean value) {
auto map = SettingsMap.synchronize();
if (!map->contains(key)) {
throw std::logic_error { "Undefined setting key accessed in Settings::set(bool)" };
}
if (!std::holds_alternative<bool>(map->at(key))) {
throw std::logic_error { fmt::format("Wrong value type in Settings::set(bool): index {}", map->at(key).index()) };
}
map->at(key) = value;
}

const std::unordered_map<ComposedKey, SettingsAccessControl> getAccessControlMap() const;
SettingsAccessControl getConsoleInputAccessMapping(const ComposedKey& keyName);

void setConsoleInputAccessMapping(const ComposedKey& keyName, const std::string& value);
void setConsoleInputAccessMapping(const ComposedKey& keyName, int value);
void setConsoleInputAccessMapping(const ComposedKey& keyName, bool value);
};


9 changes: 9 additions & 0 deletions include/Sync.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <boost/thread/synchronized_value.hpp>
#include <mutex>

/// This header provides convenience aliases for synchronization primitives.

template <typename T>
using Sync = boost::synchronized_value<T, std::recursive_mutex>;
5 changes: 2 additions & 3 deletions include/TConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include "Common.h"
#include "Settings.h"

#include <atomic>
#include <filesystem>
Expand All @@ -40,9 +41,7 @@ class TConfig {
private:
void CreateConfigFile();
void ParseFromFile(std::string_view name);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, Settings::Key key);

void ParseOldFormat();
std::string TagsAsPrettyArray() const;
Expand Down
16 changes: 7 additions & 9 deletions src/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
#include "CustomAssert.h"
#include "Http.h"

Application::TSettings Application::Settings = {};

void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
std::unique_lock Lock(mShutdownHandlersMutex);
if (Handler) {
Expand Down Expand Up @@ -257,7 +255,7 @@ static std::mutex ThreadNameMapMutex {};

std::string ThreadName(bool DebugModeOverride) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
if (DebugModeOverride || Application::Settings.getAsBool(Settings::Key::General_Debug)) {
auto id = std::this_thread::get_id();
if (threadNameMap.find(id) != threadNameMap.end()) {
// found
Expand All @@ -269,21 +267,21 @@ std::string ThreadName(bool DebugModeOverride) {

TEST_CASE("ThreadName") {
RegisterThread("MyThread");
auto OrigDebug = Application::Settings.DebugModeEnabled;
auto OrigDebug = Application::Settings.getAsBool(Settings::Key::General_Debug);

// ThreadName adds a space at the end, legacy but we need it still
SUBCASE("Debug mode enabled") {
Application::Settings.DebugModeEnabled = true;
Application::Settings.set(Settings::Key::General_Debug, true);
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "MyThread ");
}
SUBCASE("Debug mode disabled") {
Application::Settings.DebugModeEnabled = false;
Application::Settings.set(Settings::Key::General_Debug, false);
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "");
}
// cleanup
Application::Settings.DebugModeEnabled = OrigDebug;
Application::Settings.set(Settings::Key::General_Debug, OrigDebug);
}

void RegisterThread(const std::string& str) {
Expand All @@ -297,7 +295,7 @@ void RegisterThread(const std::string& str) {
#elif defined(BEAMMP_FREEBSD)
ThreadId = std::to_string(getpid());
#endif
if (Application::Settings.DebugModeEnabled) {
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
std::ofstream ThreadFile(".Threads.log", std::ios::app);
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
}
Expand Down Expand Up @@ -330,7 +328,7 @@ TEST_CASE("Version::AsString") {
}

void LogChatMessage(const std::string& name, int id, const std::string& msg) {
if (Application::Settings.LogChat) {
if (Application::Settings.getAsBool(Settings::Key::General_LogChat)) {
std::stringstream ss;
ss << ThreadName();
ss << "[CHAT] ";
Expand Down
2 changes: 1 addition & 1 deletion src/Compat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ TEST_CASE("init and reset termios") {
CHECK_EQ(current.c_lflag, original.c_lflag);
#ifndef BEAMMP_FREEBSD // The 'c_line' attribute seems to only exist on Linux, so we need to omit it on other platforms
CHECK_EQ(current.c_line, original.c_line);
#endif
#endif
CHECK_EQ(current.c_oflag, original.c_oflag);
CHECK_EQ(current.c_ospeed, original.c_ospeed);
}
Expand Down
5 changes: 0 additions & 5 deletions src/Http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ Http::Server::THttpServerInstance::THttpServerInstance() {
}

void Http::Server::THttpServerInstance::operator()() try {
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort));
std::unique_ptr<httplib::Server> HttpLibServerInstance;
HttpLibServerInstance = std::make_unique<httplib::Server>();
// todo: make this IP agnostic so people can set their own IP
Expand Down Expand Up @@ -241,10 +240,6 @@ void Http::Server::THttpServerInstance::operator()() try {
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
});
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
if (!ret) {
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
}
} catch (const std::exception& e) {
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
}
Loading
Loading