Compare commits

..

1 Commits

Author SHA1 Message Date
Lion Kortlepel fd12ee672d Add various debug functions 2022-01-26 20:33:12 +01:00
25 changed files with 330 additions and 441 deletions
+1 -12
View File
@@ -20,7 +20,7 @@ jobs:
with:
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment
@@ -44,15 +44,4 @@ jobs:
name: BeamMP-Server.exe
path: ${{github.workspace}}/build-windows/Release/BeamMP-Server.exe
- name: Build debug
working-directory: ${{github.workspace}}/build-windows
shell: bash
run: |
cmake --build . --config Debug
- name: Archive debug artifacts
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-debug.exe
path: ${{github.workspace}}/build-windows/Debug/BeamMP-Server.exe
+1 -1
View File
@@ -85,7 +85,7 @@ jobs:
with:
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment
+1 -4
View File
@@ -21,7 +21,4 @@
url = https://github.com/nih-at/libzip
[submodule "deps/cpp-httplib"]
path = deps/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json
url = https://github.com/yhirose/cpp-httplib
+16 -25
View File
@@ -8,15 +8,12 @@ project(BeamMP-Server
HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C)
set(HTTPLIB_REQUIRE_OPENSSL ON)
include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib")
include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include")
include_directories("${PROJECT_SOURCE_DIR}/deps")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
@@ -37,33 +34,13 @@ if (WIN32)
endif()
include_directories("include/sentry-native/include")
set(BUILD_SHARED_LIBS OFF)
set(SENTRY_BUILD_SHARED_LIBS OFF)
if (MSVC)
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif()
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \
This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.")
set(BEAMMP_SECRET_SENTRY_URL "")
set(SENTRY_BACKEND none)
else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN)
message(STATUS "Sentry URL is length ${URL_LEN}")
set(SENTRY_BACKEND breakpad)
endif()
set(SENTRY_BACKEND breakpad)
add_subdirectory("deps/sentry-native")
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif ()
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
message(STATUS "Setting compiler flags")
if (WIN32)
@@ -81,7 +58,21 @@ elseif (UNIX)
endif (SANITIZE)
endif ()
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \
This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.")
set(BEAMMP_SECRET_SENTRY_URL "")
else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN)
message(STATUS "Sentry URL is length ${URL_LEN}")
endif()
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
set(CMAKE_CXX_STANDARD 17)
-21
View File
@@ -1,24 +1,3 @@
# v3.0.2
- ADDED Periodic update message if a new server is released
- ADDED Config setting for the IP the http server listens on
- CHANGED Default MaxPlayers to 8
- CHANGED Default http server listen IP to localhost
- 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 Lua result queue handling not checking all results
- FIXED bug which caused ServerConfig.toml to generate incorrectly
# v3.0.1
- ADDED Backup URLs to UpdateCheck (will fail less often now)
- ADDED console cursor left and right movement (with arrow keys) and working HOME and END key (via github.com/lionkor/commandline)
- FIXED infinite snowmen / infinite unicycle spawning bug
- FIXED a bug where, when run with --working-directory, the Server.log would still be in the original directory
- FIXED a bug which could cause the plugin reload thread to spin at 100% if the reloaded plugin's didn't terminate
- FIXED an issue which would cause servers to crash on mod download via SIGPIPE on POSIX
- FIXED an issue which would cause servers to crash when checking if a vehicle is a unicycle
# v3.0.0
- CHANGED entire plugin Lua implementation (rewrite)
Vendored
-1
Submodule deps/json deleted from eb21824147
Vendored
+1 -1
+1 -1
View File
@@ -92,7 +92,7 @@ private:
std::queue<std::string> mPacketsSync;
std::unordered_map<std::string, std::string> mIdentifiers;
bool mIsGuest = false;
mutable std::mutex mVehicleDataMutex;
std::mutex mVehicleDataMutex;
TSetOfVehicleData mVehicleData;
std::string mName = "Unknown Client";
SOCKET mSocket[2] { SOCKET(0), SOCKET(0) };
+6 -14
View File
@@ -7,7 +7,6 @@ extern TSentry Sentry;
#include <atomic>
#include <cstring>
#include <deque>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
@@ -44,7 +43,7 @@ public:
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { false };
int MaxPlayers { 8 };
int MaxPlayers { 10 };
bool Private { true };
int MaxCars { 1 };
bool DebugModeEnabled { false };
@@ -53,9 +52,7 @@ public:
bool SendErrors { true };
bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 };
std::string HTTPServerIP { "127.0.0.1" };
bool HTTPServerUseSSL { false };
bool HideUpdateMessages { false };
bool HTTPServerUseSSL { true };
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
};
@@ -77,15 +74,10 @@ public:
static TSettings Settings;
static std::vector<std::string> GetBackendUrlsInOrder() {
return {
"backend.beammp.com",
"backup1.beammp.com",
"backup2.beammp.com"
};
}
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendHostname() { return "backend.beammp.com"; }
static std::string GetBackup1Hostname() { return "backup1.beammp.com"; }
static std::string GetBackup2Hostname() { return "backup2.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
@@ -122,7 +114,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 0, 2 };
static inline Version mVersion { 3, 0, 0 };
};
std::string ThreadName(bool DebugModeOverride = false);
+1 -1
View File
@@ -25,7 +25,7 @@ constexpr size_t RSA_DEFAULT_KEYLENGTH { 2048 };
namespace Http {
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);
namespace Status {
std::string ToString(int code);
}
+1 -3
View File
@@ -15,13 +15,13 @@ public:
void WriteRaw(const std::string& str);
void InitializeLuaConsole(TLuaEngine& Engine);
void BackupOldLog();
void StartLoggingToFile();
Commandline& Internal() { return mCommandline; }
private:
void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false);
void ChangeToLuaConsole(const std::string& LuaStateId);
void ChangeToRegularConsole();
void HandleLuaInternalCommand(const std::string& cmd);
void Command_Lua(const std::string& cmd);
void Command_Help(const std::string& cmd);
@@ -38,6 +38,4 @@ private:
bool mFirstTime { true };
std::string mStateId;
const std::string mDefaultStateId = "BEAMMP_SERVER_CONSOLE";
std::ofstream mLogFileStream;
std::mutex mLogFileStreamMtx;
};
+17 -23
View File
@@ -6,7 +6,6 @@
#include <condition_variable>
#include <filesystem>
#include <initializer_list>
#include <list>
#include <lua.hpp>
#include <memory>
#include <mutex>
@@ -33,8 +32,8 @@ static constexpr size_t TLuaArgTypes_Bool = 3;
class TLuaPlugin;
struct TLuaResult {
std::atomic_bool Ready;
std::atomic_bool Error;
bool Ready;
bool Error;
std::string ErrorMessage;
sol::object Result { sol::lua_nil };
TLuaStateId StateId;
@@ -73,18 +72,6 @@ private:
class TLuaEngine : IThreaded {
public:
enum CallStrategy : int {
BestEffort,
Precise,
};
struct QueuedFunction {
std::string FunctionName;
std::shared_ptr<TLuaResult> Result;
std::vector<TLuaArgTypes> Args;
std::string EventName; // optional, may be empty
};
TLuaEngine();
~TLuaEngine() noexcept {
beammp_debug("Lua Engine terminated");
@@ -102,6 +89,7 @@ public:
std::unique_lock Lock(mResultsToCheckMutex);
return mResultsToCheck.size();
}
size_t GetLuaStateCount() {
std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.size();
@@ -158,14 +146,19 @@ public:
return Results; //
}
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);
void CancelEventTimers(const std::string& EventName, TLuaStateId StateId);
sol::state_view GetStateForPlugin(const fs::path& PluginPath);
TLuaStateId GetStateIDForPlugin(const fs::path& PluginPath);
void AddResultToCheck(const std::shared_ptr<TLuaResult>& Result);
static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND";
// Debugging functions (slow)
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
private:
void CollectAndInitPlugins();
void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
@@ -179,12 +172,15 @@ private:
~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy);
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
void AddPath(const fs::path& Path); // to be added to path and cpath
void operator()() override;
sol::state_view State() { return sol::state_view(mState); }
// Debug functions, slow
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueue();
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> Debug_GetStateFunctionQueue();
private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
@@ -202,7 +198,7 @@ private:
std::thread mThread;
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> mStateExecuteQueue;
std::recursive_mutex mStateExecuteQueueMutex;
std::vector<QueuedFunction> mStateFunctionQueue;
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> mStateFunctionQueue;
std::mutex mStateFunctionQueueMutex;
std::condition_variable mStateFunctionQueueCond;
TLuaEngine* mEngine;
@@ -216,7 +212,6 @@ private:
std::chrono::high_resolution_clock::time_point LastCompletion {};
std::string EventName;
TLuaStateId StateId;
CallStrategy Strategy;
bool Expired();
void Reset();
};
@@ -233,9 +228,8 @@ private:
std::recursive_mutex mLuaEventsMutex;
std::vector<TimedEvent> mTimedEvents;
std::recursive_mutex mTimedEventsMutex;
std::list<std::shared_ptr<TLuaResult>> mResultsToCheck;
std::mutex mResultsToCheckMutex;
std::condition_variable mResultsToCheckCond;
std::queue<std::shared_ptr<TLuaResult>> mResultsToCheck;
std::recursive_mutex mResultsToCheckMutex;
};
// std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
-1
View File
@@ -27,7 +27,6 @@ void TClient::ClearCars() {
int TClient::GetOpenCarID() const {
int OpenID = 0;
bool found;
std::unique_lock lock(mVehicleDataMutex);
do {
found = true;
for (auto& v : mVehicleData) {
+17 -31
View File
@@ -97,42 +97,28 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status
void Application::CheckForUpdates() {
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting);
static bool FirstTime = true;
// checks current version against latest version
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
for (const auto& url : GetBackendUrlsInOrder()) {
auto Response = Http::GET(GetBackendUrlsInOrder().at(0), 443, "/v/s");
bool Matches = std::regex_match(Response, VersionRegex);
if (Matches) {
auto MyVersion = ServerVersion();
auto RemoteVersion = Version(VersionStrToInts(Response));
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = RemoteVersion.AsString();
beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION IS OUT! Please update to the new version (v" + RealVersionString + ") of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server" + std::string(ANSI_RESET));
} else {
if (FirstTime) {
beammp_info("Server up-to-date!");
}
}
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good);
break;
auto Response = Http::GET(GetBackendHostname(), 443, "/v/s");
bool Matches = std::regex_match(Response, VersionRegex);
if (Matches) {
auto MyVersion = ServerVersion();
auto RemoteVersion = Version(VersionStrToInts(Response));
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = RemoteVersion.AsString();
beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET));
} else {
if (FirstTime) {
beammp_debug("Failed to fetch version from: " + url);
beammp_trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad);
}
beammp_info("Server up-to-date!");
}
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good);
} else {
beammp_warn("Unable to fetch version from backend.");
beammp_trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad);
}
if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) {
if (FirstTime) {
beammp_warn("Unable to fetch version info from backend.");
}
}
FirstTime = false;
}
// thread name stuff
+4 -16
View File
@@ -4,7 +4,6 @@
#include "Common.h"
#include "CustomAssert.h"
#include "LuaAPI.h"
#include "httplib.h"
#include <map>
#include <random>
@@ -34,20 +33,17 @@ std::string Http::GET(const std::string& host, int port, const std::string& targ
}
}
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status) {
httplib::SSLClient client(host, port);
client.set_read_timeout(std::chrono::seconds(10));
beammp_assert(client.is_valid());
client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET);
auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
auto res = client.Post(target.c_str(), body.c_str(), body.size(), ContentType.c_str());
if (res) {
if (status) {
*status = res->status;
}
return res->body;
} else {
beammp_debug("POST failed: " + httplib::to_string(res.error()));
return Http::ErrorString;
}
}
@@ -293,7 +289,7 @@ Http::Server::THttpServerInstance::THttpServerInstance() {
mThread.detach();
}
void Http::Server::THttpServerInstance::operator()() try {
void Http::Server::THttpServerInstance::operator()() {
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort));
std::unique_ptr<httplib::Server> HttpLibServerInstance;
if (Application::Settings.HTTPServerUseSSL) {
@@ -370,14 +366,6 @@ void Http::Server::THttpServerInstance::operator()() try {
HttpLibServerInstance->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) {
res.set_content(std::string(Magic), "text/plain");
});
HttpLibServerInstance->set_logger([](const httplib::Request& Req, const httplib::Response& Res) {
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
});
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
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.");
}
} catch (const std::exception& e) {
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
HttpLibServerInstance->listen("0.0.0.0", Application::Settings.HTTPServerPort);
}
+1 -2
View File
@@ -112,8 +112,7 @@ bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName,
}
auto c = MaybeClient.value().lock();
if (!Engine->Network().Respond(*c, Packet, true)) {
beammp_lua_error("Respond failed, dropping client " + std::to_string(PlayerID));
Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
beammp_lua_error("Respond failed");
return false;
}
}
+38 -41
View File
@@ -17,19 +17,15 @@ static constexpr std::string_view StrName = "Name";
static constexpr std::string_view StrDescription = "Description";
static constexpr std::string_view StrResourceFolder = "ResourceFolder";
static constexpr std::string_view StrAuthKey = "AuthKey";
// Misc
static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
// HTTP
static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled";
static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL";
// HTTP
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 StrHTTPServerIP = "HTTPServerIP";
TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) {
@@ -62,8 +58,8 @@ void SetComment(CommentsT& Comments, const std::string& Comment) {
* whether it is in TConfig.cpp or the configuration file.
*/
void TConfig::FlushToFile() {
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
auto data = toml::value {};
auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
data["General"] = toml::table();
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");
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
@@ -75,39 +71,19 @@ void TConfig::FlushToFile() {
data["General"][StrMap.data()] = Application::Settings.MapName;
data["General"][StrDescription.data()] = Application::Settings.ServerDesc;
data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
// Misc
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages;
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors;
SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
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`");
// HTTP
data["General"][StrSendErrors.data()] = Application::Settings.SendErrors;
SetComment(data["General"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
SetComment(data["General"][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`");
data["HTTP"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath;
data["HTTP"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath;
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.");
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");
SetComment(data["HTTP"][StrHTTPServerUseSSL.data()].comments(), " Recommended to keep enabled. With SSL the server will serve https and requires valid key and cert files");
data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled;
SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server");
std::stringstream Ss;
Ss << "# 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"
<< 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);
std::ofstream Stream(mConfigFileName);
Stream << data << std::flush;
}
void TConfig::CreateConfigFile(std::string_view name) {
@@ -122,7 +98,32 @@ void TConfig::CreateConfigFile(std::string_view name) {
beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
}
{ // create file context
std::ofstream ofs(name.data());
}
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) {
@@ -157,15 +158,12 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc);
TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource);
TryReadValue(data, "General", StrAuthKey, Application::Settings.Key);
// Misc
TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors);
TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled);
TryReadValue(data, "General", StrSendErrors, Application::Settings.SendErrors);
TryReadValue(data, "General", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled);
// 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", StrHTTPServerIP, Application::Settings.HTTPServerIP);
TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled);
TryReadValue(data, "HTTP", StrHTTPServerUseSSL, Application::Settings.HTTPServerUseSSL);
} catch (const std::exception& err) {
@@ -204,7 +202,6 @@ void TConfig::PrintDebug() {
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(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\"");
// special!
beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
}
+63 -18
View File
@@ -97,17 +97,6 @@ void TConsole::BackupOldLog() {
}
}
void TConsole::StartLoggingToFile() {
mLogFileStream.open("Server.log");
Application::Console().Internal().on_write = [this](const std::string& ToWrite) {
// TODO: Sanitize by removing all ansi escape codes (vt100)
std::unique_lock Lock(mLogFileStreamMtx);
mLogFileStream.write(ToWrite.c_str(), ToWrite.size());
mLogFileStream.write("\n", 1);
mLogFileStream.flush();
};
}
void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
if (!mIsLuaConsole) {
if (!mLuaEngine) {
@@ -118,10 +107,10 @@ void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
mStateId = LuaStateId;
mIsLuaConsole = true;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Entered Lua console for state '" + mStateId + "'. To exit, type `exit()`");
Application::Console().WriteRaw("Attached to Lua state '" + mStateId + "'. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua @" + LuaStateId + "> ");
} else {
Application::Console().WriteRaw("Entered Lua console. To exit, type `exit()`");
Application::Console().WriteRaw("Attached to Lua. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua> ");
}
mCachedRegularHistory = mCommandline.history();
@@ -133,9 +122,9 @@ void TConsole::ChangeToRegularConsole() {
if (mIsLuaConsole) {
mIsLuaConsole = false;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Left Lua console for state '" + mStateId + "'.");
Application::Console().WriteRaw("Detached from Lua state '" + mStateId + "'.");
} else {
Application::Console().WriteRaw("Left Lua console.");
Application::Console().WriteRaw("Detached from Lua.");
}
mCachedLuaHistory = mCommandline.history();
mCommandline.set_history(mCachedRegularHistory);
@@ -375,11 +364,67 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
}
}
void TConsole::HandleLuaInternalCommand(const std::string& cmd) {
if (cmd == "detach") {
ChangeToRegularConsole();
} else if (cmd == "queued") {
auto QueuedFunctions = LuaAPI::MP::Engine->Debug_GetStateFunctionQueueForState(mStateId);
Application::Console().WriteRaw("Pending functions in State '" + mStateId + "'");
std::unordered_map<std::string, size_t> FunctionsCount;
std::vector<std::string> FunctionsInOrder;
while (!QueuedFunctions.empty()) {
auto Tuple = QueuedFunctions.front();
QueuedFunctions.pop();
FunctionsInOrder.push_back(std::get<0>(Tuple));
FunctionsCount[std::get<0>(Tuple)] += 1;
}
std::set<std::string> Uniques;
for (const auto& Function : FunctionsInOrder) {
if (Uniques.count(Function) == 0) {
Uniques.insert(Function);
if (FunctionsCount.at(Function) > 1) {
Application::Console().WriteRaw(" " + Function + " (" + std::to_string(FunctionsCount.at(Function)) + "x)");
} else {
Application::Console().WriteRaw(" " + Function);
}
}
}
Application::Console().WriteRaw("Executed functions waiting to be checked in State '" + mStateId + "'");
for (const auto& Function : LuaAPI::MP::Engine->Debug_GetResultsToCheckForState(mStateId)) {
Application::Console().WriteRaw(" '" + Function.Function + "' (Ready? " + (Function.Ready ? "Yes" : "No") + ", Error? " + (Function.Error ? "Yes: '" + Function.ErrorMessage + "'" : "No") + ")");
}
} else if (cmd == "events") {
auto Events = LuaAPI::MP::Engine->Debug_GetEventsForState(mStateId);
Application::Console().WriteRaw("Registered Events + Handlers for State '" + mStateId + "'");
for (const auto& EventHandlerPair : Events) {
Application::Console().WriteRaw(" Event '" + EventHandlerPair.first + "'");
for (const auto& Handler : EventHandlerPair.second) {
Application::Console().WriteRaw(" " + Handler);
}
}
} else if (cmd == "help") {
Application::Console().WriteRaw(R"(BeamMP Lua Debugger
All commands must be prefixed with a `:`. Non-prefixed commands are interpreted as Lua.
Commands
:detach detaches (exits) from this Lua console
:help displays this help
:events shows a list of currently registered events
:queued shows a list of all pending and queued functions)");
} else {
beammp_error("internal command '" + cmd + "' is not known");
}
}
TConsole::TConsole() {
mCommandline.enable_history();
mCommandline.set_history_limit(20);
mCommandline.set_prompt("> ");
BackupOldLog();
bool success = mCommandline.enable_write_to_file("Server.log");
if (!success) {
beammp_error("unable to open file for writing: \"Server.log\"");
}
mCommandline.on_command = [this](Commandline& c) {
try {
auto cmd = c.get_command();
@@ -388,12 +433,12 @@ TConsole::TConsole() {
if (mIsLuaConsole) {
if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second");
} else if (cmd == "exit()") {
ChangeToRegularConsole();
} else if (!cmd.empty() && cmd.at(0) == ':') {
HandleLuaInternalCommand(cmd.substr(1));
} else {
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(cmd), "", "" });
while (!Future->Ready) {
std::this_thread::yield(); // TODO: Add a timeout
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // TODO: Add a timeout
}
if (Future->Error) {
beammp_lua_error(Future->ErrorMessage);
+29 -71
View File
@@ -19,9 +19,7 @@ void THeartbeatThread::operator()() {
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
bool isAuth = false;
size_t UpdateReminderCounter = 0;
while (!mShutdown) {
++UpdateReminderCounter;
Body = GenerateCall();
// a hot-change occurs when a setting has changed, to update the backend of that change.
auto Now = std::chrono::high_resolution_clock::now();
@@ -56,82 +54,42 @@ void THeartbeatThread::operator()() {
auto Target = "/heartbeat";
unsigned int ResponseCode = 0;
T = Http::POST(Application::GetBackendHostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode);
json::Document Doc;
bool Ok = false;
for (const auto& Url : Application::GetBackendUrlsInOrder()) {
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
beammp_trace(T);
Doc.Parse(T.data(), T.size());
if (Doc.HasParseError() || !Doc.IsObject()) {
beammp_debug("Failed to contact backend at " + Url + " (this is not an error).");
beammp_trace("Response was: " + T);
Sentry.SetContext("JSON Response", { { "reponse", T } });
SentryReportError(Url + Target, ResponseCode);
} else if (ResponseCode != 200) {
SentryReportError(Url + Target, ResponseCode);
} else {
// all ok
Ok = true;
break;
}
if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) {
beammp_trace("got " + T + " from backend");
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad);
SentryReportError(Application::GetBackendHostname() + Target, ResponseCode);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::string Status {};
std::string Code {};
std::string Message {};
const auto StatusKey = "status";
const auto CodeKey = "code";
const auto MessageKey = "msg";
if (Ok) {
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) {
Status = Doc[StatusKey].GetString();
T = Http::POST(Application::GetBackup1Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode);
if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) {
SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode);
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackup2Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode);
if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) {
beammp_warn("Backend system refused server! Server will not show in the public server list.");
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad);
isAuth = false;
SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode);
} else {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
} else {
Sentry.SetContext("JSON Response", { { StatusKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) {
Code = Doc[CodeKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { CodeKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) {
Message = Doc[MessageKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { MessageKey, "invalid string / missing" } });
Ok = false;
}
if (!Ok) {
beammp_error("Missing/invalid json members in backend response");
Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__));
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
} else {
if (!Application::Settings.Private) {
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
}
}
if (Ok && !isAuth) {
if (Status == "2000") {
beammp_info(("Authenticated!"));
isAuth = true;
} else if (Status == "200") {
beammp_info(("Resumed authenticated session!"));
isAuth = true;
} else {
if (Message.empty()) {
Message = "Backend didn't provide a reason";
}
beammp_error("Backend REFUSED the auth key. " + Message);
}
}
if (isAuth) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
Application::CheckForUpdates();
if (!isAuth) {
if (T == "2000") {
beammp_info(("Authenticated!"));
isAuth = true;
} else if (T == "200") {
beammp_info(("Resumed authenticated session!"));
isAuth = true;
}
}
}
}
+99 -93
View File
@@ -44,7 +44,7 @@ void TLuaEngine::operator()() {
CollectAndInitPlugins();
// now call all onInit's
auto Futures = TriggerEvent("onInit", "");
WaitForAll(Futures, std::chrono::seconds(5));
WaitForAll(Futures);
for (const auto& Future : Futures) {
if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) {
beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage);
@@ -54,20 +54,31 @@ void TLuaEngine::operator()() {
auto ResultCheckThread = std::thread([&] {
RegisterThread("ResultCheckThread");
while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock Lock(mResultsToCheckMutex);
mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
if (!mResultsToCheck.empty()) {
mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool {
if (Ptr->Ready) {
return true;
} else if (Ptr->Error) {
if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
}
return true;
auto Res = mResultsToCheck.front();
mResultsToCheck.pop();
Lock.unlock();
size_t Waited = 0;
while (!Res->Ready) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Waited++;
if (Waited > 250) {
// FIXME: This should *eventually* timeout.
// beammp_lua_error(Res->Function + " in " + Res->StateId + " took >1s to respond, not printing possible errors");
Lock.lock();
mResultsToCheck.push(Res);
Lock.unlock();
break;
}
return false;
});
}
if (Res->Error) {
if (Res->ErrorMessage != BeamMPFnNotFoundError) {
beammp_lua_error(Res->Function + ": " + Res->ErrorMessage);
}
}
}
}
});
@@ -75,37 +86,27 @@ void TLuaEngine::operator()() {
auto Before = std::chrono::high_resolution_clock::now();
while (!mShutdown) {
if (mLuaStates.size() == 0) {
std::this_thread::sleep_for(std::chrono::seconds(100));
std::this_thread::sleep_for(std::chrono::seconds(500));
}
{ // Timed Events Scope
std::unique_lock Lock(mTimedEventsMutex);
for (auto& Timer : mTimedEvents) {
if (Timer.Expired()) {
auto LastCompletionBeforeReset = Timer.LastCompletion;
Timer.Reset();
auto Handlers = GetEventHandlersForState(Timer.EventName, Timer.StateId);
std::unique_lock StateLock(mLuaStatesMutex);
std::unique_lock Lock2(mResultsToCheckMutex);
for (auto& Handler : Handlers) {
auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCallFromCustomEvent(Handler, {}, Timer.EventName, Timer.Strategy);
if (Res) {
mResultsToCheck.push_back(Res);
mResultsToCheckCond.notify_one();
} else {
// "revert" reset
Timer.LastCompletion = LastCompletionBeforeReset;
// beammp_trace("Reverted reset of \"" + Timer.EventName + "\" timer");
// no need to try to enqueue more handlers for this event (they will all fail)
break;
}
auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCall(Handler, {});
mResultsToCheck.push(Res);
}
}
}
}
const auto Expected = std::chrono::milliseconds(10);
if (auto Diff = std::chrono::high_resolution_clock::now() - Before;
Diff < Expected) {
std::this_thread::sleep_for(Expected - Diff);
std::chrono::high_resolution_clock::duration Diff;
if ((Diff = std::chrono::high_resolution_clock::now() - Before)
< std::chrono::milliseconds(10)) {
std::this_thread::sleep_for(Diff);
} else {
beammp_trace("Event loop cannot keep up! Running " + std::to_string(Diff.count()) + "s behind");
}
@@ -148,10 +149,48 @@ TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) {
return "";
}
void TLuaEngine::AddResultToCheck(const std::shared_ptr<TLuaResult>& Result) {
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::unique_lock Lock(mLuaEventsMutex);
for (const auto& EventNameToEventMap : mLuaEvents) {
for (const auto& IdSetOfHandlersPair : EventNameToEventMap.second) {
if (IdSetOfHandlersPair.first == StateId) {
for (const auto& Handler : IdSetOfHandlersPair.second) {
Result[EventNameToEventMap.first].push_back(Handler);
}
}
}
}
return Result;
}
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) {
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue();
return Result;
}
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> TLuaEngine::Debug_GetStateFunctionQueueForState(TLuaStateId StateId) {
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateFunctionQueue();
return Result;
}
std::vector<TLuaResult> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
std::unique_lock Lock(mResultsToCheckMutex);
mResultsToCheck.push_back(Result);
mResultsToCheckCond.notify_one();
auto ResultsToCheckCopy = mResultsToCheck;
Lock.unlock();
std::vector<TLuaResult> Result;
while (!ResultsToCheckCopy.empty()) {
auto ResultToCheck = std::move(ResultsToCheckCopy.front());
ResultsToCheckCopy.pop();
if (ResultToCheck->StateId == StateId) {
Result.push_back(*ResultToCheck);
}
}
return Result;
}
void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<std::chrono::high_resolution_clock::duration>& Max) {
@@ -162,10 +201,8 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
std::this_thread::sleep_for(std::chrono::milliseconds(10));
ms += 10;
if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) {
beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms)");
Cancelled = true;
} else if (ms > 1000 * 60) {
beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
}
}
if (Cancelled) {
@@ -183,8 +220,7 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
void TLuaEngine::ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results) {
std::unique_lock Lock2(mResultsToCheckMutex);
for (const auto& Result : Results) {
mResultsToCheck.push_back(Result);
mResultsToCheckCond.notify_one();
mResultsToCheck.push(Result);
}
}
@@ -512,27 +548,11 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
return Lua_GetPlayerIdentifiers(ID);
});
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
// const std::string& EventName, size_t IntervalMS, int strategy
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
if (Args.size() < 2 || Args.size() > 3) {
beammp_lua_error("CreateEventTimer expects 2 or 3 arguments.");
}
if (Args.get_type(0) != sol::type::string) {
beammp_lua_error("CreateEventTimer expects 1st argument to be a string");
}
if (Args.get_type(1) != sol::type::number) {
beammp_lua_error("CreateEventTimer expects 2nd argument to be a number");
}
if (Args.size() == 3 && Args.get_type(2) != sol::type::number) {
beammp_lua_error("CreateEventTimer expects 3rd argument to be a number (MP.CallStrategy)");
}
auto EventName = Args.get<std::string>(0);
auto IntervalMS = Args.get<size_t>(1);
CallStrategy Strategy = Args.size() > 2 ? Args.get<CallStrategy>(2) : CallStrategy::BestEffort;
MPTable.set_function("CreateEventTimer", [&](const std::string& EventName, size_t IntervalMS) {
if (IntervalMS < 25) {
beammp_warn("Timer for \"" + EventName + "\" on \"" + mStateId + "\" is set to trigger at <25ms, which is likely too fast and won't cancel properly.");
}
mEngine->CreateEventTimer(EventName, mStateId, IntervalMS, Strategy);
mEngine->CreateEventTimer(EventName, mStateId, IntervalMS);
});
MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) {
mEngine->CancelEventTimers(EventName, mStateId);
@@ -552,10 +572,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
"Name", 5,
"Description", 6);
MPTable.create_named("CallStrategy",
"BestEffort", CallStrategy::BestEffort,
"Precise", CallStrategy::Precise);
auto FSTable = StateView.create_named_table("FS");
FSTable.set_function("CreateDirectory", &LuaAPI::FS::CreateDirectory);
FSTable.set_function("Exists", &LuaAPI::FS::Exists);
@@ -578,34 +594,12 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLu
return Result;
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy) {
// TODO: Document all this
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
if (Strategy == CallStrategy::BestEffort) {
Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(),
[&EventName](const QueuedFunction& Element) {
return Element.EventName == EventName;
});
}
if (Iter == mStateFunctionQueue.end()) {
auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
mStateFunctionQueueCond.notify_all();
return Result;
} else {
return nullptr;
}
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" });
mStateFunctionQueue.push({ FunctionName, Result, Args });
mStateFunctionQueueCond.notify_all();
return Result;
}
@@ -668,13 +662,12 @@ void TLuaEngine::StateThreadData::operator()() {
std::chrono::milliseconds(500),
[&]() -> bool { return !mStateFunctionQueue.empty(); });
if (NotExpired) {
auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
mStateFunctionQueue.erase(mStateFunctionQueue.begin());
auto FnNameResultPair = std::move(mStateFunctionQueue.front());
mStateFunctionQueue.pop();
Lock.unlock();
auto& FnName = TheQueuedFunction.FunctionName;
auto& Result = TheQueuedFunction.Result;
auto Args = TheQueuedFunction.Args;
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
auto& FnName = std::get<0>(FnNameResultPair);
auto& Result = std::get<1>(FnNameResultPair);
auto Args = std::get<2>(FnNameResultPair);
Result->StateId = mStateId;
sol::state_view StateView(mState);
auto Fn = StateView[FnName];
@@ -722,14 +715,23 @@ void TLuaEngine::StateThreadData::operator()() {
}
}
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) {
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() {
std::unique_lock Lock(mStateExecuteQueueMutex);
return mStateExecuteQueue;
}
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> TLuaEngine::StateThreadData::Debug_GetStateFunctionQueue() {
std::unique_lock Lock(mStateFunctionQueueMutex);
return mStateFunctionQueue;
}
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS) {
std::unique_lock Lock(mTimedEventsMutex);
TimedEvent Event {
std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) },
std::chrono::high_resolution_clock::now(),
EventName,
StateId,
Strategy
StateId
};
mTimedEvents.push_back(std::move(Event));
beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval");
@@ -757,7 +759,6 @@ void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) {
void TLuaResult::WaitUntilReady() {
while (!Ready) {
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
@@ -815,7 +816,12 @@ void TPluginMonitor::operator()() {
auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine.EnqueueScript(StateID, Chunk);
// TODO: call onInit
mEngine.AddResultToCheck(Res);
while (!Res->Ready) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
if (Res->Error) {
beammp_lua_error(Res->ErrorMessage);
}
} else {
// TODO: trigger onFileChanged event
beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet");
+9 -29
View File
@@ -154,19 +154,6 @@ void TNetwork::TCPServerMain() {
beammp_warn(("Got an invalid client socket on connect! Skipping..."));
continue;
}
// set timeout (DWORD, aka uint32_t)
uint32_t SendTimeoutMS = 30 * 1000;
#if defined(BEAMMP_WINDOWS)
int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&SendTimeoutMS), sizeof(SendTimeoutMS));
#else // POSIX
struct timeval optval;
optval.tv_sec = (int)(SendTimeoutMS / 1000);
optval.tv_usec = (SendTimeoutMS % 1000) * 1000;
int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<void*>(&optval), sizeof(optval));
#endif
if (ret < 0) {
throw std::runtime_error("setsockopt recv timeout: " + GetPlatformAgnosticErrorString());
}
std::thread ID(&TNetwork::Identify, this, client);
ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) {
@@ -200,11 +187,7 @@ void TNetwork::Identify(const TConnection& client) {
} else if (Code == 'D') {
HandleDownload(client.Socket);
} else if (Code == 'P') {
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
send(client.Socket, "P", 1, MSG_NOSIGNAL);
#else
send(client.Socket, "P", 1, 0);
#endif
CloseSocketProper(client.Socket);
return;
} else {
@@ -527,7 +510,6 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
}
}
}
void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
// TODO: the c.expired() might cause issues here, remove if you end up here with your debugger
if (c.expired() || c.lock()->GetTCPSock() == -1) {
@@ -756,11 +738,11 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
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);
uint32_t Split = 0x7735940; // 125MB
std::vector<char> Data;
char* Data;
if (Size > Split)
Data.resize(Split);
Data = new char[Split];
else
Data.resize(Size);
Data = new char[Size];
SOCKET TCPSock;
if (D)
TCPSock = c.GetDownSock();
@@ -771,8 +753,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
size_t Diff = Size - Sent;
if (Diff > Split) {
f.seekg(Sent, std::ios_base::beg);
f.read(Data.data(), Split);
if (!TCPSendRaw(c, TCPSock, Data.data(), Split)) {
f.read(Data, Split);
if (!TCPSendRaw(c, TCPSock, Data, Split)) {
if (c.GetStatus() > -1)
c.SetStatus(-1);
break;
@@ -780,8 +762,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Split;
} else {
f.seekg(Sent, std::ios_base::beg);
f.read(Data.data(), Diff);
if (!TCPSendRaw(c, TCPSock, Data.data(), int32_t(Diff))) {
f.read(Data, Diff);
if (!TCPSendRaw(c, TCPSock, Data, int32_t(Diff))) {
if (c.GetStatus() > -1)
c.SetStatus(-1);
break;
@@ -789,16 +771,14 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Diff;
}
}
delete[] Data;
f.close();
}
bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) {
intmax_t Sent = 0;
do {
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), MSG_NOSIGNAL);
#else
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), 0);
#endif
if (Temp < 1) {
beammp_info("Socket Closed! " + std::to_string(socket));
CloseSocketProper(socket);
+2 -2
View File
@@ -21,8 +21,8 @@ TPPSMonitor::TPPSMonitor(TServer& Server)
void TPPSMonitor::operator()() {
RegisterThread("PPSMonitor");
while (!mNetwork) {
// hard(-ish) spin
std::this_thread::yield();
// hard spi
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
beammp_debug("PPSMonitor starting");
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good);
+16 -14
View File
@@ -7,14 +7,14 @@
#include <any>
#include <sstream>
#include <nlohmann/json.hpp>
#include "LuaAPI.h"
#undef GetObject // Fixes Windows
#include "Json.h"
namespace json = rapidjson;
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -171,25 +171,27 @@ void TServer::HandleEvent(TClient& c, const std::string& Data) {
}
}
bool TServer::IsUnicycle(TClient& c, const std::string& CarJson) {
try {
auto Car = nlohmann::json::parse(CarJson);
const std::string jbm = "jbm";
if (Car.contains(jbm) && Car[jbm].is_string() && Car[jbm] == "unicycle") {
return true;
}
} catch (const std::exception& e) {
beammp_error("Failed to parse vehicle data as json for client " + std::to_string(c.GetID()) + ": '" + CarJson + "'");
rapidjson::Document Car;
Car.Parse(CarJson.c_str(), CarJson.size());
if (Car.HasParseError()) {
beammp_error("Failed to parse vehicle data -> " + CarJson);
} else if (Car["jbm"].IsString() && std::string(Car["jbm"].GetString()) == "unicycle") {
return true;
}
return false;
}
bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
if (IsUnicycle(c, CarJson) && c.GetUnicycleID() < 0) {
if (c.GetUnicycleID() > -1 && (c.GetCarCount() - 1) < Application::Settings.MaxCars) {
return true;
}
if (IsUnicycle(c, CarJson)) {
c.SetUnicycleID(ID);
return true;
} else {
return c.GetCarCount() < Application::Settings.MaxCars;
}
return Application::Settings.MaxCars > c.GetCarCount();
}
void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network) {
+5 -15
View File
@@ -78,6 +78,10 @@ int main(int argc, char** argv) {
int BeamMPServerMain(MainArguments Arguments) {
setlocale(LC_ALL, "C");
Application::InitializeConsole();
Application::SetSubsystemStatus("Main", Application::Status::Starting);
SetupSignalHandlers();
ArgsParser Parser;
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
@@ -118,21 +122,14 @@ int BeamMPServerMain(MainArguments Arguments) {
}
}
Application::SetSubsystemStatus("Main", Application::Status::Starting);
Application::Console().StartLoggingToFile();
SetupSignalHandlers();
bool Shutdown = false;
Application::RegisterShutdownHandler([&Shutdown] {
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown);
Shutdown = true;
});
Application::RegisterShutdownHandler([] {
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
TLuaEngine::WaitForAll(Futures);
});
TServer Server(Arguments.List);
@@ -171,10 +168,6 @@ int BeamMPServerMain(MainArguments Arguments) {
Application::SetSubsystemStatus("Main", Application::Status::Good);
RegisterThread("Main(Waiting)");
std::set<std::string> IgnoreSubsystems {
"UpdateCheck" // Ignore as not to confuse users (non-vital system)
};
bool FullyStarted = false;
while (!Shutdown) {
if (!FullyStarted) {
@@ -183,9 +176,6 @@ int BeamMPServerMain(MainArguments Arguments) {
std::string SystemsBadList {};
auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) {
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
continue; // ignore
}
if (NameStatusPair.second == Application::Status::Starting) {
FullyStarted = false;
} else if (NameStatusPair.second == Application::Status::Bad) {