aboutsummaryrefslogtreecommitdiff
path: root/src/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cc')
-rw-r--r--src/main.cc193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..56efb55
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,193 @@
+#include "main.hh"
+
+static void set_signal(const decltype(SIGINT) signal,
+ void (*const callback)(int)) {
+ struct sigaction sa {};
+ sa.sa_handler = callback;
+ sa.sa_flags = 0;
+ if (sigaction(signal, &sa, nullptr) == -1) {
+ throw std::runtime_error("failed to set signal handler for signal " +
+ std::to_string(signal));
+ }
+}
+
+// Parse arg, store result in dest after casting with boost::lexical_cast.
+template <typename T>
+static bool maybe_parse_arg(std::vector<std::string_view>& args,
+ const std::initializer_list<std::string_view>& keys,
+ T& dest) noexcept {
+ const auto find_it = std::ranges::find_if(args, [&keys](const auto& arg) {
+ return std::ranges::find(keys, arg) != std::end(keys);
+ });
+
+ if (find_it == std::end(args)) {
+ return false;
+ }
+
+ try {
+ const auto i = static_cast<unsigned int>(find_it - std::begin(args));
+ dest = boost::lexical_cast<T>(args.at(i + 1u));
+ } catch (const boost::bad_lexical_cast& e) {
+ shared::print::fault("bad type conversion for \"" +
+ std::string{*find_it} + "\" arg\n");
+ std::exit(EXIT_FAILURE);
+ } catch (const std::out_of_range& e) {
+ shared::print::fault("missing \"" + std::string{*find_it} + "\" arg\n");
+ std::exit(EXIT_FAILURE);
+ }
+
+ args.erase(find_it, std::next(find_it, 2));
+
+ return true;
+}
+
+// Parse arg, return true or false if arg exists.
+static bool
+maybe_parse_arg(std::vector<std::string_view>& args,
+ const std::initializer_list<std::string_view>& keys) noexcept {
+
+ const auto find_it = std::ranges::find_if(args, [&keys](const auto& arg) {
+ return std::ranges::find(keys, arg) != std::end(keys);
+ });
+
+ if (find_it == std::end(args)) {
+ return false;
+ }
+
+ args.erase(find_it);
+ return true;
+}
+
+static void do_server(const std::string_view address,
+ const std::string_view port) noexcept {
+ try {
+ server::main(address, port);
+ } catch (const std::exception& e) {
+ shared::print::fault(std::string{"exception in server!\n"} +
+ " what: " + e.what() + '\n');
+ std::exit(EXIT_FAILURE);
+ }
+}
+
+static void do_client(const std::string_view address,
+ const std::string_view port) noexcept {
+ try {
+ client::main(address, port);
+ } catch (const std::exception& e) {
+ shared::print::fault(std::string{"exception in client!\n"} +
+ " what: " + e.what() + '\n');
+ std::exit(EXIT_FAILURE);
+ }
+}
+
+static void init() noexcept {
+ // Because we use noexcept liberally, exceptions are often not unwound.
+ std::set_terminate([]() {
+ try {
+ throw;
+ } catch (const std::exception& e) {
+ shared::print::fault(std::string{"unhandled exception!\n"} +
+ " what: " + e.what() + '\n');
+
+ constexpr std::size_t BACKTRACE_BUF_SIZE = 64;
+ std::array<void*, BACKTRACE_BUF_SIZE> bt_buffer{};
+ const int nptrs =
+ backtrace(std::data(bt_buffer), std::size(bt_buffer));
+ char** strings = backtrace_symbols(std::data(bt_buffer), nptrs);
+
+ shared::print::message("\n backtrace:\n", false);
+ for (int i = 0; i < nptrs; ++i) {
+ shared::print::message(" " + std::string{strings[i]} + '\n',
+ false);
+ }
+ free(strings); // controversial
+
+ } catch (...) {
+ }
+ std::exit(EXIT_FAILURE);
+ });
+
+ set_signal(SIGPIPE, SIG_IGN);
+ set_signal(SIGINT, []([[maybe_unused]] const int signum) {
+ shared::print::warn(" interrupt signal received\n", false);
+ shared::should_exit = true; // graceful cleanup
+ });
+
+#ifndef NDEBUG
+ shared::print::debug(
+ "This binary is a debug build and should not be used in production.\n",
+ false);
+#endif
+}
+
+int main(const int argc, const char* const argv[]) {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ // Default argument values, which starts a multiplayer server with a client.
+ std::string address = "0.0.0.0";
+ std::string port = "8191";
+ bool has_address = false;
+ bool has_headless = false;
+ {
+ std::vector<std::string_view> args;
+ for (auto i = 1; i < argc; ++i) {
+ args.emplace_back(argv[i]);
+ }
+
+ if (maybe_parse_arg(args, {"--help", "-h"})) {
+ // clang-format off
+ std::cout << " --help -h : display this, quit\n"
+ " --address -a <string> : connect elsewhere\n"
+ " --port -p <int> : override the default port\n"
+ " --headless -H : run without a client\n"
+ " --singleplayer -s : avoid hosting a lan server\n"
+ " --seed -S <int> : manually set the worldseed\n"
+ " --directory -d <string> : manually set the directory\n"
+ " --tickrate -t <int> : override the default tickrate\n"
+ " --distance -D <int> : set the max chunk distance the server provides\n";
+ // clang-format on
+ std::exit(EXIT_SUCCESS);
+ }
+
+ has_address = maybe_parse_arg(args, {"--address", "-a"}, address);
+ has_headless = maybe_parse_arg(args, {"--headless", "-H"});
+
+ maybe_parse_arg(args, {"--port", "-p"}, port);
+ maybe_parse_arg(args, {"--seed", "-S"}, server::state.seed);
+ maybe_parse_arg(args, {"--directory", "-d"}, server::state.directory);
+ maybe_parse_arg(args, {"--tickrate", "-t"}, server::state.tickrate);
+ maybe_parse_arg(args, {"--distance", "-D"},
+ server::state.draw_distance);
+
+ if (maybe_parse_arg(args, {"--singleplayer", "-s"})) {
+ if (has_address) {
+ shared::print::warn("singleplayer argument ignored due "
+ "to conflicting address argument\n");
+ }
+ address = "localhost";
+ }
+
+ std::ranges::for_each(args, [](const auto& arg) {
+ shared::print::warn("unused argument \"" + std::string{arg} +
+ "\"\n");
+ });
+ }
+
+ init();
+
+ // Split server/client execution, while waiting for server to boot before
+ // running the client.
+ std::jthread server_thread;
+ if (!has_address) {
+ if (has_headless) {
+ do_server(address, port);
+ std::exit(EXIT_SUCCESS);
+ }
+ server_thread = std::jthread{do_server, address, port};
+ while (!server::has_initialised) {
+ continue;
+ }
+ }
+
+ do_client(address, port);
+}