Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 18 additions & 1 deletion Server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.20)

include(${CMAKE_BINARY_DIR}/conan_toolchain.cmake)

project(r-type_server
VERSION 0.0.1
DESCRIPTION "R-Type Server"
Expand All @@ -12,12 +14,16 @@ if(PROJECT_IS_TOP_LEVEL)
message(WARNING "Building Server standalone, adding Shuvlog and rtnt manually")
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../lib/shuvlog shuvlog)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../lib/cli_parser cli_parser)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../lib/rtnt rtnt)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../lib/rteng rteng)
endif()

# --- Sources / Headers ---
add_executable(${PROJECT_NAME}
src/main.cpp
src/lobby/lobby.cpp
src/lobby/lobby_manager.cpp
src/app.cpp
)

target_include_directories(${PROJECT_NAME} PRIVATE
Expand All @@ -26,8 +32,12 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../common
)

find_package(asio REQUIRED)

# --- Libraries ---
target_link_libraries(${PROJECT_NAME} PRIVATE
asio::asio
rtnt
rteng
cli_parser
shuvlog
Expand All @@ -52,7 +62,14 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)

if (MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /permissive-)
target_compile_definitions(${PROJECT_NAME}
PUBLIC
WIN32_LEAN_AND_MEAN
NOMINMAX
NOGDI
NOUSER
)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
else()
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall -Wextra -Werror -pedantic
Expand Down
55 changes: 55 additions & 0 deletions Server/src/app.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "app.hpp"

#include "logger/Logger.h"
#include "logger/Thread.h"
#include "utils.hpp"

namespace server {

App::App(const unsigned short port)
: _server(_context,
port)
{
registerCallbacks();
_server.onConnect(
[](std::shared_ptr<rtnt::core::Session>) { LOG_INFO("Accepting new connection"); });
_server.onDisconnect(
[](std::shared_ptr<rtnt::core::Session> s) { LOG_INFO("Disconnected {}", s->getId()); });
_server.onMessage([](std::shared_ptr<rtnt::core::Session> s, rtnt::core::Packet& p) {
LOG_INFO("Client {} sent a packet of type {}", s->getId(), p.getId());
});
_lobbyManager.createLobby();
}

App::~App()
{
LOG_INFO("Shutting down server.");
_lobbyManager.stopAll();
_context.stop();
if (_ioThread.joinable()) {
_ioThread.join();
}
}

void App::start()
{
_server.start();
_ioThread = std::thread([this]() {
logger::setThreadLabel("IoThread");
_context.run();
});
_ioThread.detach();
utils::LoopTimer loopTimer(TPS);

while (true) {
_server.update();
loopTimer.waitForNextTick();
}
}

void App::registerCallbacks()
{
// Empty for now but register the packet callbacks here;
}

} // namespace server
40 changes: 40 additions & 0 deletions Server/src/app.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <thread>

#include "lobby/lobby_manager.hpp"
#include "rtnt/core/server.hpp"

#define TPS 20

namespace server {

/**
* @class App
* @brief A server application containing a lobby manager.
*/
class App
{
public:
/**
* @brief Creates a server listening to specified port.
* @param port The port to listen to.
*/
explicit App(unsigned short port);
~App();

/**
* @brief Starts the server and updates it periodically.
*/
[[noreturn]] void start();

private:
asio::io_context _context;
rtnt::core::Server _server;
std::thread _ioThread;
lobby::Manager _lobbyManager;

void registerCallbacks();
};

} // namespace server
85 changes: 85 additions & 0 deletions Server/src/lobby/lobby.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include "lobby.hpp"

#include "components/all.hpp"
#include "components/position.hpp"
#include "components/type.hpp"
#include "enums/entity_types.hpp"
#include "logger/Thread.h"

Lobby::Lobby(const lobby::Id id)
: _roomId(id),
_engine(components::GameComponents{}),
_isRunning(false)
{
LOG_INFO("Creating new lobby.");
}

lobby::Id Lobby::getRoomId() const { return _roomId; }

void Lobby::pushTask(lobby::Callback action) { _actionQueue.push(std::move(action)); }

bool Lobby::hasJoined(const rtnt::core::session::Id sessionId) const
{
return _players.contains(sessionId);
}

bool Lobby::join(const rtnt::core::session::Id sessionId)
{
if (_players.contains(sessionId)) {
LOG_WARN("Player already joined this lobby.");
return false;
}
_players.try_emplace(sessionId, 0);
if (_players.contains(sessionId)) {
_actionQueue.push(
[](rteng::GameEngine&
engine) { // Create a new player entity on join (maybe do it otherwise)
engine.registerEntity<components::Position, components::Type>(
nullptr, {10, 10}, {entity::Type::kPlayer});
});
return true;
}
LOG_WARN("Player couldn't join this lobby");
return false;
}

void Lobby::leave(const rtnt::core::session::Id sessionId)
{
if (_players.contains(sessionId)) {
_players.erase(sessionId);
} else {
LOG_WARN("Player was not in this lobby");
}
}

void Lobby::stop()
{
if (!_isRunning) {
return;
}
LOG_INFO("Stopping lobby {}.", _roomId);
_isRunning = false;
if (_thread.joinable()) {
_thread.join();
}
}

void Lobby::start()
{
_isRunning = true;
_thread = std::thread(&Lobby::run, this);
_thread.detach();
}

void Lobby::run()
{
logger::setThreadLabel(("Lobby " + std::to_string(_roomId)).c_str());
lobby::Callback callbackFunction;
while (_isRunning) {
while (_actionQueue.pop(callbackFunction)) {
callbackFunction(_engine);
}
_engine.runOnce(0.16);
}
}

79 changes: 79 additions & 0 deletions Server/src/lobby/lobby.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#pragma once
#include <unordered_map>

#include "concurrent_queue.hpp"
#include "rteng.hpp"
#include "rtnt/core/session.hpp"

namespace lobby {

using Id = uint32_t;
using Callback = std::function<void(rteng::GameEngine&)>;

} // namespace lobby

/**
* @class Lobby
* @brief Encapsulates a gameEngine instance and it's networking interface
*/
class Lobby
{
public:
/**
* @brief Creates a lobby with the specified @code id@endcode.
* @param id an uint32(lobby::Id) that represents the id of the lobby.
*
* Note that the uniqueness of the ID depends on the user providing a distinct value.
*/
explicit Lobby(lobby::Id id);

/**
* @brief Tries to join this lobby.
* @param sessionId The id of the session trying to join.
* @return A boolean representing the status of the request.
*/
bool join(rtnt::core::session::Id sessionId);

/**
* @return The id of this lobby.
*/
lobby::Id getRoomId() const;

/**
* @brief Removes the @code SessionId@endcode from this lobby.
* @param sessionId The id of the session to remove.
*/
void leave(rtnt::core::session::Id sessionId);
/**
* @param sessionId The researched id.
* @return Whether this lobby contains this @code sessionId@endcode.
*/
bool hasJoined(rtnt::core::session::Id sessionId) const;

/**
* @brief Pushes a task to be made inside the running thread.
* This function is thread-safe.
* @param action A function performing the required action.
*/
void pushTask(lobby::Callback action);

/**
* @brief Start this lobby.
*/
void start();

/**
* @brief Stop this lobby.
*/
void stop();

private:
lobby::Id _roomId;
utils::ConcurrentQueue<lobby::Callback> _actionQueue;
rteng::GameEngine _engine;
std::unordered_map<rtnt::core::session::Id, rtecs::EntityID> _players;
std::atomic<bool> _isRunning;
std::thread _thread;

void run();
};
53 changes: 53 additions & 0 deletions Server/src/lobby/lobby_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "lobby_manager.hpp"

#include <ranges>

namespace lobby {

Id Manager::createLobby()
{
static Id nbLobbies = 0;
_lobbies.emplace(nbLobbies, std::make_unique<Lobby>(nbLobbies));
_lobbies.at(nbLobbies)->start();
return nbLobbies++;
}

Manager::~Manager() { stopAll(); }

void Manager::stopAll() const
{
for (const auto& lobby : _lobbies | std::views::values) {
lobby->stop();
}
}

void Manager::pushActionToLobby(rtnt::core::session::Id sessionId,
Callback action)
{
const auto it = _playerLookup.find(sessionId);
if (it != _playerLookup.end()) {
Lobby* lobby = it->second;
lobby->pushTask(std::move(action));
} else {
LOG_WARN("Session {} is not in any lobby.", sessionId);
}
}

bool Manager::joinRoom(const rtnt::core::session::Id sessionId,
const lobby::Id roomId) const
{
if (_lobbies.contains(roomId)) {
return _lobbies.at(roomId)->join(sessionId);
}
return false;
}

void Manager::leaveRoom(rtnt::core::session::Id sessionId)
{
const auto it = _playerLookup.find(sessionId);
if (it != _playerLookup.end()) {
it->second->leave(sessionId);
}
}

} // namespace lobby
Loading
Loading