RyanHub – file viewer
filename: src/Server.cpp
branch: feature/world
back to repo
/*
 *
 *               _____  _                 _
 *              /  ___|| |               | |
 *              \ `--. | |_  _ __   __ _ | |_   ___   ___
 *               `--. \| __|| '__| / _` || __| / _ \ / __|
 *              /\__/ /| |_ | |   | (_| || |_ | (_) |\__ \
 *              \____/  \__||_|    \__,_| \__| \___/ |___/
 *
 *
 * 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