diff --git a/CMakeLists.txt b/CMakeLists.txt index f574900..f5a2e70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if (UNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -g") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin") - if (SANITIZE) + if (SANITIZE) message(STATUS "sanitize is ON") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread") endif (SANITIZE) @@ -43,7 +43,8 @@ add_executable(BeamMP-Server include/TResourceManager.h src/TResourceManager.cpp include/THeartbeatThread.h src/THeartbeatThread.cpp include/Http.h src/Http.cpp - include/SocketIO.h src/SocketIO.cpp) + include/SocketIO.h src/SocketIO.cpp + include/TPPSMonitor.h src/TPPSMonitor.cpp) target_include_directories(BeamMP-Server PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/commandline") diff --git a/include/Common.h b/include/Common.h index f9c65f5..969ece2 100644 --- a/include/Common.h +++ b/include/Common.h @@ -24,11 +24,11 @@ public: std::string Resource; std::string MapName; std::string Key; - int MaxPlayers; - bool Private; - int MaxCars; + int MaxPlayers {}; + bool Private {}; + int MaxCars {}; bool DebugModeEnabled; - int Port; + int Port {}; std::string CustomIP; [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } @@ -46,22 +46,33 @@ public: static void GracefullyShutdown(); static TConsole& Console() { return *mConsole; } static std::string ServerVersion() { return "v1.20"; } + static std::string ClientVersion() { return "v1.80"; } + static std::string PPS() { return mPPS; } + static void SetPPS(std::string NewPPS) { mPPS = NewPPS; } static inline TSettings Settings {}; private: + static inline std::string mPPS; static std::unique_ptr mConsole; static inline std::mutex mShutdownHandlersMutex {}; static inline std::vector mShutdownHandlers {}; }; -#define warn(x) Application::Console().Write(std::string("[WARN] ") + (x)) -#define error(x) Application::Console().Write(std::string("[ERROR] ") + (x)) -#define info(x) Application::Console().Write(std::string("[INFO] ") + (x)) -#define luaprint(x) Application::Console().Write(std::string("[LUA] ") + (x)) -#define debug(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(std::string("[DEBUG] ") + (x)); \ - } \ - } while (false) +static inline void warn(const std::string& str) { + Application::Console().Write(std::string("[WARN] ") + str); +} +static inline void error(const std::string& str) { + Application::Console().Write(std::string("[ERROR] ") + str); +} +static inline void info(const std::string& str) { + Application::Console().Write(std::string("[INFO] ") + str); +} +static inline void debug(const std::string& str) { + if (Application::Settings.DebugModeEnabled) { + Application::Console().Write(std::string("[DEBUG] ") + str); + } +} +static inline void luaprint(const std::string& str) { + Application::Console().Write(std::string("[LUA] ") + str); +} diff --git a/include/IThreaded.h b/include/IThreaded.h index 27288eb..19ba8fe 100644 --- a/include/IThreaded.h +++ b/include/IThreaded.h @@ -7,10 +7,10 @@ class IThreaded { public: IThreaded() // invokes operator() on this object - : _Thread(std::thread([this] { (*this)(); })) { } + : mThread(std::thread([this] { (*this)(); })) { } virtual void operator()() = 0; protected: - std::thread _Thread; + std::thread mThread; }; diff --git a/include/SocketIO.h b/include/SocketIO.h index a73a796..3dec06f 100644 --- a/include/SocketIO.h +++ b/include/SocketIO.h @@ -5,6 +5,7 @@ #include #include #include +#include /* * We send relevant server events over socket.io to the backend. @@ -44,10 +45,10 @@ public: ~SocketIO(); - void SetAuthenticated(bool auth) { _Authenticated = auth; } + void SetAuthenticated(bool auth) { mAuthenticated = auth; } private: - SocketIO(); + SocketIO() noexcept; void ThreadMain(); @@ -57,12 +58,12 @@ private: std::string Data; }; - bool _Authenticated { false }; - sio::client _Client; - std::thread _Thread; - std::atomic_bool _CloseThread { false }; - std::mutex _QueueMutex; - std::deque _Queue; + bool mAuthenticated { false }; + sio::client mClient; + std::thread mThread; + std::atomic_bool mCloseThread { false }; + std::mutex mQueueMutex; + std::deque mQueue; friend std::unique_ptr std::make_unique(); }; diff --git a/include/TConfig.h b/include/TConfig.h index df86764..2e78bf3 100644 --- a/include/TConfig.h +++ b/include/TConfig.h @@ -7,6 +7,6 @@ public: TConfig(const std::string& ConfigFile); private: - std::string RemoveComments(const std::string& Line); - void SetValues(const std::string& Line, int Index); + static std::string RemoveComments(const std::string& Line); + static void SetValues(const std::string& Line, int Index); }; diff --git a/include/THeartbeatThread.h b/include/THeartbeatThread.h index 7267cb5..9b1b7d8 100644 --- a/include/THeartbeatThread.h +++ b/include/THeartbeatThread.h @@ -1,10 +1,21 @@ #pragma once -#include "IThreaded.h" #include "Common.h" +#include "IThreaded.h" +#include "TResourceManager.h" +#include "TServer.h" class THeartbeatThread : public IThreaded { public: - THeartbeatThread(); + THeartbeatThread(TResourceManager& ResourceManager, TServer& Server); + ~THeartbeatThread(); void operator()() override; + +private: + std::string GenerateCall(); + std::string GetPlayers(); + + bool mShutdown = false; + TResourceManager& mResourceManager; + TServer& mServer; }; \ No newline at end of file diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index b1b1e80..da7b8d2 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -3,13 +3,12 @@ #include "Common.h" #include "IThreaded.h" +#include "TLuaFile.h" #include "TServer.h" #include #include #include -class TLuaFile; - class TLuaEngine : public IThreaded { public: explicit TLuaEngine(TServer& Server); diff --git a/include/TLuaFile.h b/include/TLuaFile.h index d5c383a..f7c09c8 100644 --- a/include/TLuaFile.h +++ b/include/TLuaFile.h @@ -1,7 +1,6 @@ #ifndef TLUAFILE_H #define TLUAFILE_H -#include "TLuaEngine.h" #include #include #include @@ -17,6 +16,8 @@ struct TLuaArg { void PushArgs(lua_State* State); }; +class TLuaEngine; + class TLuaFile { public: void Init(); diff --git a/include/TPPSMonitor.h b/include/TPPSMonitor.h new file mode 100644 index 0000000..6f5e8f2 --- /dev/null +++ b/include/TPPSMonitor.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Common.h" +#include "IThreaded.h" +#include "TServer.h" + +class TPPSMonitor : public IThreaded { +public: + TPPSMonitor(TServer& Server); + + void operator()() override; + +private: + + TServer& mServer; + bool mShutdown { false }; + int mInternalPPS { 0 }; +}; \ No newline at end of file diff --git a/src/Http.cpp b/src/Http.cpp index aad38e5..890c6d1 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -1,6 +1,6 @@ #include "Http.h" -#include "CustomAssert.h" +#include "Common.h" #include #include #include diff --git a/src/SocketIO.cpp b/src/SocketIO.cpp index 9c5c4e3..14c6c6e 100644 --- a/src/SocketIO.cpp +++ b/src/SocketIO.cpp @@ -1,6 +1,7 @@ #include "SocketIO.h" -#include "Logger.h" -#include "Settings.h" +#include "Common.h" + +#include static std::unique_ptr SocketIOInstance = std::make_unique(); @@ -8,19 +9,18 @@ SocketIO& SocketIO::Get() { return *SocketIOInstance; } -SocketIO::SocketIO() - : _Thread([this] { ThreadMain(); }) { - _Client.socket("/")->on("Hello", [&](sio::event&) { - DebugPrintTIDInternal("Hello-handler"); +SocketIO::SocketIO() noexcept + : mThread([this] { ThreadMain(); }) { + mClient.socket("/")->on("Hello", [&](sio::event&) { info("Got 'Hello' from backend socket-io!"); }); - _Client.connect("https://backend.beammp.com"); - _Client.set_logs_quiet(); + mClient.connect("https://backend.beammp.com"); + mClient.set_logs_quiet(); } SocketIO::~SocketIO() { - _CloseThread.store(true); - _Thread.join(); + mCloseThread.store(true); + mThread.join(); } static constexpr auto RoomNameFromEnum(SocketIORoom Room) { @@ -60,29 +60,28 @@ static constexpr auto EventNameFromEnum(SocketIOEvent Event) { } void SocketIO::Emit(SocketIORoom Room, SocketIOEvent Event, const std::string& Data) { - if (!_Authenticated) { + if (!mAuthenticated) { debug("trying to emit a socket.io event when not yet authenticated"); return; } std::string RoomName = RoomNameFromEnum(Room); std::string EventName = EventNameFromEnum(Event); debug("emitting event \"" + EventName + "\" with data: \"" + Data + "\" in room \"/key/" + RoomName + "\""); - std::unique_lock Lock(_QueueMutex); - _Queue.push_back({ RoomName, EventName, Data }); - debug("queue now has " + std::to_string(_Queue.size()) + " events"); + std::unique_lock Lock(mQueueMutex); + mQueue.push_back({ RoomName, EventName, Data }); + debug("queue now has " + std::to_string(mQueue.size()) + " events"); } void SocketIO::ThreadMain() { bool FirstTime = true; - while (!_CloseThread.load()) { - if (_Authenticated && FirstTime) { + while (!mCloseThread.load()) { + if (mAuthenticated && FirstTime) { FirstTime = false; - DebugPrintTID(); } bool empty = false; { // queue lock scope - std::unique_lock Lock(_QueueMutex); - empty = _Queue.empty(); + std::unique_lock Lock(mQueueMutex); + empty = mQueue.empty(); } // end queue lock scope if (empty) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -90,20 +89,20 @@ void SocketIO::ThreadMain() { } else { Event TheEvent; { // queue lock scope - std::unique_lock Lock(_QueueMutex); - TheEvent = _Queue.front(); - _Queue.pop_front(); + std::unique_lock Lock(mQueueMutex); + TheEvent = mQueue.front(); + mQueue.pop_front(); } // end queue lock scope debug("sending \"" + TheEvent.Name + "\" event"); auto Room = "/" + TheEvent.Room; - _Client.socket("/")->emit(TheEvent.Name, TheEvent.Data); + mClient.socket("/")->emit(TheEvent.Name, TheEvent.Data); debug("sent \"" + TheEvent.Name + "\" event"); } } + // using std::cout as this happens during static destruction and the logger might be dead already std::cout << "closing " + std::string(__func__) << std::endl; - _Client.sync_close(); - _Client.clear_con_listeners(); + mClient.sync_close(); + mClient.clear_con_listeners(); std::cout << "closed" << std::endl; - } diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index cba3c10..a3d337b 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -1,10 +1,9 @@ #include "THeartbeatThread.h" +#include "Client.h" #include "Http.h" #include "SocketIO.h" - -THeartbeatThread::THeartbeatThread() { -} +#include void THeartbeatThread::operator()() { std::string Body; @@ -15,7 +14,7 @@ void THeartbeatThread::operator()() { static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); bool isAuth = false; - while (true) { + while (!mShutdown) { Body = GenerateCall(); // a hot-change occurs when a setting has changed, to update the backend of that change. auto Now = std::chrono::high_resolution_clock::now(); @@ -55,3 +54,43 @@ void THeartbeatThread::operator()() { SocketIO::Get().SetAuthenticated(isAuth); } } +std::string THeartbeatThread::GenerateCall() { + std::stringstream Ret; + + Ret << "uuid=" << Application::Settings.Key + << "&players=" << mServer.ClientCount() + << "&maxplayers=" << Application::Settings.MaxPlayers + << "&port=" << Application::Settings.Port + << "&map=" << Application::Settings.MapName + << "&private=" << (Application::Settings.Private ? "true" : "false") + << "&version=" << Application::ServerVersion() + << "&clientversion=" << Application::ClientVersion() + << "&name=" << Application::Settings.ServerName + << "&pps=" << Application::PPS() + << "&modlist=" << mResourceManager.FileList() + << "&modstotalsize=" << mResourceManager.MaxModSize() + << "&modstotal=" << mResourceManager.ModsLoaded() + << "&playerslist=" << GetPlayers() + << "&desc=" << Application::Settings.ServerDesc; + return Ret.str(); +} +THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server) + : mResourceManager(ResourceManager) + , mServer(Server) { + Application::RegisterShutdownHandler([&] { mShutdown = true; }); +} +std::string THeartbeatThread::GetPlayers() { + std::string Return; + mServer.ForEachClient([&](std::weak_ptr ClientPtr) -> bool { + if (!ClientPtr.expired()) { + Return += ClientPtr.lock()->GetName() + ";"; + } + return true; + }); + return Return; +} +THeartbeatThread::~THeartbeatThread() { + if (mThread.joinable()) { + mThread.join(); + } +} diff --git a/src/TLuaFile.cpp b/src/TLuaFile.cpp index d9e8e12..39328c4 100644 --- a/src/TLuaFile.cpp +++ b/src/TLuaFile.cpp @@ -2,6 +2,7 @@ #include "Client.h" #include "Common.h" #include "CustomAssert.h" +#include "TLuaEngine.h" #include "TServer.h" #include diff --git a/src/TPPSMonitor.cpp b/src/TPPSMonitor.cpp new file mode 100644 index 0000000..1eddd26 --- /dev/null +++ b/src/TPPSMonitor.cpp @@ -0,0 +1,35 @@ +#include "TPPSMonitor.h" +#include "Client.h" + +TPPSMonitor::TPPSMonitor(TServer& Server) + : mServer(Server) { + Application::SetPPS("-"); + Application::RegisterShutdownHandler([&] { mShutdown = true; }); +} +void TPPSMonitor::operator()() { + while (!mShutdown) { + int C = 0, V = 0; + if (mServer.ClientCount() == 0) { + Application::SetPPS("-"); + return; + } + mServer.ForEachClient([&](std::weak_ptr ClientPtr) -> bool { + if (!ClientPtr.expired()) { + auto c = ClientPtr.lock(); + if (c->GetCarCount() > 0) { + C++; + V += c->GetCarCount(); + } + } + return true; + }); + if (C == 0 || mInternalPPS == 0) { + Application::SetPPS("-"); + } else { + int R = (mInternalPPS / C) / V; + Application::SetPPS(std::to_string(R)); + } + mInternalPPS = 0; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} diff --git a/src/main.cpp b/src/main.cpp index 69c7c64..a4d45a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,9 +3,11 @@ #include "IThreaded.h" #include "TConfig.h" #include "TConsole.h" +#include "THeartbeatThread.h" #include "TLuaEngine.h" #include "TResourceManager.h" #include "TServer.h" +#include "TPPSMonitor.h" #include #include #include @@ -43,10 +45,11 @@ int main(int argc, char** argv) { #endif // __unix TServer Server(argc, argv); - TConfig Config("Server.cfg"); + [[maybe_unused]] TConfig Config("Server.cfg"); TLuaEngine LuaEngine(Server); TResourceManager ResourceManager; - THeartbeatThread Heartbeat; + [[maybe_unused]] TPPSMonitor PPSMonitor(Server); + THeartbeatThread Heartbeat(ResourceManager, Server); // TODO: replace bool Shutdown = false;