Compare commits

..

18 Commits

Author SHA1 Message Date
Lion Kortlepel 5c8826a337 rename HttpLibServerInstance to Server 2022-05-24 21:13:55 +02:00
Lion Kortlepel 9fccc27741 http: add /player/{name}/vehicles 2022-05-24 21:05:10 +02:00
Lion Kortlepel b0021d42b5 add /player/{name}/ draft 2022-05-23 23:17:17 +02:00
Lion Kortlepel 5598d75fd5 fix lifetime of http server, add /version 2022-05-23 23:16:53 +02:00
Lion Kortlepel 48bb1373e1 fix toml11 include path 2022-05-23 19:03:51 +02:00
Lion Kortlepel 8fce2fc67f http: add /players 2022-05-23 19:01:03 +02:00
Lion Kortlepel f7acd1e819 http: add /config 2022-05-23 18:44:42 +02:00
Lion Kortlepel b46dc664c6 http: add /ready 2022-05-23 18:30:47 +02:00
Lion Kortlepel 18b4c698fc add openapi specification for http server 2022-05-23 18:02:07 +02:00
Lion Kortlepel 62c300f285 http: add error, exception handlers 2022-05-23 17:41:44 +02:00
Lion Kortlepel d8e974429d ci: use parallel build, only build server 2022-05-23 17:30:23 +02:00
Lion Kortlepel 634955660d Revert "Remove unneeded submodules"
This reverts commit a5153e4bc1.
2022-05-23 17:27:01 +02:00
Lion Kortlepel 1e9e068aec Merge branch 'rc-v3.1.0' into feature-9 2022-05-23 17:26:32 +02:00
Lion Kortlepel 87ecfdbc72 use v1 endpoint 2022-05-23 17:21:44 +02:00
Lion Kortlepel 2f1881bbe7 Merge branch 'rc-v3.0.2' into feature-9 2022-05-23 17:06:23 +02:00
Lion Kortlepel 1e60ac5ba2 remove ssl options from config 2022-05-23 17:05:43 +02:00
Lion Kortlepel cd2752a525 remove ssl code
@jimkoen

This was removed because, as useful and as much work as this was, we
can't reasonably take responsibility for this. Instead, a server like
this should *always* be localhost only, and if it's not, it should be
behind an nginx reverse proxy anyways. We're removing the config options
regarding this in one of the next commits.
2022-05-23 16:59:31 +02:00
Lion Kortlepel 66b10f19c2 update lionkor/commandline 2022-05-23 16:59:18 +02:00
42 changed files with 701 additions and 1072 deletions
+26 -60
View File
@@ -7,72 +7,38 @@ env:
jobs: jobs:
linux-build: linux-build:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
- name: Install Dependencies - name: Install Dependencies
env: env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: | run: |
echo ${#beammp_sentry_url} echo ${#beammp_sentry_url}
sudo apt-get update sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev cmake g++-10 sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux run: cmake -E make_directory ${{github.workspace}}/build-linux
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
env: env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url" run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build Server - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Build Tests - name: Archive artifacts
working-directory: ${{github.workspace}}/build-linux uses: actions/upload-artifact@v2
shell: bash with:
run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server-tests --parallel name: BeamMP-Server-linux
path: ${{github.workspace}}/build-linux/BeamMP-Server
- name: Archive server artifact
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-linux
path: ${{github.workspace}}/build-linux/BeamMP-Server
- name: Archive test artifact
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-linux-tests
path: ${{github.workspace}}/build-linux/BeamMP-Server-tests
run-tests:
needs: linux-build
runs-on: ubuntu-20.04
steps:
- uses: actions/download-artifact@master
with:
name: BeamMP-Server-linux-tests
path: ${{github.workspace}}
- name: Install Runtime Dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y liblua5.3 openssl
- name: Test
working-directory: ${{github.workspace}}
shell: bash
run: |
chmod +x ./BeamMP-Server-tests
./BeamMP-Server-tests
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
working-directory: ${{github.workspace}}/build-windows working-directory: ${{github.workspace}}/build-windows
shell: bash shell: bash
run: | run: |
cmake --build . --config Debug cmake --build . --config Debug -t BeamMP-Server --parallel
- name: Archive debug artifacts - name: Archive debug artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
+2 -2
View File
@@ -57,7 +57,7 @@ jobs:
- name: Build - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
@@ -101,7 +101,7 @@ jobs:
- name: Build - name: Build
working-directory: ${{github.workspace}}/build-windows working-directory: ${{github.workspace}}/build-windows
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
+3 -6
View File
@@ -1,6 +1,9 @@
[submodule "deps/commandline"] [submodule "deps/commandline"]
path = deps/commandline path = deps/commandline
url = https://github.com/lionkor/commandline url = https://github.com/lionkor/commandline
[submodule "deps/fmt"]
path = deps/fmt
url = https://github.com/fmtlib/fmt
[submodule "deps/asio"] [submodule "deps/asio"]
path = deps/asio path = deps/asio
url = https://github.com/chriskohlhoff/asio url = https://github.com/chriskohlhoff/asio
@@ -25,9 +28,3 @@
[submodule "deps/json"] [submodule "deps/json"]
path = deps/json path = deps/json
url = https://github.com/nlohmann/json url = https://github.com/nlohmann/json
[submodule "deps/fmt"]
path = deps/fmt
url = https://github.com/fmtlib/fmt
[submodule "deps/doctest"]
path = deps/doctest
url = https://github.com/doctest/doctest
+56 -101
View File
@@ -26,16 +26,17 @@ endif()
set(HTTPLIB_REQUIRE_OPENSSL ON) set(HTTPLIB_REQUIRE_OPENSSL ON)
set(SENTRY_BUILD_SHARED_LIBS OFF) set(SENTRY_BUILD_SHARED_LIBS OFF)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include") include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/rapidjson/include") include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp") include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/commandline") include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/sol2/include") include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/cpp-httplib") include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/json/single_include") include_directories("${PROJECT_SOURCE_DIR}/deps/toml11")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps") include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include")
include_directories("${PROJECT_SOURCE_DIR}/deps")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT=1) add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
# ------------------------ APPLE --------------------------------- # ------------------------ APPLE ---------------------------------
if(APPLE) if(APPLE)
@@ -50,12 +51,18 @@ elseif (WIN32)
message(STATUS "MSVC -> forcing use of statically-linked runtime.") message(STATUS "MSVC -> forcing use of statically-linked runtime.")
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif ()
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
# ------------------------ LINUX --------------------------------- # ------------------------ LINUX ---------------------------------
elseif (UNIX) elseif (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++ -static-libgcc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++ -static-libgcc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
option(SANITIZE "Turns on thread and UB sanitizers" OFF)
if (SANITIZE) if (SANITIZE)
message(STATUS "sanitize is ON") message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
@@ -63,7 +70,7 @@ elseif (UNIX)
endif () endif ()
include_directories("include/sentry-native/include") include_directories("include/sentry-native/include")
set(BUILD_SHARED_LIBS OFF)
# ------------------------ SENTRY --------------------------------- # ------------------------ SENTRY ---------------------------------
message(STATUS "Checking for Sentry URL") message(STATUS "Checking for Sentry URL")
# this is set by the build system. # this is set by the build system.
@@ -80,62 +87,57 @@ add_subdirectory("deps/sentry-native")
# ------------------------ C++ SETUP --------------------------------- # ------------------------ C++ SETUP ---------------------------------
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif ()
# ------------------------ DEPENDENCIES ------------------------------ # ------------------------ DEPENDENCIES ------------------------------
message(STATUS "Adding local source dependencies") message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG # this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps) add_subdirectory(deps)
# ------------------------ VARIABLES --------------------------------- # ------------------------ BEAMMP SERVER -----------------------------
add_executable(BeamMP-Server
src/main.cpp
include/TConsole.h src/TConsole.cpp
include/TServer.h src/TServer.cpp
include/Compat.h src/Compat.cpp
include/Common.h src/Common.cpp
include/Client.h src/Client.cpp
include/VehicleData.h src/VehicleData.cpp
include/TConfig.h src/TConfig.cpp
include/TLuaEngine.h src/TLuaEngine.cpp
include/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp
include/Environment.h)
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline")
include(FindLua) include(FindLua)
include(FindOpenSSL) include(FindOpenSSL)
include(FindThreads) include(FindThreads)
include(FindZLIB) include(FindZLIB)
set(BeamMP_Sources target_include_directories(BeamMP-Server PUBLIC
include/TConsole.h src/TConsole.cpp ${LUA_INCLUDE_DIR}
include/TServer.h src/TServer.cpp
include/Compat.h src/Compat.cpp
include/Common.h src/Common.cpp
include/Client.h src/Client.cpp
include/VehicleData.h src/VehicleData.cpp
include/TConfig.h src/TConfig.cpp
include/TLuaEngine.h src/TLuaEngine.cpp
include/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp
include/TPluginMonitor.h src/TPluginMonitor.cpp
include/Environment.h
)
set(BeamMP_Includes
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline"
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS}
"include/tomlplusplus" "include/tomlplusplus"
"include/sentry-native/include" "include/sentry-native/include"
"include/curl/include" "include/curl/include")
)
set(BeamMP_Definitions target_link_libraries(BeamMP-Server
SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}"
)
set(BeamMP_Libraries
doctest::doctest
OpenSSL::SSL OpenSSL::SSL
OpenSSL::Crypto OpenSSL::Crypto
sol2::sol2 sol2::sol2
@@ -144,55 +146,8 @@ set(BeamMP_Libraries
ZLIB::ZLIB ZLIB::ZLIB
${LUA_LIBRARIES} ${LUA_LIBRARIES}
commandline commandline
sentry sentry)
)
if (WIN32) if (WIN32)
set(BeamMP_PlatformLibs wsock32 ws2_32) target_link_libraries(BeamMP-Server wsock32 ws2_32)
endif () endif ()
# ------------------------ BEAMMP SERVER -----------------------------
add_executable(BeamMP-Server
src/main.cpp
${BeamMP_Sources}
)
target_compile_definitions(BeamMP-Server PRIVATE
${BeamMP_Definitions}
DOCTEST_CONFIG_DISABLE
)
target_include_directories(BeamMP-Server PUBLIC
${BeamMP_Includes}
)
target_link_libraries(BeamMP-Server
${BeamMP_Libraries}
${BeamMP_PlatformLibs}
)
# ------------------------ BEAMMP SERVER TESTS -----------------------
option(BUILD_TESTS "Build BeamMP-Server tests" ON)
if(BUILD_TESTS)
add_executable(BeamMP-Server-tests
test/test_main.cpp
${BeamMP_Sources}
)
target_compile_definitions(BeamMP-Server-tests PRIVATE
${BeamMP_Definitions}
)
target_include_directories(BeamMP-Server-tests PUBLIC
${BeamMP_Includes}
)
target_link_libraries(BeamMP-Server-tests
${BeamMP_Libraries}
${BeamMP_PlatformLibs}
)
endif()
-9
View File
@@ -1,18 +1,10 @@
# v3.1.0 # v3.1.0
- ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console - ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console
- ADDED lua debug facilities (type `:help` when attached to lua via `lua`) - ADDED lua debug facilities (type `:help` when attached to lua via `lua`)
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa - ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
- ADDED FS.ListFiles and FS.ListDirectories - ADDED FS.ListFiles and FS.ListDirectories
- ADDED onFileChanged event, triggered when a server plugin file changes
- FIXED `ip` in MP.GetIdentifiers
- FIXED issue with client->server events which contain ':' - FIXED issue with client->server events which contain ':'
- FIXED a fatal exception on LuaEngine startup if Resources/Server is a symlink
- FIXED onInit not being called on hot-reload
- FIXED incorrect timing calculation of Lua EventTimer loop
- FIXED bug which caused hot-reload not to report syntax errors
- FIXED missing error messages on some event handler calls
# v3.0.2 # v3.0.2
@@ -23,7 +15,6 @@
- FIXED `MP.CreateEventTimer` filling up the queue (see <https://wiki.beammp.com/en/Scripting/new-lua-scripting#mpcreateeventtimerevent_name-string-interval_ms-number-strategy-number-since-v302>) - FIXED `MP.CreateEventTimer` filling up the queue (see <https://wiki.beammp.com/en/Scripting/new-lua-scripting#mpcreateeventtimerevent_name-string-interval_ms-number-strategy-number-since-v302>)
- FIXED `MP.TriggerClientEvent` not kicking the client if it failed - FIXED `MP.TriggerClientEvent` not kicking the client if it failed
- FIXED Lua result queue handling not checking all results - FIXED Lua result queue handling not checking all results
- FIXED bug which caused ServerConfig.toml to generate incorrectly
# v3.0.1 # v3.0.1
+1 -1
View File
@@ -109,7 +109,7 @@ On windows, use git-bash for these commands. On Linux, these should work in your
1. Make sure you have all [prerequisites](#prerequisites) installed 1. Make sure you have all [prerequisites](#prerequisites) installed
2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Server`. 2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Server`.
3. Ensure that all submodules are initialized by running `git submodule update --init --recursive`. Then change into the cloned directory by running `cd BeamMP-Server`. 3. Ensure that all submodules are initialized by running `git submodule update --init --recursive`. Then change into the cloned directory by running `cd BeamMP-Server`.
4. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v3.0.1` for version 3.0.1. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags). 4. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v2.3.3` for version 2.3.3. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags).
5. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (with `.`) 5. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (with `.`)
6. Run `make` 6. Run `make`
7. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server. 7. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server.
+1 -1
View File
@@ -1,7 +1,7 @@
include_directories("${PROJECT_SOURCE_DIR}/deps") include_directories("${PROJECT_SOURCE_DIR}/deps")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/doctest")
-1
Submodule deps/doctest deleted from 7b98851331
Vendored
+1 -1
Submodule deps/fmt updated: ce246aaf74...17dda58391
Vendored
+1 -1
Submodule deps/json updated: ede6667858...eb21824147
Vendored
+1 -1
+79 -121
View File
@@ -12,14 +12,9 @@ extern TSentry Sentry;
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <sstream> #include <sstream>
#include <zlib.h> #include <zlib.h>
#include <doctest/doctest.h>
#include <filesystem>
namespace fs = std::filesystem;
#include "Compat.h" #include "Compat.h"
#include "TConsole.h" #include "TConsole.h"
@@ -47,8 +42,6 @@ public:
std::string Resource { "Resources" }; std::string Resource { "Resources" };
std::string MapName { "/levels/gridmap_v2/info.json" }; std::string MapName { "/levels/gridmap_v2/info.json" };
std::string Key {}; std::string Key {};
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { false }; bool HTTPServerEnabled { false };
int MaxPlayers { 8 }; int MaxPlayers { 8 };
bool Private { true }; bool Private { true };
@@ -97,8 +90,6 @@ public:
static void CheckForUpdates(); static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str); static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
static bool IsOutdated(const Version& Current, const Version& Newest); static bool IsOutdated(const Version& Current, const Version& Newest);
static bool IsShuttingDown();
static void SleepSafeSeconds(size_t Seconds);
static void InitializeConsole() { static void InitializeConsole() {
if (!mConsole) { if (!mConsole) {
@@ -124,14 +115,10 @@ public:
static void SetSubsystemStatus(const std::string& Subsystem, Status status); static void SetSubsystemStatus(const std::string& Subsystem, Status status);
private: private:
static void SetShutdown(bool Val);
static inline SystemStatusMap mSystemStatusMap {}; static inline SystemStatusMap mSystemStatusMap {};
static inline std::mutex mSystemStatusMapMutex {}; static inline std::mutex mSystemStatusMapMutex {};
static inline std::string mPPS; static inline std::string mPPS;
static inline std::unique_ptr<TConsole> mConsole; static inline std::unique_ptr<TConsole> mConsole;
static inline std::shared_mutex mShutdownMtx {};
static inline bool mShutdown { false };
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
@@ -142,126 +129,97 @@ std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str); void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__) #define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024llu #define KB 1024
#define MB (KB * 1024llu) #define MB (KB * 1024)
#define GB (MB * 1024llu)
#define SSU_UNRAW SECRET_SENTRY_URL #define SSU_UNRAW SECRET_SENTRY_URL
#define _file_basename std::filesystem::path(__FILE__).filename().string() #define _file_basename std::filesystem::path(__FILE__).filename().string()
#define _line std::to_string(__LINE__) #define _line std::to_string(__LINE__)
#define _in_lambda (std::string(__func__) == "operator()") #define _in_lambda (std::string(__func__) == "operator()")
// we would like the full function signature 'void a::foo() const'
// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__,
// feel free to add more
#if defined(WIN32)
#define _function_name std::string(__FUNCSIG__)
#elif defined(__unix) || defined(__unix__)
#define _function_name std::string(__PRETTY_FUNCTION__)
#else
#define _function_name std::string(__func__)
#endif
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
// each info/debug/warn... call instead of the 'filename:line' format.
#if defined(BMP_FULL_FUNCTION_NAMES)
#define _this_location (ThreadName() + _function_name + " ")
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#define SU_RAW SSU_UNRAW
#else // !defined(DEBUG)
#define SU_RAW RAWIFY(SSU_UNRAW)
#define _this_location (ThreadName())
#endif // defined(DEBUG)
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define beammp_lua_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// for those times when you just need to ignore something :^) // for those times when you just need to ignore something :^)
// explicity disables a [[nodiscard]] warning // explicity disables a [[nodiscard]] warning
#define beammp_ignore(x) (void)x #define beammp_ignore(x) (void)x
// trace() is a debug-build debug()
// clang-format off
#ifdef DOCTEST_CONFIG_DISABLE
// we would like the full function signature 'void a::foo() const'
// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__,
// feel free to add more
#if defined(WIN32)
#define _function_name std::string(__FUNCSIG__)
#elif defined(__unix) || defined(__unix__)
#define _function_name std::string(__PRETTY_FUNCTION__)
#else
#define _function_name std::string(__func__)
#endif
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
// each info/debug/warn... call instead of the 'filename:line' format.
#if defined(BMP_FULL_FUNCTION_NAMES)
#define _this_location (ThreadName() + _function_name + " ")
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#endif // defined(DEBUG)
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define beammp_lua_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// trace() is a debug-build debug()
#if defined(DEBUG)
#define beammp_trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else
#define beammp_trace(x)
#endif // defined(DEBUG)
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__))
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
#else // DOCTEST_CONFIG_DISABLE
#define beammp_error(x) /* x */
#define beammp_lua_error(x) /* x */
#define beammp_warn(x) /* x */
#define beammp_lua_warn(x) /* x */
#define beammp_info(x) /* x */
#define beammp_event(x) /* x */
#define beammp_debug(x) /* x */
#define beammp_trace(x) /* x */
#define luaprint(x) /* x */
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__))
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
#endif // DOCTEST_CONFIG_DISABLE
#if defined(DEBUG) #if defined(DEBUG)
#define SU_RAW SSU_UNRAW #define beammp_trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else #else
#define SU_RAW RAWIFY(SSU_UNRAW) #define beammp_trace(x)
#define _this_location (ThreadName()) #endif // defined(DEBUG)
#endif
// clang-format on #define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__))
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
void LogChatMessage(const std::string& name, int id, const std::string& msg); void LogChatMessage(const std::string& name, int id, const std::string& msg);
+11 -1
View File
@@ -17,6 +17,10 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
class TServer;
class TNetwork;
class TResourceManager;
namespace Http { namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {}); std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {});
@@ -28,13 +32,19 @@ const std::string ErrorString = "-1";
namespace Server { namespace Server {
class THttpServerInstance { class THttpServerInstance {
public: public:
THttpServerInstance(); THttpServerInstance(TServer&, TNetwork&, TResourceManager&);
~THttpServerInstance();
static fs::path KeyFilePath;
static fs::path CertFilePath;
protected: protected:
void operator()(); void operator()();
private: private:
std::thread mThread; std::thread mThread;
TServer& mServer;
TNetwork& mNetwork;
TResourceManager& mResourceManager;
}; };
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@
#include <filesystem> #include <filesystem>
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
#include <toml11/toml.hpp> // header-only version of TOML++ #include <toml.hpp> // header-only version of TOML++
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -19,7 +19,7 @@ public:
void FlushToFile(); void FlushToFile();
private: private:
void CreateConfigFile(); void CreateConfigFile(std::string_view name);
void ParseFromFile(std::string_view name); void ParseFromFile(std::string_view name);
void PrintDebug(); void PrintDebug();
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue); void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue);
-2
View File
@@ -35,7 +35,6 @@ private:
void Command_List(const std::string& cmd, const std::vector<std::string>& args); void Command_List(const std::string& cmd, const std::vector<std::string>& args);
void Command_Status(const std::string& cmd, const std::vector<std::string>& args); void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args); void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
void Command_Clear(const std::string&, const std::vector<std::string>& args);
void Command_Say(const std::string& FullCommand); void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n); bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
@@ -51,7 +50,6 @@ private:
{ "list", [this](const auto& a, const auto& b) { Command_List(a, b); } }, { "list", [this](const auto& a, const auto& b) { Command_List(a, b); } },
{ "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } }, { "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } },
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } }, { "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called { "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
}; };
+1
View File
@@ -15,6 +15,7 @@ private:
std::string GenerateCall(); std::string GenerateCall();
std::string GetPlayers(); std::string GetPlayers();
bool mShutdown = false;
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
TServer& mServer; TServer& mServer;
}; };
+21 -21
View File
@@ -13,7 +13,7 @@
#include <queue> #include <queue>
#include <random> #include <random>
#include <set> #include <set>
#include <toml11/toml.hpp> #include <toml.hpp>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -48,7 +48,6 @@ struct TLuaPluginConfig {
static inline const std::string FileName = "PluginConfig.toml"; static inline const std::string FileName = "PluginConfig.toml";
TLuaStateId StateId; TLuaStateId StateId;
// TODO: Add execute list // TODO: Add execute list
// TODO: Build a better toml serializer, or some way to do this in an easier way
}; };
struct TLuaChunk { struct TLuaChunk {
@@ -60,7 +59,20 @@ struct TLuaChunk {
std::string PluginPath; std::string PluginPath;
}; };
class TLuaEngine : public std::enable_shared_from_this<TLuaEngine>, IThreaded { class TPluginMonitor : IThreaded {
public:
TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown);
void operator()();
private:
TLuaEngine& mEngine;
fs::path mPath;
std::atomic_bool& mShutdown;
std::unordered_map<std::string, fs::file_time_type> mFileTimes;
};
class TLuaEngine : IThreaded {
public: public:
enum CallStrategy : int { enum CallStrategy : int {
BestEffort, BestEffort,
@@ -119,6 +131,7 @@ public:
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false); void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName); void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
template <typename... ArgsT>
/** /**
* *
* @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means * @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means
@@ -127,7 +140,6 @@ public:
* @param Args * @param Args
* @return * @return
*/ */
template <typename... ArgsT>
[[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) { [[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) {
std::unique_lock Lock(mLuaEventsMutex); std::unique_lock Lock(mLuaEventsMutex);
beammp_event(EventName); beammp_event(EventName);
@@ -147,21 +159,6 @@ public:
} }
return Results; // return Results; //
} }
template <typename... ArgsT>
[[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerLocalEvent(const TLuaStateId& StateId, const std::string& EventName, ArgsT&&... Args) {
std::unique_lock Lock(mLuaEventsMutex);
beammp_event(EventName + " in '" + StateId + "'");
if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately
return {};
}
std::vector<std::shared_ptr<TLuaResult>> Results;
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
const auto Handlers = GetEventHandlersForState(EventName, StateId);
for (const auto& Handler : Handlers) {
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
}
return Results;
}
std::set<std::string> GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); std::set<std::string> GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId);
void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy); void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy);
void CancelEventTimers(const std::string& EventName, TLuaStateId StateId); void CancelEventTimers(const std::string& EventName, TLuaStateId StateId);
@@ -187,7 +184,7 @@ private:
class StateThreadData : IThreaded { class StateThreadData : IThreaded {
public: public:
StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine); StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine);
StateThreadData(const StateThreadData&) = delete; StateThreadData(const StateThreadData&) = delete;
~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); } ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
@@ -218,6 +215,7 @@ private:
sol::table Lua_FS_ListDirectories(const std::string& Path); sol::table Lua_FS_ListDirectories(const std::string& Path);
std::string mName; std::string mName;
std::atomic_bool& mShutdown;
TLuaStateId mStateId; TLuaStateId mStateId;
lua_State* mState; lua_State* mState;
std::thread mThread; std::thread mThread;
@@ -246,7 +244,9 @@ private:
TNetwork* mNetwork; TNetwork* mNetwork;
TServer* mServer; TServer* mServer;
const fs::path mResourceServerPath; TPluginMonitor mPluginMonitor;
std::atomic_bool mShutdown { false };
fs::path mResourceServerPath;
std::vector<std::shared_ptr<TLuaPlugin>> mLuaPlugins; std::vector<std::shared_ptr<TLuaPlugin>> mLuaPlugins;
std::unordered_map<TLuaStateId, std::unique_ptr<StateThreadData>> mLuaStates; std::unordered_map<TLuaStateId, std::unique_ptr<StateThreadData>> mLuaStates;
std::recursive_mutex mLuaStatesMutex; std::recursive_mutex mLuaStatesMutex;
+1 -1
View File
@@ -32,6 +32,7 @@ private:
TServer& mServer; TServer& mServer;
TPPSMonitor& mPPSMonitor; TPPSMonitor& mPPSMonitor;
SOCKET mUDPSock {}; SOCKET mUDPSock {};
bool mShutdown { false };
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
std::thread mUDPThread; std::thread mUDPThread;
std::thread mTCPThread; std::thread mTCPThread;
@@ -47,5 +48,4 @@ private:
void SendFile(TClient& c, const std::string& Name); void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size); static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size);
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name); static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
static uint8_t* SendSplit(TClient& c, SOCKET Socket, uint8_t* DataPtr, size_t Size);
}; };
+2 -1
View File
@@ -22,5 +22,6 @@ private:
TServer& mServer; TServer& mServer;
std::optional<std::reference_wrapper<TNetwork>> mNetwork { std::nullopt }; std::optional<std::reference_wrapper<TNetwork>> mNetwork { std::nullopt };
bool mShutdown { false };
int mInternalPPS { 0 }; int mInternalPPS { 0 };
}; };
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include "Common.h"
#include "IThreaded.h"
#include <atomic>
#include <memory>
#include <unordered_map>
class TLuaEngine;
class TPluginMonitor : IThreaded, public std::enable_shared_from_this<TPluginMonitor> {
public:
TPluginMonitor(const fs::path& Path, std::shared_ptr<TLuaEngine> Engine);
void operator()();
private:
std::shared_ptr<TLuaEngine> mEngine;
fs::path mPath;
std::unordered_map<std::string, fs::file_time_type> mFileTimes;
};
+7 -1
View File
@@ -1,10 +1,13 @@
#pragma once #pragma once
#include <nlohmann/json.hpp>
#include <string> #include <string>
using json = nlohmann::json;
class TVehicleData final { class TVehicleData final {
public: public:
TVehicleData(int ID, std::string Data); TVehicleData(int ID, const std::string& Data);
~TVehicleData(); ~TVehicleData();
// We cannot delete this, since vector needs to be able to copy when it resizes. // We cannot delete this, since vector needs to be able to copy when it resizes.
// Deleting this causes some wacky template errors which are hard to decipher, // Deleting this causes some wacky template errors which are hard to decipher,
@@ -19,9 +22,12 @@ public:
bool operator==(const TVehicleData& v) const { return mID == v.mID; } bool operator==(const TVehicleData& v) const { return mID == v.mID; }
const json& Json() const;
private: private:
int mID { -1 }; int mID { -1 };
std::string mData; std::string mData;
json mJson;
}; };
// TODO: unused now, remove? // TODO: unused now, remove?
+135
View File
@@ -0,0 +1,135 @@
openapi: "3.0.2"
info:
title: BeamMP-Server
version: "api-v1"
description: |
The BeamMP-Server optionally runs an HTTP server
which can be used to query information about the server's
status, health, players, etc.
servers:
- url: http://localhost:{port}/api/v1
description: BeamMP-Server API v1
variables:
port:
default: "8000"
paths:
/player/{name}/vehicles:
parameters:
- name: "name"
in: path
required: true
schema:
type: string
example: "LionKor"
get:
summary: vehicles this player has spawend
responses:
"200":
description: OK
content:
"application/json":
schema:
type: array
items:
type: object
properties:
jbm:
type: string
example: "bolide"
/version:
get:
summary: server version
description: |
Server version in 'major.minor.patch' semver format
responses:
"200":
description: OK
content:
"text/plain":
schema:
type: string
example: "3.1.0"
/players:
get:
summary: list of players on this server
responses:
"200":
description: OK
content:
"application/json":
schema:
type: array
example:
- "LionKor"
- "Titch"
items:
type: string
/config:
get:
summary: describes server configuration
description: |
An overview over the configuration currently
used by this server.
responses:
"200":
description: OK
content:
"application/json":
schema:
type: object
properties:
name:
type: string
example: "my awesome beammp server"
description:
type: string
example: "join!"
max_players:
type: integer
example: 7
max_cars:
type: integer
example: 2
map:
type: string
example: "/levels/gridmap_v2/info.json"
private:
type: boolean
/ready:
get:
summary: whether the server has started fully
description: |
True once all subsystems have started up.
This doesn't tell you whether they're healthy,
check /health for that information.
responses:
"200":
description: OK
content:
"text/plain":
schema:
type: boolean
/health:
get:
summary: health of the server's systems
responses:
"200":
description: OK
content:
"application/json":
schema:
type: object
properties:
healthy:
type: boolean
good:
type: array
items:
type: string
example: "Heartbeat"
bad:
type: array
items:
type: string
example: "ResourceManager"
-76
View File
@@ -1,7 +1,6 @@
#include "ArgsParser.h" #include "ArgsParser.h"
#include "Common.h" #include "Common.h"
#include <algorithm> #include <algorithm>
#include <doctest/doctest.h>
void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) { void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
for (const auto& Arg : ArgList) { for (const auto& Arg : ArgList) {
@@ -93,78 +92,3 @@ void ArgsParser::ConsumeLongFlag(const std::string& Arg) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored."); beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
} }
} }
TEST_CASE("ArgsParser") {
ArgsParser parser;
SUBCASE("Simple args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({ "--a", "--hello" });
CHECK(parser.Verify());
CHECK(parser.FoundArgument({ "a" }));
CHECK(parser.FoundArgument({ "hello" }));
CHECK(parser.FoundArgument({ "a", "hello" }));
CHECK(!parser.FoundArgument({ "b" }));
CHECK(!parser.FoundArgument({ "goodbye" }));
}
SUBCASE("No args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({});
CHECK(parser.Verify());
CHECK(!parser.FoundArgument({ "a" }));
CHECK(!parser.FoundArgument({ "hello" }));
CHECK(!parser.FoundArgument({ "a", "hello" }));
CHECK(!parser.FoundArgument({ "b" }));
CHECK(!parser.FoundArgument({ "goodbye" }));
CHECK(!parser.FoundArgument({ "" }));
}
SUBCASE("Value args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::HAS_VALUE);
parser.Parse({ "--a=5", "--hello=world" });
CHECK(parser.Verify());
REQUIRE(parser.FoundArgument({ "a" }));
REQUIRE(parser.FoundArgument({ "hello" }));
CHECK(parser.GetValueOfArgument({ "a" }).has_value());
CHECK(parser.GetValueOfArgument({ "a" }).value() == "5");
CHECK(parser.GetValueOfArgument({ "hello" }).has_value());
CHECK(parser.GetValueOfArgument({ "hello" }).value() == "world");
}
SUBCASE("Mixed value & no-value args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({ "--a=5", "--hello" });
CHECK(parser.Verify());
REQUIRE(parser.FoundArgument({ "a" }));
REQUIRE(parser.FoundArgument({ "hello" }));
CHECK(parser.GetValueOfArgument({ "a" }).has_value());
CHECK(parser.GetValueOfArgument({ "a" }).value() == "5");
CHECK(!parser.GetValueOfArgument({ "hello" }).has_value());
}
SUBCASE("Required args") {
SUBCASE("Two required, two present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--a", "--hello" });
CHECK(parser.Verify());
}
SUBCASE("Two required, one present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--a" });
CHECK(!parser.Verify());
}
SUBCASE("Two required, none present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--b" });
CHECK(!parser.Verify());
}
}
}
-126
View File
@@ -12,9 +12,6 @@
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
Application::TSettings Application::Settings = {}; Application::TSettings Application::Settings = {};
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
@@ -25,7 +22,6 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
} }
void Application::GracefullyShutdown() { void Application::GracefullyShutdown() {
SetShutdown(true);
static bool AlreadyShuttingDown = false; static bool AlreadyShuttingDown = false;
static uint8_t ShutdownAttempts = 0; static uint8_t ShutdownAttempts = 0;
if (AlreadyShuttingDown) { if (AlreadyShuttingDown) {
@@ -65,23 +61,6 @@ std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
return Version; return Version;
} }
TEST_CASE("Application::VersionStrToInts") {
auto v = Application::VersionStrToInts("1.2.3");
CHECK(v[0] == 1);
CHECK(v[1] == 2);
CHECK(v[2] == 3);
v = Application::VersionStrToInts("10.20.30");
CHECK(v[0] == 10);
CHECK(v[1] == 20);
CHECK(v[2] == 30);
v = Application::VersionStrToInts("100.200.255");
CHECK(v[0] == 100);
CHECK(v[1] == 200);
CHECK(v[2] == 255);
}
bool Application::IsOutdated(const Version& Current, const Version& Newest) { bool Application::IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) { if (Newest.major > Current.major) {
return true; return true;
@@ -94,65 +73,6 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) {
} }
} }
bool Application::IsShuttingDown() {
std::shared_lock Lock(mShutdownMtx);
return mShutdown;
}
void Application::SleepSafeSeconds(size_t Seconds) {
// Sleeps for 500 ms, checks if a shutdown occurred, and so forth
for (size_t i = 0; i < Seconds * 2; ++i) {
if (Application::IsShuttingDown()) {
return;
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
}
TEST_CASE("Application::IsOutdated (version check)") {
SUBCASE("Same version") {
CHECK(!Application::IsOutdated({ 1, 2, 3 }, { 1, 2, 3 }));
}
// we need to use over 1-2 digits to test against lexical comparisons
SUBCASE("Patch outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor), uint8_t(Patch + 1) }));
}
}
}
}
SUBCASE("Minor outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor + 1), uint8_t(Patch) }));
}
}
}
}
SUBCASE("Major outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor), uint8_t(Patch) }));
}
}
}
}
SUBCASE("All outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor + 1), uint8_t(Patch + 1) }));
}
}
}
}
}
void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) { void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) {
switch (status) { switch (status) {
case Status::Good: case Status::Good:
@@ -175,20 +95,6 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status
mSystemStatusMap[Subsystem] = status; mSystemStatusMap[Subsystem] = status;
} }
void Application::SetShutdown(bool Val) {
std::unique_lock Lock(mShutdownMtx);
mShutdown = Val;
}
TEST_CASE("Application::SetSubsystemStatus") {
Application::SetSubsystemStatus("Test", Application::Status::Good);
auto Map = Application::GetSubsystemStatuses();
CHECK(Map.at("Test") == Application::Status::Good);
Application::SetSubsystemStatus("Test", Application::Status::Bad);
Map = Application::GetSubsystemStatuses();
CHECK(Map.at("Test") == Application::Status::Bad);
}
void Application::CheckForUpdates() { void Application::CheckForUpdates() {
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting);
static bool FirstTime = true; static bool FirstTime = true;
@@ -246,25 +152,6 @@ std::string ThreadName(bool DebugModeOverride) {
return ""; return "";
} }
TEST_CASE("ThreadName") {
RegisterThread("MyThread");
auto OrigDebug = Application::Settings.DebugModeEnabled;
// ThreadName adds a space at the end, legacy but we need it still
SUBCASE("Debug mode enabled") {
Application::Settings.DebugModeEnabled = true;
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "MyThread ");
}
SUBCASE("Debug mode disabled") {
Application::Settings.DebugModeEnabled = false;
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "");
}
// cleanup
Application::Settings.DebugModeEnabled = OrigDebug;
}
void RegisterThread(const std::string& str) { void RegisterThread(const std::string& str) {
std::string ThreadId; std::string ThreadId;
#ifdef BEAMMP_WINDOWS #ifdef BEAMMP_WINDOWS
@@ -282,11 +169,6 @@ void RegisterThread(const std::string& str) {
threadNameMap[std::this_thread::get_id()] = str; threadNameMap[std::this_thread::get_id()] = str;
} }
TEST_CASE("RegisterThread") {
RegisterThread("MyThread");
CHECK(threadNameMap.at(std::this_thread::get_id()) == "MyThread");
}
Version::Version(uint8_t major, uint8_t minor, uint8_t patch) Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
: major(major) : major(major)
, minor(minor) , minor(minor)
@@ -300,12 +182,6 @@ std::string Version::AsString() {
return fmt::format("{:d}.{:d}.{:d}", major, minor, patch); return fmt::format("{:d}.{:d}.{:d}", major, minor, patch);
} }
TEST_CASE("Version::AsString") {
CHECK(Version { 0, 0, 0 }.AsString() == "0.0.0");
CHECK(Version { 1, 2, 3 }.AsString() == "1.2.3");
CHECK(Version { 255, 255, 255 }.AsString() == "255.255.255");
}
void LogChatMessage(const std::string& name, int id, const std::string& msg) { void LogChatMessage(const std::string& name, int id, const std::string& msg) {
if (Application::Settings.LogChat) { if (Application::Settings.LogChat) {
std::stringstream ss; std::stringstream ss;
@@ -317,9 +193,7 @@ void LogChatMessage(const std::string& name, int id, const std::string& msg) {
ss << name << ""; ss << name << "";
} }
ss << msg; ss << msg;
#ifdef DOCTEST_CONFIG_DISABLE
Application::Console().Write(ss.str()); Application::Console().Write(ss.str());
#endif
} }
} }
-20
View File
@@ -1,8 +1,5 @@
#include "Compat.h" #include "Compat.h"
#include <cstring>
#include <doctest/doctest.h>
#ifndef WIN32 #ifndef WIN32
static struct termios old, current; static struct termios old, current;
@@ -23,23 +20,6 @@ void resetTermios(void) {
tcsetattr(0, TCSANOW, &old); tcsetattr(0, TCSANOW, &old);
} }
TEST_CASE("init and reset termios") {
if (isatty(STDIN_FILENO)) {
struct termios original;
tcgetattr(0, &original);
SUBCASE("no echo") {
initTermios(false);
}
SUBCASE("yes echo") {
initTermios(true);
}
resetTermios();
struct termios current;
tcgetattr(0, &current);
CHECK(std::memcmp(&original, &current, sizeof(struct termios)) == 0);
}
}
char getch_(int echo) { char getch_(int echo) {
char ch; char ch;
initTermios(echo); initTermios(echo);
+120 -17
View File
@@ -4,6 +4,9 @@
#include "Common.h" #include "Common.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "TNetwork.h"
#include "TResourceManager.h"
#include "TServer.h"
#include "httplib.h" #include "httplib.h"
#include <map> #include <map>
@@ -142,29 +145,115 @@ std::string Http::Status::ToString(int Code) {
} }
} }
TEST_CASE("Http::Status::ToString") { #define API_V1 "/api/v1"
CHECK(Http::Status::ToString(200) == "OK");
CHECK(Http::Status::ToString(696969) == "696969");
CHECK(Http::Status::ToString(-1) == "Invalid Response Code");
}
Http::Server::THttpServerInstance::THttpServerInstance() { Http::Server::THttpServerInstance::THttpServerInstance(TServer& Server, TNetwork& Network, TResourceManager& ResMan)
: mServer(Server)
, mNetwork(Network)
, mResourceManager(ResMan) {
Application::SetSubsystemStatus("HTTPServer", Application::Status::Starting); Application::SetSubsystemStatus("HTTPServer", Application::Status::Starting);
mThread = std::thread(&Http::Server::THttpServerInstance::operator(), this); mThread = std::thread(&Http::Server::THttpServerInstance::operator(), this);
}
Http::Server::THttpServerInstance::~THttpServerInstance() {
mThread.detach(); mThread.detach();
} }
void Http::Server::THttpServerInstance::operator()() try { void Http::Server::THttpServerInstance::operator()() try {
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort)); beammp_infof("HTTP Server starting on [{}]:{}", Application::Settings.HTTPServerIP, Application::Settings.HTTPServerPort);
std::unique_ptr<httplib::Server> HttpLibServerInstance; if (Application::Settings.Private
HttpLibServerInstance = std::make_unique<httplib::Server>(); && Application::Settings.HTTPServerIP != "::"
// todo: make this IP agnostic so people can set their own IP && Application::Settings.HTTPServerIP != "localhost"
HttpLibServerInstance->Get("/", [](const httplib::Request&, httplib::Response& res) { && Application::Settings.HTTPServerIP != "127.0.0.1") {
res.set_content("<!DOCTYPE html><article><h1>Hello World!</h1><section><p>BeamMP Server can now serve HTTP requests!</p></section></article></html>", "text/html"); beammp_warnf("HTTP Server is running on a possibly public IP ({}), but the server is private. The server's HTTP API supports viewing config, players, and other information, even though the server is private. (this is not an error)", Application::Settings.HTTPServerIP);
}
std::unique_ptr<httplib::Server> Server = std::make_unique<httplib::Server>();
Server->Get(API_V1 R"(/player/(.+)/vehicles)", [this](const httplib::Request& Req, httplib::Response& Res) {
if (Req.matches.empty()) {
Res.status = 404;
} else {
const std::string PlayerName = Req.matches[1];
bool Found = false;
json Result = json::array();
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) {
if (!ClientPtr.expired()) {
const auto Client = ClientPtr.lock();
if (Client->GetName() == PlayerName) {
const auto [Cars, Lock] = Client->GetAllCars();
for (const auto& Car : *Cars) {
Result.emplace_back().at("jbm") = Car.Json().at("jbm");
}
Found = true;
}
}
return true;
});
if (!Found) {
Res.status = 404;
} else {
Res.status = 200;
Res.set_content(Result.dump(), "application/json");
}
}
}); });
HttpLibServerInstance->Get("/health", [](const httplib::Request&, httplib::Response& res) {
Server->Get(API_V1 "/players", [this](const httplib::Request&, httplib::Response& res) {
res.status = 200;
json Players = json::array();
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) {
if (!ClientPtr.expired()) {
Players.push_back(ClientPtr.lock()->GetName());
}
return true;
});
res.set_content(Players.dump(),
"application/json");
});
Server->Get(API_V1 "/version", [](const httplib::Request&, httplib::Response& res) {
res.status = 200;
res.set_content(Application::ServerVersionString(), "text/plain");
});
Server->Get(API_V1 "/config", [](const httplib::Request&, httplib::Response& res) {
res.status = 200;
res.set_content(json {
{ "name", Application::Settings.ServerName },
{ "description", Application::Settings.ServerDesc },
{ "max_players", Application::Settings.MaxPlayers },
{ "max_cars", Application::Settings.MaxCars },
{ "map", Application::Settings.MapName },
{ "private", Application::Settings.Private },
}
.dump(),
"application/json");
});
Server->Get(API_V1 "/ready", [](const httplib::Request&, httplib::Response& res) {
auto Statuses = Application::GetSubsystemStatuses();
bool Started = true;
for (const auto& NameStatusPair : Statuses) {
switch (NameStatusPair.second) {
case Application::Status::Starting:
case Application::Status::ShuttingDown:
case Application::Status::Shutdown:
Started = false;
break;
case Application::Status::Good:
case Application::Status::Bad:
break;
}
}
res.status = 200;
res.set_content(Started ? "true" : "false", "text/plain");
});
Server->Get(API_V1 "/health", [](const httplib::Request&, httplib::Response& res) {
size_t SystemsGood = 0; size_t SystemsGood = 0;
size_t SystemsBad = 0; size_t SystemsBad = 0;
json Good = json::array();
json Bad = json::array();
auto Statuses = Application::GetSubsystemStatuses(); auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) { for (const auto& NameStatusPair : Statuses) {
switch (NameStatusPair.second) { switch (NameStatusPair.second) {
@@ -172,30 +261,44 @@ void Http::Server::THttpServerInstance::operator()() try {
case Application::Status::ShuttingDown: case Application::Status::ShuttingDown:
case Application::Status::Shutdown: case Application::Status::Shutdown:
case Application::Status::Good: case Application::Status::Good:
Good.push_back(NameStatusPair.first);
SystemsGood++; SystemsGood++;
break; break;
case Application::Status::Bad: case Application::Status::Bad:
Bad.push_back(NameStatusPair.first);
SystemsBad++; SystemsBad++;
break; break;
} }
} }
res.set_content( res.set_content(
json { json {
{ "ok", SystemsBad == 0 }, { "healthy", SystemsBad == 0 },
{ "good", Good },
{ "bad", Bad },
} }
.dump(), .dump(),
"application/json"); "application/json");
res.status = 200; res.status = 200;
}); });
// magic endpoint // magic endpoint
HttpLibServerInstance->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) { Server->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) {
res.set_content(std::string(Magic), "text/plain"); res.set_content(std::string(Magic), "text/plain");
}); });
HttpLibServerInstance->set_logger([](const httplib::Request& Req, const httplib::Response& Res) { Server->set_logger([](const httplib::Request& Req, const httplib::Response& Res) {
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status)); beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
}); });
Server->set_error_handler([](const httplib::Request&, httplib::Response& Res) {
if (Res.status >= 400 && Res.body.empty()) {
Res.set_content("Error: " + std::to_string(Res.status) + " " + Map.at(Res.status), "text/plain");
}
});
Server->set_exception_handler([](const httplib::Request& Req, httplib::Response& Res, std::exception& e) {
Res.status = 500;
Res.set_content("Internal Server Error", "text/plain");
beammp_errorf("Exception in http server serving '{}': {}", Req.target, e.what());
});
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good); Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort); auto ret = Server->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
if (!ret) { if (!ret) {
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it."); beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
} }
+11 -146
View File
@@ -102,14 +102,6 @@ void LuaAPI::Print(sol::variadic_args Args) {
luaprint(ToPrint); luaprint(ToPrint);
} }
TEST_CASE("LuaAPI::MP::GetServerVersion") {
const auto [ma, mi, pa] = LuaAPI::MP::GetServerVersion();
const auto real = Application::ServerVersion();
CHECK(ma == real.major);
CHECK(mi == real.minor);
CHECK(pa == real.patch);
}
static inline bool InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) { static inline bool InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) {
std::string Packet = "E:" + EventName + ":" + Data; std::string Packet = "E:" + EventName + ":" + Data;
if (PlayerID == -1) if (PlayerID == -1)
@@ -184,57 +176,50 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
if (NewValue.is<bool>()) { if (NewValue.is<bool>()) {
Application::Settings.DebugModeEnabled = NewValue.as<bool>(); Application::Settings.DebugModeEnabled = NewValue.as<bool>();
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false")); beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
} else { } else
beammp_lua_error("set invalid argument [2] expected boolean"); beammp_lua_error("set invalid argument [2] expected boolean");
}
break; break;
case 1: // private case 1: // private
if (NewValue.is<bool>()) { if (NewValue.is<bool>()) {
Application::Settings.Private = NewValue.as<bool>(); Application::Settings.Private = NewValue.as<bool>();
beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false")); beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false"));
} else { } else
beammp_lua_error("set invalid argument [2] expected boolean"); beammp_lua_error("set invalid argument [2] expected boolean");
}
break; break;
case 2: // max cars case 2: // max cars
if (NewValue.is<int>()) { if (NewValue.is<int>()) {
Application::Settings.MaxCars = NewValue.as<int>(); Application::Settings.MaxCars = NewValue.as<int>();
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars)); beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars));
} else { } else
beammp_lua_error("set invalid argument [2] expected integer"); beammp_lua_error("set invalid argument [2] expected integer");
}
break; break;
case 3: // max players case 3: // max players
if (NewValue.is<int>()) { if (NewValue.is<int>()) {
Application::Settings.MaxPlayers = NewValue.as<int>(); Application::Settings.MaxPlayers = NewValue.as<int>();
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers)); beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers));
} else { } else
beammp_lua_error("set invalid argument [2] expected integer"); beammp_lua_error("set invalid argument [2] expected integer");
}
break; break;
case 4: // Map case 4: // Map
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.MapName = NewValue.as<std::string>(); Application::Settings.MapName = NewValue.as<std::string>();
beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName); beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName);
} else { } else
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
case 5: // Name case 5: // Name
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.ServerName = NewValue.as<std::string>(); Application::Settings.ServerName = NewValue.as<std::string>();
beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName); beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName);
} else { } else
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
case 6: // Desc case 6: // Desc
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.ServerDesc = NewValue.as<std::string>(); Application::Settings.ServerDesc = NewValue.as<std::string>();
beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc); beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc);
} else { } else
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
default: default:
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this."); beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
@@ -270,9 +255,7 @@ void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
ToPrint += LuaToString(static_cast<const sol::object>(Arg)); ToPrint += LuaToString(static_cast<const sol::object>(Arg));
ToPrint += "\t"; ToPrint += "\t";
} }
#ifdef DOCTEST_CONFIG_DISABLE
Application::Console().WriteRaw(ToPrint); Application::Console().WriteRaw(ToPrint);
#endif
} }
int LuaAPI::PanicHandler(lua_State* State) { int LuaAPI::PanicHandler(lua_State* State) {
@@ -295,7 +278,7 @@ static std::pair<bool, std::string> FSWrapper(FnT Fn, ArgsT&&... Args) {
std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path) { std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::create_directories(Path, errc); fs::create_directories(fs::relative(Path), errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -303,33 +286,6 @@ std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::CreateDirectory") {
std::string TestDir = "beammp_test_dir";
fs::remove_all(TestDir);
SUBCASE("Single level dir") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir));
}
SUBCASE("Multi level dir") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir + "/a/b/c");
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir + "/a/b/c"));
}
SUBCASE("Already exists") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir));
const auto [Ok2, Err2] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok2);
CHECK(Err2 == "");
}
fs::remove_all(TestDir);
}
std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) { std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
@@ -341,30 +297,10 @@ std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) {
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Remove") {
const std::string TestFileOrDir = "beammp_test_thing";
SUBCASE("Remove existing directory") {
fs::create_directory(TestFileOrDir);
const auto [Ok, Err] = LuaAPI::FS::Remove(TestFileOrDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestFileOrDir));
}
SUBCASE("Remove non-existing directory") {
fs::remove_all(TestFileOrDir);
const auto [Ok, Err] = LuaAPI::FS::Remove(TestFileOrDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestFileOrDir));
}
// TODO: add tests for files
// TODO: add tests for files and folders without access permissions (failure)
}
std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const std::string& NewPath) { std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const std::string& NewPath) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::rename(Path, NewPath, errc); fs::rename(fs::relative(Path), fs::relative(NewPath), errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -372,25 +308,10 @@ std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const s
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Rename") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
const auto [Ok, Err] = LuaAPI::FS::Rename(TestDir, OtherTestDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestDir));
CHECK(fs::exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
}
std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std::string& NewPath) { std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std::string& NewPath) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::copy(Path, NewPath, fs::copy_options::recursive, errc); fs::copy(fs::relative(Path), fs::relative(NewPath), fs::copy_options::recursive, errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -398,86 +319,30 @@ std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Copy") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
const auto [Ok, Err] = LuaAPI::FS::Copy(TestDir, OtherTestDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(fs::exists(TestDir));
CHECK(fs::exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
}
bool LuaAPI::FS::Exists(const std::string& Path) { bool LuaAPI::FS::Exists(const std::string& Path) {
return fs::exists(Path); return fs::exists(fs::relative(Path));
}
TEST_CASE("LuaAPI::FS::Exists") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
CHECK(LuaAPI::FS::Exists(TestDir));
CHECK(!LuaAPI::FS::Exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
} }
std::string LuaAPI::FS::GetFilename(const std::string& Path) { std::string LuaAPI::FS::GetFilename(const std::string& Path) {
return fs::path(Path).filename().string(); return fs::path(Path).filename().string();
} }
TEST_CASE("LuaAPI::FS::GetFilename") {
CHECK(LuaAPI::FS::GetFilename("test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("/test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("place/test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("/some/../place/test.txt") == "test.txt");
}
std::string LuaAPI::FS::GetExtension(const std::string& Path) { std::string LuaAPI::FS::GetExtension(const std::string& Path) {
return fs::path(Path).extension().string(); return fs::path(Path).extension().string();
} }
TEST_CASE("LuaAPI::FS::GetExtension") {
CHECK(LuaAPI::FS::GetExtension("test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("place/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test") == "");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.a.b.c") == ".c");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.") == ".");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.a.b.") == ".");
}
std::string LuaAPI::FS::GetParentFolder(const std::string& Path) { std::string LuaAPI::FS::GetParentFolder(const std::string& Path) {
return fs::path(Path).parent_path().string(); return fs::path(Path).parent_path().string();
} }
TEST_CASE("LuaAPI::FS::GetParentFolder") {
CHECK(LuaAPI::FS::GetParentFolder("test.txt") == "");
CHECK(LuaAPI::FS::GetParentFolder("/test.txt") == "/");
CHECK(LuaAPI::FS::GetParentFolder("place/test.txt") == "place");
CHECK(LuaAPI::FS::GetParentFolder("/some/../place/test.txt") == "/some/../place");
}
// TODO: add tests
bool LuaAPI::FS::IsDirectory(const std::string& Path) { bool LuaAPI::FS::IsDirectory(const std::string& Path) {
return fs::is_directory(Path); return fs::is_directory(Path);
} }
// TODO: add tests
bool LuaAPI::FS::IsFile(const std::string& Path) { bool LuaAPI::FS::IsFile(const std::string& Path) {
return fs::is_regular_file(Path); return fs::is_regular_file(Path);
} }
// TODO: add tests
std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) { std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
fs::path Path; fs::path Path;
for (size_t i = 0; i < Args.size(); ++i) { for (size_t i = 0; i < Args.size(); ++i) {
+30 -56
View File
@@ -27,42 +27,15 @@ static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
// HTTP // HTTP
static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled";
static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL"; static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL";
static constexpr std::string_view StrSSLKeyPath = "SSLKeyPath";
static constexpr std::string_view StrSSLCertPath = "SSLCertPath";
static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort";
static constexpr std::string_view StrHTTPServerIP = "HTTPServerIP"; static constexpr std::string_view StrHTTPServerIP = "HTTPServerIP";
TEST_CASE("TConfig::TConfig") {
const std::string CfgFile = "beammp_server_testconfig.toml";
fs::remove(CfgFile);
TConfig Cfg(CfgFile);
CHECK(fs::file_size(CfgFile) != 0);
std::string buf;
{
buf.resize(fs::file_size(CfgFile));
auto fp = std::fopen(CfgFile.c_str(), "r");
std::fread(buf.data(), 1, buf.size(), fp);
std::fclose(fp);
}
INFO("file contents are:", buf);
const auto table = toml::parse(CfgFile);
CHECK(table.at("General").is_table());
CHECK(table.at("Misc").is_table());
CHECK(table.at("HTTP").is_table());
fs::remove(CfgFile);
}
TConfig::TConfig(const std::string& ConfigFileName) TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) { : mConfigFileName(ConfigFileName) {
Application::SetSubsystemStatus("Config", Application::Status::Starting); Application::SetSubsystemStatus("Config", Application::Status::Starting);
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) { if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
beammp_info("No config file found! Generating one..."); beammp_info("No config file found! Generating one...");
CreateConfigFile(); CreateConfigFile(mConfigFileName);
} }
if (!mFailed) { if (!mFailed) {
if (fs::exists("Server.cfg")) { if (fs::exists("Server.cfg")) {
@@ -88,8 +61,7 @@ void SetComment(CommentsT& Comments, const std::string& Comment) {
* whether it is in TConfig.cpp or the configuration file. * whether it is in TConfig.cpp or the configuration file.
*/ */
void TConfig::FlushToFile() { void TConfig::FlushToFile() {
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName); auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
auto data = toml::value {};
data["General"][StrAuthKey.data()] = Application::Settings.Key; data["General"][StrAuthKey.data()] = Application::Settings.Key;
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server"); SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
data["General"][StrLogChat.data()] = Application::Settings.LogChat; data["General"][StrLogChat.data()] = Application::Settings.LogChat;
@@ -111,34 +83,16 @@ void TConfig::FlushToFile() {
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`"); SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
// HTTP // HTTP
data["HTTP"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath;
data["HTTP"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath;
data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort;
SetComment(data["HTTP"][StrHTTPServerIP.data()].comments(), " Which IP to listen on. Pick 0.0.0.0 for a public-facing server with no specific IP, and 127.0.0.1 or 'localhost' for a local server."); SetComment(data["HTTP"][StrHTTPServerIP.data()].comments(), " Which IP to listen on. Pick 0.0.0.0 for a public-facing server with no specific IP, and 127.0.0.1 or 'localhost' for a local server.");
data["HTTP"][StrHTTPServerIP.data()] = Application::Settings.HTTPServerIP; data["HTTP"][StrHTTPServerIP.data()] = Application::Settings.HTTPServerIP;
data["HTTP"][StrHTTPServerUseSSL.data()] = Application::Settings.HTTPServerUseSSL;
SetComment(data["HTTP"][StrHTTPServerUseSSL.data()].comments(), " Recommended to have enabled for servers which face the internet. With SSL the server will serve https and requires valid key and cert files");
data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled; data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled;
SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server"); SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server");
std::stringstream Ss; std::ofstream Stream(mConfigFileName, std::ios::trunc | std::ios::out);
Ss << "# This is the BeamMP-Server config file.\n" Stream << data << std::flush;
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< data;
auto File = std::fopen(mConfigFileName.c_str(), "w+");
if (!File) {
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
throw std::runtime_error("Failed to create/write to config file");
}
auto Str = Ss.str();
auto N = std::fwrite(Str.data(), sizeof(char), Str.size(), File);
if (N != Str.size()) {
beammp_error("Failed to write to config file properly, config file might be misshapen");
}
std::fclose(File);
} }
void TConfig::CreateConfigFile() { void TConfig::CreateConfigFile(std::string_view name) {
// build from old config Server.cfg // build from old config Server.cfg
try { try {
@@ -150,7 +104,32 @@ void TConfig::CreateConfigFile() {
beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what())); beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
} }
{ // create file context
std::ofstream ofs(name.data());
}
FlushToFile(); FlushToFile();
size_t FileSize = fs::file_size(name);
std::fstream ofs { std::string(name), std::ios::in | std::ios::out };
if (ofs.good()) {
std::string Contents {};
Contents.resize(FileSize);
ofs.readsome(Contents.data(), FileSize);
ofs.seekp(0);
ofs << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< '\n'
<< Contents;
beammp_error("There was no \"" + std::string(mConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
mFailed = true;
ofs.close();
} else {
beammp_error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work.");
Application::SetSubsystemStatus("Config", Application::Status::Bad);
mFailed = true;
}
} }
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue) { void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue) {
@@ -191,12 +170,9 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages); TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled);
// HTTP // HTTP
TryReadValue(data, "HTTP", StrSSLKeyPath, Application::Settings.SSLKeyPath);
TryReadValue(data, "HTTP", StrSSLCertPath, Application::Settings.SSLCertPath);
TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort); TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort);
TryReadValue(data, "HTTP", StrHTTPServerIP, Application::Settings.HTTPServerIP); TryReadValue(data, "HTTP", StrHTTPServerIP, Application::Settings.HTTPServerIP);
TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled); TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled);
TryReadValue(data, "HTTP", StrHTTPServerUseSSL, Application::Settings.HTTPServerUseSSL);
} catch (const std::exception& err) { } catch (const std::exception& err) {
beammp_error("Error parsing config file value: " + std::string(err.what())); beammp_error("Error parsing config file value: " + std::string(err.what()));
mFailed = true; mFailed = true;
@@ -231,8 +207,6 @@ void TConfig::PrintDebug() {
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\""); beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\""); beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\""); beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
beammp_debug(std::string(StrSSLKeyPath) + ": \"" + Application::Settings.SSLKeyPath + "\"");
beammp_debug(std::string(StrSSLCertPath) + ": \"" + Application::Settings.SSLCertPath + "\"");
beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\""); beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\"");
beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\""); beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\"");
// special! // special!
+1 -35
View File
@@ -14,17 +14,6 @@ static inline bool StringStartsWith(const std::string& What, const std::string&
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith; return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
} }
TEST_CASE("StringStartsWith") {
CHECK(StringStartsWith("Hello, World", "Hello"));
CHECK(StringStartsWith("Hello, World", "H"));
CHECK(StringStartsWith("Hello, World", ""));
CHECK(!StringStartsWith("Hello, World", "ello"));
CHECK(!StringStartsWith("Hello, World", "World"));
CHECK(StringStartsWith("", ""));
CHECK(!StringStartsWith("", "hello"));
}
// Trims leading and trailing spaces, newlines, tabs, etc.
static inline std::string TrimString(std::string S) { static inline std::string TrimString(std::string S) {
S.erase(S.begin(), std::find_if(S.begin(), S.end(), [](unsigned char ch) { S.erase(S.begin(), std::find_if(S.begin(), S.end(), [](unsigned char ch) {
return !std::isspace(ch); return !std::isspace(ch);
@@ -36,21 +25,6 @@ static inline std::string TrimString(std::string S) {
return S; return S;
} }
TEST_CASE("TrimString") {
CHECK(TrimString("hel lo") == "hel lo");
CHECK(TrimString(" hel lo") == "hel lo");
CHECK(TrimString(" hel lo ") == "hel lo");
CHECK(TrimString("hel lo ") == "hel lo");
CHECK(TrimString(" hel lo") == "hel lo");
CHECK(TrimString("hel lo ") == "hel lo");
CHECK(TrimString(" hel lo ") == "hel lo");
CHECK(TrimString("\t\thel\nlo\n\n") == "hel\nlo");
CHECK(TrimString("\n\thel\tlo\n\t") == "hel\tlo");
CHECK(TrimString(" ") == "");
CHECK(TrimString(" \t\n\r ") == "");
CHECK(TrimString("") == "");
}
std::string GetDate() { std::string GetDate() {
std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
time_t tt = std::chrono::system_clock::to_time_t(now); time_t tt = std::chrono::system_clock::to_time_t(now);
@@ -227,8 +201,7 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
say <message> sends the message to all players in chat say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua lua [state id] switches to lua, optionally into a specific state id's lua
settings [command] sets or gets settings for the server, run `settings help` for more info settings [command] sets or gets settings for the server, run `settings help` for more info
status how the server is doing and what it's up to status how the server is doing and what it's up to)";
clear clears the console window)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
} }
@@ -241,13 +214,6 @@ std::string TConsole::ConcatArgs(const std::vector<std::string>& args, char spac
return Result; return Result;
} }
void TConsole::Command_Clear(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0, size_t(-1))) {
return;
}
mCommandline.write("\x1b[;H\x1b[2J");
}
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) { void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 1, size_t(-1))) { if (!EnsureArgsCount(args, 1, size_t(-1))) {
return; return;
+3 -3
View File
@@ -7,7 +7,6 @@
#include <rapidjson/rapidjson.h> #include <rapidjson/rapidjson.h>
#include <sstream> #include <sstream>
namespace json = rapidjson;
void THeartbeatThread::operator()() { void THeartbeatThread::operator()() {
RegisterThread("Heartbeat"); RegisterThread("Heartbeat");
@@ -20,7 +19,7 @@ void THeartbeatThread::operator()() {
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
bool isAuth = false; bool isAuth = false;
size_t UpdateReminderCounter = 0; size_t UpdateReminderCounter = 0;
while (!Application::IsShuttingDown()) { while (!mShutdown) {
++UpdateReminderCounter; ++UpdateReminderCounter;
Body = GenerateCall(); Body = GenerateCall();
// a hot-change occurs when a setting has changed, to update the backend of that change. // a hot-change occurs when a setting has changed, to update the backend of that change.
@@ -57,7 +56,7 @@ void THeartbeatThread::operator()() {
auto Target = "/heartbeat"; auto Target = "/heartbeat";
unsigned int ResponseCode = 0; unsigned int ResponseCode = 0;
json::Document Doc; rapidjson::Document Doc;
bool Ok = false; bool Ok = false;
for (const auto& Url : Application::GetBackendUrlsInOrder()) { for (const auto& Url : Application::GetBackendUrlsInOrder()) {
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } }); T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
@@ -164,6 +163,7 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown); Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
mShutdown = true;
mThread.join(); mThread.join();
} }
Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown); Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown);
+93 -45
View File
@@ -16,29 +16,26 @@
TLuaEngine* LuaAPI::MP::Engine; TLuaEngine* LuaAPI::MP::Engine;
TLuaEngine::TLuaEngine() TLuaEngine::TLuaEngine()
: mResourceServerPath(fs::path(Application::Settings.Resource) / "Server") { : mPluginMonitor(fs::path(Application::Settings.Resource) / "Server", *this, mShutdown) {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting); Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
LuaAPI::MP::Engine = this; LuaAPI::MP::Engine = this;
if (!fs::exists(Application::Settings.Resource)) { if (!fs::exists(Application::Settings.Resource)) {
fs::create_directory(Application::Settings.Resource); fs::create_directory(Application::Settings.Resource);
} }
if (!fs::exists(mResourceServerPath)) { fs::path Path = fs::path(Application::Settings.Resource) / "Server";
fs::create_directory(mResourceServerPath); if (!fs::exists(Path)) {
fs::create_directory(Path);
} }
mResourceServerPath = Path;
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("LuaEngine", Application::Status::ShuttingDown); Application::SetSubsystemStatus("LuaEngine", Application::Status::ShuttingDown);
mShutdown = true;
if (mThread.joinable()) { if (mThread.joinable()) {
mThread.join(); mThread.join();
} }
Application::SetSubsystemStatus("LuaEngine", Application::Status::Shutdown); Application::SetSubsystemStatus("LuaEngine", Application::Status::Shutdown);
}); });
IThreaded::Start(); Start();
}
TEST_CASE("TLuaEngine ctor & dtor") {
Application::Settings.Resource = "beammp_server_test_resources";
TLuaEngine engine;
Application::GracefullyShutdown();
} }
void TLuaEngine::operator()() { void TLuaEngine::operator()() {
@@ -57,28 +54,30 @@ void TLuaEngine::operator()() {
auto ResultCheckThread = std::thread([&] { auto ResultCheckThread = std::thread([&] {
RegisterThread("ResultCheckThread"); RegisterThread("ResultCheckThread");
while (!Application::IsShuttingDown()) { while (!mShutdown) {
std::unique_lock Lock(mResultsToCheckMutex); std::unique_lock Lock(mResultsToCheckMutex);
mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
if (!mResultsToCheck.empty()) { if (!mResultsToCheck.empty()) {
mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool { mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool {
if (Ptr->Ready) { if (Ptr->Ready) {
if (Ptr->Error) { return true;
if (Ptr->ErrorMessage != BeamMPFnNotFoundError) { } else if (Ptr->Error) {
beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage); if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
} beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
} }
return true; return true;
} }
return false; return false;
}); });
} else {
mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
} }
} }
}); });
// event loop // event loop
auto Before = std::chrono::high_resolution_clock::now(); auto Before = std::chrono::high_resolution_clock::now();
while (!Application::IsShuttingDown()) { while (!mShutdown) {
if (mLuaStates.size() == 0) {
std::this_thread::sleep_for(std::chrono::seconds(100));
}
{ // Timed Events Scope { // Timed Events Scope
std::unique_lock Lock(mTimedEventsMutex); std::unique_lock Lock(mTimedEventsMutex);
for (auto& Timer : mTimedEvents) { for (auto& Timer : mTimedEvents) {
@@ -104,18 +103,12 @@ void TLuaEngine::operator()() {
} }
} }
} }
if (mLuaStates.size() == 0) { const auto Expected = std::chrono::milliseconds(10);
beammp_trace("No Lua states, event loop running extremely sparsely"); if (auto Diff = std::chrono::high_resolution_clock::now() - Before;
Application::SleepSafeSeconds(10); Diff < Expected) {
std::this_thread::sleep_for(Expected - Diff);
} else { } else {
constexpr double NsFactor = 1000000.0; beammp_trace("Event loop cannot keep up! Running " + std::to_string(Diff.count()) + "s behind");
constexpr double Expected = 10.0; // ms
const auto Diff = (std::chrono::high_resolution_clock::now() - Before).count() / NsFactor;
if (Diff < Expected) {
std::this_thread::sleep_for(std::chrono::nanoseconds(size_t((Expected - Diff) * NsFactor)));
} else {
beammp_tracef("Event loop cannot keep up! Running {}ms behind", Diff);
}
} }
Before = std::chrono::high_resolution_clock::now(); Before = std::chrono::high_resolution_clock::now();
} }
@@ -162,7 +155,7 @@ void TLuaEngine::AddResultToCheck(const std::shared_ptr<TLuaResult>& Result) {
mResultsToCheckCond.notify_one(); mResultsToCheckCond.notify_one();
} }
std::unordered_map<std::string /* event name */, std::vector<std::string> /* handlers */> TLuaEngine::Debug_GetEventsForState(TLuaStateId StateId) { std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> TLuaEngine::Debug_GetEventsForState(TLuaStateId StateId) {
std::unordered_map<std::string, std::vector<std::string>> Result; std::unordered_map<std::string, std::vector<std::string>> Result;
std::unique_lock Lock(mLuaEventsMutex); std::unique_lock Lock(mLuaEventsMutex);
for (const auto& EventNameToEventMap : mLuaEvents) { for (const auto& EventNameToEventMap : mLuaEvents) {
@@ -276,9 +269,6 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID,
} }
void TLuaEngine::CollectAndInitPlugins() { void TLuaEngine::CollectAndInitPlugins() {
if (!fs::exists(mResourceServerPath)) {
fs::create_directories(mResourceServerPath);
}
for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) { for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) {
auto Path = Dir.path(); auto Path = Dir.path();
Path = fs::relative(Path); Path = fs::relative(Path);
@@ -328,7 +318,7 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
std::unique_lock Lock(mLuaStatesMutex); std::unique_lock Lock(mLuaStatesMutex);
if (mLuaStates.find(StateId) == mLuaStates.end()) { if (mLuaStates.find(StateId) == mLuaStates.end()) {
beammp_debug("Creating lua state for state id \"" + StateId + "\""); beammp_debug("Creating lua state for state id \"" + StateId + "\"");
auto DataPtr = std::make_unique<StateThreadData>(Name, StateId, *this); auto DataPtr = std::make_unique<StateThreadData>(Name, mShutdown, StateId, *this);
mLuaStates[StateId] = std::move(DataPtr); mLuaStates[StateId] = std::move(DataPtr);
RegisterEvent("onInit", StateId, "onInit"); RegisterEvent("onInit", StateId, "onInit");
if (!DontCallOnInit) { if (!DontCallOnInit) {
@@ -616,8 +606,9 @@ sol::table TLuaEngine::StateThreadData::Lua_JsonDecode(const std::string& str) {
return table; return table;
} }
TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine) TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine)
: mName(Name) : mName(Name)
, mShutdown(Shutdown)
, mStateId(StateId) , mStateId(StateId)
, mState(luaL_newstate()) , mState(luaL_newstate())
, mEngine(&Engine) { , mEngine(&Engine) {
@@ -726,15 +717,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten); UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten);
UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify); UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify);
UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify); UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify);
UtilTable.set_function("Stress", [](size_t n) {
std::vector<std::thread> s;
for (size_t i = 0; i < n; ++i) {
s.emplace_back([]{ while (true); });
}
for (auto& t : s) {
t.detach();
}
});
UtilTable.set_function("Random", [this] { UtilTable.set_function("Random", [this] {
return mUniformRealDistribution01(mMersenneTwister); return mUniformRealDistribution01(mMersenneTwister);
}); });
@@ -829,7 +811,7 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
void TLuaEngine::StateThreadData::operator()() { void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId); RegisterThread("Lua:" + mStateId);
while (!Application::IsShuttingDown()) { while (!mShutdown) {
{ // StateExecuteQueue Scope { // StateExecuteQueue Scope
std::unique_lock Lock(mStateExecuteQueueMutex); std::unique_lock Lock(mStateExecuteQueueMutex);
if (!mStateExecuteQueue.empty()) { if (!mStateExecuteQueue.empty()) {
@@ -999,3 +981,69 @@ bool TLuaEngine::TimedEvent::Expired() {
void TLuaEngine::TimedEvent::Reset() { void TLuaEngine::TimedEvent::Reset() {
LastCompletion = std::chrono::high_resolution_clock::now(); LastCompletion = std::chrono::high_resolution_clock::now();
} }
TPluginMonitor::TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown)
: mEngine(Engine)
, mPath(Path)
, mShutdown(Shutdown) {
if (!fs::exists(mPath)) {
fs::create_directories(mPath);
}
for (const auto& Entry : fs::recursive_directory_iterator(mPath)) {
// TODO: trigger an event when a subfolder file changes
if (Entry.is_regular_file()) {
mFileTimes[Entry.path().string()] = fs::last_write_time(Entry.path());
}
}
Start();
}
void TPluginMonitor::operator()() {
RegisterThread("PluginMonitor");
beammp_info("PluginMonitor started");
while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::vector<std::string> ToRemove;
for (const auto& Pair : mFileTimes) {
try {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime != Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_info("File \"" + Pair.first + "\" changed, reloading");
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine.EnqueueScript(StateID, Chunk);
// TODO: call onInit
mEngine.AddResultToCheck(Res);
} else {
// TODO: trigger onFileChanged event
beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet");
/*
// is in subfolder, dont reload, just trigger an event
auto Results = mEngine.TriggerEvent("onFileChanged", "", Pair.first);
mEngine.WaitForAll(Results);
for (const auto& Result : Results) {
if (Result->Error) {
beammp_lua_error(Result->ErrorMessage);
}
}*/
}
}
} catch (const std::exception& e) {
ToRemove.push_back(Pair.first);
}
}
for (const auto& File : ToRemove) {
mFileTimes.erase(File);
beammp_warn("file '" + File + "' couldn't be accessed, so it was removed from plugin hot reload monitor (probably got deleted)");
}
}
}
+19 -78
View File
@@ -25,6 +25,7 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown); Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
if (mUDPThread.joinable()) { if (mUDPThread.joinable()) {
mShutdown = true;
mUDPThread.detach(); mUDPThread.detach();
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown);
@@ -32,6 +33,7 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown); Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown);
if (mTCPThread.joinable()) { if (mTCPThread.joinable()) {
mShutdown = true;
mTCPThread.detach(); mTCPThread.detach();
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown); Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown);
@@ -66,7 +68,7 @@ void TNetwork::UDPServerMain() {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ") beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients")); + std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
while (!Application::IsShuttingDown()) { while (!mShutdown) {
try { try {
sockaddr_in client {}; sockaddr_in client {};
std::string Data = UDPRcvFromClient(client); // Receives any data from Socket std::string Data = UDPRcvFromClient(client); // Receives any data from Socket
@@ -150,7 +152,7 @@ void TNetwork::TCPServerMain() {
beammp_info("Vehicle event network online"); beammp_info("Vehicle event network online");
do { do {
try { try {
if (Application::IsShuttingDown()) { if (mShutdown) {
beammp_debug("shutdown during TCP wait for accept loop"); beammp_debug("shutdown during TCP wait for accept loop");
break; break;
} }
@@ -192,7 +194,6 @@ void TNetwork::TCPServerMain() {
#undef GetObject // Fixes Windows #undef GetObject // Fixes Windows
#include "Json.h" #include "Json.h"
namespace json = rapidjson;
void TNetwork::Identify(const TConnection& client) { void TNetwork::Identify(const TConnection& client) {
RegisterThreadAuto(); RegisterThreadAuto();
@@ -237,26 +238,13 @@ void TNetwork::HandleDownload(SOCKET TCPSock) {
}); });
} }
static int get_ip_str(const struct sockaddr* sa, char* strBuf, size_t strBufSize) {
switch (sa->sa_family) {
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), strBuf, strBufSize);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), strBuf, strBufSize);
break;
default:
return 1;
}
return 0;
}
void TNetwork::Authentication(const TConnection& ClientConnection) { void TNetwork::Authentication(const TConnection& ClientConnection) {
auto Client = CreateClient(ClientConnection.Socket); auto Client = CreateClient(ClientConnection.Socket);
char AddrBuf[INET6_ADDRSTRLEN]; char AddrBuf[64];
get_ip_str(&ClientConnection.SockAddr, AddrBuf, sizeof(AddrBuf)); // TODO: IPv6 would need this to be changed
beammp_trace("This thread is ip " + std::string(AddrBuf)); auto str = inet_ntop(AF_INET, reinterpret_cast<const void*>(&ClientConnection.SockAddr), AddrBuf, sizeof(ClientConnection.SockAddr));
Client->SetIdentifier("ip", AddrBuf); beammp_trace("This thread is ip " + std::string(str));
Client->SetIdentifier("ip", str);
std::string Rc; // TODO: figure out why this is not default constructed std::string Rc; // TODO: figure out why this is not default constructed
beammp_info("Identifying new ClientConnection..."); beammp_info("Identifying new ClientConnection...");
@@ -292,7 +280,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, RequestString, "application/json", &ResponseCode); Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, RequestString, "application/json", &ResponseCode);
} }
json::Document AuthResponse; rapidjson::Document AuthResponse;
AuthResponse.Parse(Rc.c_str()); AuthResponse.Parse(Rc.c_str());
if (Rc == Http::ErrorString || AuthResponse.HasParseError()) { if (Rc == Http::ErrorString || AuthResponse.HasParseError()) {
ClientKick(*Client, "Invalid key! Please restart your game."); ClientKick(*Client, "Invalid key! Please restart your game.");
@@ -772,63 +760,14 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
} }
} }
static std::pair<size_t /* count */, size_t /* last chunk */> SplitIntoChunks(size_t FullSize, size_t ChunkSize) {
if (FullSize < ChunkSize) {
return { 0, FullSize };
}
size_t Count = FullSize / (FullSize / ChunkSize);
size_t LastChunkSize = FullSize - (Count * ChunkSize);
return { Count, LastChunkSize };
}
TEST_CASE("SplitIntoChunks") {
size_t FullSize;
size_t ChunkSize;
SUBCASE("Normal case") {
FullSize = 1234567;
ChunkSize = 1234;
}
SUBCASE("Zero original size") {
FullSize = 0;
ChunkSize = 100;
}
SUBCASE("Equal full size and chunk size") {
FullSize = 125;
ChunkSize = 125;
}
SUBCASE("Even split") {
FullSize = 10000;
ChunkSize = 100;
}
SUBCASE("Odd split") {
FullSize = 13;
ChunkSize = 2;
}
SUBCASE("Large sizes") {
FullSize = 10 * GB;
ChunkSize = 125 * MB;
}
auto [Count, LastSize] = SplitIntoChunks(FullSize, ChunkSize);
CHECK((Count * ChunkSize) + LastSize == FullSize);
}
uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, SOCKET Socket, uint8_t* DataPtr, size_t Size) {
if (TCPSendRaw(c, Socket, reinterpret_cast<char*>(DataPtr), Size)) {
return DataPtr + Size;
} else {
return nullptr;
}
}
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) { void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) {
std::ifstream f(Name.c_str(), std::ios::binary); std::ifstream f(Name.c_str(), std::ios::binary);
auto Buf = f.rdbuf();
uint32_t Split = 125 * MB; uint32_t Split = 125 * MB;
std::vector<uint8_t> Data; char* Data;
if (Size > Split) if (Size > Split)
Data.resize(Split); Data = new char[Split];
else else
Data.resize(Size); Data = new char[Size];
SOCKET TCPSock; SOCKET TCPSock;
if (D) if (D)
TCPSock = c.GetDownSock(); TCPSock = c.GetDownSock();
@@ -839,8 +778,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
size_t Diff = Size - Sent; size_t Diff = Size - Sent;
if (Diff > Split) { if (Diff > Split) {
f.seekg(Sent, std::ios_base::beg); f.seekg(Sent, std::ios_base::beg);
f.read(reinterpret_cast<char*>(Data.data()), Split); f.read(Data, Split);
if (!TCPSendRaw(c, TCPSock, reinterpret_cast<char*>(Data.data()), Split)) { if (!TCPSendRaw(c, TCPSock, Data, Split)) {
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
break; break;
@@ -848,8 +787,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Split; Sent += Split;
} else { } else {
f.seekg(Sent, std::ios_base::beg); f.seekg(Sent, std::ios_base::beg);
f.read(reinterpret_cast<char*>(Data.data()), Diff); f.read(Data, Diff);
if (!TCPSendRaw(c, TCPSock, reinterpret_cast<char*>(Data.data()), int32_t(Diff))) { if (!TCPSendRaw(c, TCPSock, Data, int32_t(Diff))) {
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
break; break;
@@ -857,6 +796,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Diff; Sent += Diff;
} }
} }
delete[] Data;
f.close();
} }
bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) { bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) {
+2 -1
View File
@@ -10,6 +10,7 @@ TPPSMonitor::TPPSMonitor(TServer& Server)
Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown); Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
beammp_debug("shutting down PPSMonitor"); beammp_debug("shutting down PPSMonitor");
mShutdown = true;
mThread.join(); mThread.join();
beammp_debug("shut down PPSMonitor"); beammp_debug("shut down PPSMonitor");
} }
@@ -26,7 +27,7 @@ void TPPSMonitor::operator()() {
beammp_debug("PPSMonitor starting"); beammp_debug("PPSMonitor starting");
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good); Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good);
std::vector<std::shared_ptr<TClient>> TimedOutClients; std::vector<std::shared_ptr<TClient>> TimedOutClients;
while (!Application::IsShuttingDown()) { while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
int C = 0, V = 0; int C = 0, V = 0;
if (mServer.ClientCount() == 0) { if (mServer.ClientCount() == 0) {
-75
View File
@@ -1,75 +0,0 @@
#include "TPluginMonitor.h"
#include "TLuaEngine.h"
TPluginMonitor::TPluginMonitor(const fs::path& Path, std::shared_ptr<TLuaEngine> Engine)
: mEngine(Engine)
, mPath(Path) {
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Starting);
if (!fs::exists(mPath)) {
fs::create_directories(mPath);
}
for (const auto& Entry : fs::recursive_directory_iterator(mPath)) {
// TODO: trigger an event when a subfolder file changes
if (Entry.is_regular_file()) {
mFileTimes[Entry.path().string()] = fs::last_write_time(Entry.path());
}
}
Application::RegisterShutdownHandler([this] {
if (mThread.joinable()) {
mThread.join();
}
});
Start();
}
void TPluginMonitor::operator()() {
RegisterThread("PluginMonitor");
beammp_info("PluginMonitor started");
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Good);
while (!Application::IsShuttingDown()) {
std::vector<std::string> ToRemove;
for (const auto& Pair : mFileTimes) {
try {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime > Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_infof("File \"{}\" changed, reloading", Pair.first);
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine->EnqueueScript(StateID, Chunk);
Res->WaitUntilReady();
if (Res->Error) {
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
} else {
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
}
} else {
// is in subfolder, dont reload, just trigger an event
beammp_debugf("File \"{}\" changed, not reloading because it's in a subdirectory. Triggering 'onFileChanged' event instead", Pair.first);
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
}
}
} catch (const std::exception& e) {
ToRemove.push_back(Pair.first);
}
}
Application::SleepSafeSeconds(3);
for (const auto& File : ToRemove) {
mFileTimes.erase(File);
beammp_warnf("File \"{}\" couldn't be accessed, so it was removed from plugin hot reload monitor (probably got deleted)", File);
}
}
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Shutdown);
}
+1 -2
View File
@@ -28,9 +28,8 @@ TResourceManager::TResourceManager() {
} }
} }
if (mModsLoaded) { if (mModsLoaded)
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods"); beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
}
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
} }
+18 -2
View File
@@ -1,14 +1,30 @@
#include "VehicleData.h" #include "VehicleData.h"
#include "Common.h" #include "Common.h"
#include <regex>
#include <utility> #include <utility>
TVehicleData::TVehicleData(int ID, std::string Data) TVehicleData::TVehicleData(int ID, const std::string& Data)
: mID(ID) : mID(ID)
, mData(std::move(Data)) { , mData(Data) {
try {
std::regex Reg(R"(^[a-zA-Z0-9_\-.]+:[a-zA-Z0-9_\-.]+:[a-zA-Z0-9_\-.]+:\d+\-\d+:(\{.+\}))", std::regex::ECMAScript);
std::smatch Match;
std::string Result;
if (std::regex_search(Data, Match, Reg) && Match.size() > 1) {
Result = Match.str(1);
mJson = json::parse(Result);
}
} catch (const std::exception& e) {
beammp_debugf("Failed to parse vehicle data for vehicle {} as json, this isn't expected: {}", ID, e.what());
}
beammp_trace("vehicle " + std::to_string(mID) + " constructed"); beammp_trace("vehicle " + std::to_string(mID) + " constructed");
} }
TVehicleData::~TVehicleData() { TVehicleData::~TVehicleData() {
beammp_trace("vehicle " + std::to_string(mID) + " destroyed"); beammp_trace("vehicle " + std::to_string(mID) + " destroyed");
} }
const json& TVehicleData::Json() const {
return mJson;
}
+15 -10
View File
@@ -11,14 +11,13 @@
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include "TNetwork.h" #include "TNetwork.h"
#include "TPPSMonitor.h" #include "TPPSMonitor.h"
#include "TPluginMonitor.h"
#include "TResourceManager.h" #include "TResourceManager.h"
#include "TScopedTimer.h" #include "TScopedTimer.h"
#include "TServer.h" #include "TServer.h"
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
static const std::string sCommandlineArguments = R"( static const std::string sCommandlineArguments = R"(
USAGE: USAGE:
BeamMP-Server [arguments] BeamMP-Server [arguments]
@@ -45,6 +44,10 @@ EXAMPLES:
'MyWestCoastServerConfig.toml'. 'MyWestCoastServerConfig.toml'.
)"; )";
// this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
struct MainArguments { struct MainArguments {
int argc {}; int argc {};
char** argv {}; char** argv {};
@@ -69,7 +72,7 @@ int main(int argc, char** argv) {
Sentry.LogException(e, _file_basename, _line); Sentry.LogException(e, _file_basename, _line);
MainRet = -1; MainRet = -1;
} }
std::exit(MainRet); return MainRet;
} }
int BeamMPServerMain(MainArguments Arguments) { int BeamMPServerMain(MainArguments Arguments) {
@@ -134,9 +137,9 @@ int BeamMPServerMain(MainArguments Arguments) {
TServer Server(Arguments.List); TServer Server(Arguments.List);
TConfig Config(ConfigPath); TConfig Config(ConfigPath);
auto LuaEngine = std::make_shared<TLuaEngine>(); TLuaEngine LuaEngine;
LuaEngine->SetServer(&Server); LuaEngine.SetServer(&Server);
Application::Console().InitializeLuaConsole(*LuaEngine); Application::Console().InitializeLuaConsole(LuaEngine);
if (Config.Failed()) { if (Config.Failed()) {
beammp_info("Closing in 10 seconds"); beammp_info("Closing in 10 seconds");
@@ -156,14 +159,16 @@ int BeamMPServerMain(MainArguments Arguments) {
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);
LuaEngine->SetNetwork(&Network); LuaEngine.SetNetwork(&Network);
PPSMonitor.SetNetwork(Network); PPSMonitor.SetNetwork(Network);
Application::CheckForUpdates(); Application::CheckForUpdates();
TPluginMonitor PluginMonitor(fs::path(Application::Settings.Resource) / "Server", LuaEngine); std::unique_ptr<Http::Server::THttpServerInstance> HttpServer;
if (Application::Settings.HTTPServerEnabled) { if (Application::Settings.HTTPServerEnabled) {
Http::Server::THttpServerInstance HttpServerInstance {}; HttpServer = std::make_unique<Http::Server::THttpServerInstance>(
Server,
Network,
ResourceManager);
} }
Application::SetSubsystemStatus("Main", Application::Status::Good); Application::SetSubsystemStatus("Main", Application::Status::Good);
-22
View File
@@ -1,22 +0,0 @@
#define DOCTEST_CONFIG_IMPLEMENT
#include <doctest/doctest.h>
#include <Common.h>
int main(int argc, char** argv) {
doctest::Context context;
// Application::InitializeConsole();
context.applyCommandLine(argc, argv);
int res = context.run(); // run
if (context.shouldExit()) // important - query flags (and --exit) rely on the user doing this
return res; // propagate the result of the tests
int client_stuff_return_code = 0;
// your program - if the testing framework is integrated in your production code
return res + client_stuff_return_code; // the result from doctest is propagated here as well
}
+18
View File
@@ -0,0 +1,18 @@
include(FetchContent)
message(STATUS "Getting, checking and running vcpkg, this may take a while")
FetchContent_Declare(
vcpkg
GIT_REPOSITORY https://github.com/microsoft/vcpkg.git
)
FetchContent_GetProperties(vcpkg)
if(NOT vcpkg_POPULATED)
FetchContent_Populate(vcpkg)
execute_process(COMMAND ./${vcpkg_SOURCE_DIR}/bootstrap-vcpkg.sh)
endif()
set(CMAKE_TOOLCHAIN_FILE ${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake
CACHE STRING "Vcpkg toolchain file")
+17
View File
@@ -0,0 +1,17 @@
{
"name": "beammp-server",
"version-string": "3.0.2",
"homepage": "https://beammp.com",
"dependencies": [
"sentry-native",
"lua",
"zlib",
"rapidjson",
"nlohmann-json",
"openssl",
"curl",
"sol2",
"cpp-httplib",
"toml11"
]
}