/* * * _____ _ _ * / ___|| | | | * \ `--. | |_ _ __ __ _ | |_ ___ ___ * `--. \| __|| '__| / _` || __| / _ \ / __| * /\__/ /| |_ | | | (_| || |_ | (_) |\__ \ * \____/ \__||_| \__,_| \__| \___/ |___/ * * * 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 3 of the License, or * (at your option) any later version. * * Copyright (C) 2025 Armen Deroian * */ #include "Server.h" #include "network/Network.h" #include "utils/config/Config.h" #include "utils/MathUtils.h" #include "utils/TimeUtils.h" #define DEFAULT_SERVER_PROPERTIES R"(# Basic server settings server-name= motd=A Minecraft Server max-players=20 online-mode=true pvp=true difficulty=1 gamemode=0 hardcore=false allow-nether=true enable-command-block=false spawn-npcs=true spawn-animals=true spawn-monsters=true generate-structures=true # Networking server-port=25565 server-ip= enable-query=false enable-rcon=false # World settings level-name=world level-seed= level-type=default view-distance=10 simulation-distance=10 max-build-height=256 # Performance tuning max-tick-time=60000 tick-distance=10 # Logging debug=false)" namespace stratos { std::unique_ptr<Server> server = nullptr; Server::Server(const std::shared_ptr<spdlog::logger>& logger, Path path) : logger(logger), root(std::move(path)) { averageTPS.fill(20.0f); averageUse.fill(1.0f); try { serverConfig = std::make_unique<PropertiesConfig>(root / "server.properties", DEFAULT_SERVER_PROPERTIES); logger->info("Loaded server.properties"); } catch (const std::exception& e) { logger->error("Failed to load server configuration: {}", e.what()); throw; } try { const std::optional<PropertiesConfig::Property> ip = serverConfig->getProperty("server-ip"); address = ip && !ip->value.empty() ? std::move(ip->asString()) : "0.0.0.0"; port = serverConfig->getProperty("server-port").value().get().asInt(); name = serverConfig->getProperty("server-name").value().get().asString(); motd = serverConfig->getProperty("motd").value().get().asString(); onlineMode = serverConfig->getProperty("online-mode").value().get().asBool(); maxPlayers = serverConfig->getProperty("max-players").value().get().asInt(); } catch (const std::bad_optional_access& e) { throw std::runtime_error("Missing required properties in 'server.properties'"); } try { logger->info("Starting network..."); network = std::make_unique<NetworkManager>(this, logger, address, port); } catch (const std::exception& e) { logger->error("Failed to create NetworkManager: {}", e.what()); throw; } } Server::~Server() {} std::shared_ptr<spdlog::logger> Server::getLogger() const { return logger; } bool Server::isRunning() const { return running.load(); } unsigned int Server::getTick() const { return tickCounter; } float Server::getCurrentTPS() const { return currentTPS; } float Server::getCurrentLoad() const { return currentUse; } float Server::getAverageTPS() const { float total = 0.0f; for (const float tps : averageTPS) { total += tps; } return utils::round(total / averageTPS.size(), 2); } float Server::getAverageLoad() const { float total = 0.0f; for (const float use : averageUse) { total += use; } return utils::round((total / averageUse.size()) * 100.0f, 2); } const Path& Server::getServerDirectory() const { return root; } const std::unique_ptr<PropertiesConfig>& Server::getServerProperties() const { return serverConfig; } std::string Server::getAddress() const { return address; } int Server::getPort() const { return port; } std::string Server::getName() const { return name; } std::string Server::getMotd() const { return motd; } bool Server::isOnlineMode() const { return onlineMode; } int Server::getMaxPlayers() const { return maxPlayers; } int Server::getOnlinePlayers() const { return onlinePlayers; } void Server::start() { logger->info("Starting server..."); running = true; startTime = utils::currentTimeMillis(); try { network->start(); } catch (std::exception& e) { logger->error("Error starting server: {}", e.what()); // TODO: Crash? Force Shutdown? return; } tickProcessor(); } void Server::shutdown() { logger->info("Shutting down server..."); running = false; try { network->stop(); } catch (std::exception& e) { logger->error("Error shutting down server: {}", e.what()); } } void Server::tickProcessor() { do { // Tick the server, calculate the time taken, long tickTime = utils::currentTimeMillis(); if (const long tickDur = tick(tickTime); tickDur < TARGET_MILLIS_PER_TICK) { // Sleep for the remaining time to maintain the target tick rate if (long sleepTime = TARGET_MILLIS_PER_TICK - tickDur; sleepTime > 0) { assert(sleepTime <= 50); std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); } } } while (running); } long Server::tick(const long& tickTime) { ++tickCounter; // Tick server network->tick(); const long now = utils::currentTimeMillis(); const long totalTickTime = now - tickTime; currentTPS = std::min(TARGET_TICKS_PER_SECOND, 1000.0f / std::max(1L, totalTickTime)); currentUse = std::min(1.0f, totalTickTime / TARGET_MILLIS_PER_TICK); const int index = tickCounter % static_cast<int>(TARGET_TICKS_PER_SECOND); averageTPS[index] = currentTPS; averageUse[index] = currentUse; return now - tickTime; } } // namespace stratos