diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 18:05:18 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 18:05:18 +1100 |
| commit | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (patch) | |
| tree | 222dfcd07a1e40716127a347bbfd7119ce3d0984 /src/main.cc | |
initial commit
Diffstat (limited to 'src/main.cc')
| -rw-r--r-- | src/main.cc | 193 |
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); +} |
