Skip to content

Commit

Permalink
replace literally the entire lua engine
Browse files Browse the repository at this point in the history
  • Loading branch information
lionkor committed Feb 5, 2024
1 parent df2c091 commit fb0a25e
Show file tree
Hide file tree
Showing 14 changed files with 2,097 additions and 133 deletions.
18 changes: 9 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ include(cmake/Git.cmake)
### SETTINGS ###

# add all headers (.h, .hpp) to this
set(PRJ_HEADERS
set(PRJ_HEADERS
include/ArgsParser.h
include/BoostAliases.h
include/Client.h
Expand All @@ -30,48 +30,48 @@ set(PRJ_HEADERS
include/CustomAssert.h
include/Defer.h
include/Environment.h
include/FileWatcher.h
include/Http.h
include/IThreaded.h
include/Json.h
include/LuaAPI.h
include/LuaPlugin.h
include/Plugin.h
include/PluginManager.h
include/RWMutex.h
include/SignalHandling.h
include/TConfig.h
include/TConsole.h
include/THeartbeatThread.h
include/TLuaEngine.h
include/TLuaPlugin.h
include/TNetwork.h
include/TPluginMonitor.h
include/TPPSMonitor.h
include/TResourceManager.h
include/TScopedTimer.h
include/TServer.h
include/Value.h
include/VehicleData.h
include/Env.h
)
# add all source files (.cpp) to this, except the one with main()
set(PRJ_SOURCES
src/ArgsParser.cpp
src/Client.cpp
src/Common.cpp
src/Compat.cpp
src/FileWatcher.cpp
src/Http.cpp
src/LuaAPI.cpp
src/LuaPlugin.cpp
src/SignalHandling.cpp
src/TConfig.cpp
src/TConsole.cpp
src/THeartbeatThread.cpp
src/TLuaEngine.cpp
src/TLuaPlugin.cpp
src/TNetwork.cpp
src/TPluginMonitor.cpp
src/TPPSMonitor.cpp
src/TResourceManager.cpp
src/TScopedTimer.cpp
src/TServer.cpp
src/Value.cpp
src/VehicleData.cpp
src/Env.cpp
)

find_package(Lua REQUIRED)
Expand Down
14 changes: 11 additions & 3 deletions include/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Application final {
std::string Resource { "Resources" };
std::string MapName { "/levels/gridmap_v2/info.json" };
std::string Key {};
std::string Password{};
std::string Password {};
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { false };
Expand Down Expand Up @@ -218,6 +218,16 @@ void RegisterThread(const std::string& str);
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define beammp_lua_info(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA INFO] ") + (x)); \
} while (false)
#define beammp_lua_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[LUA DEBUG] ") + (x)); \
} \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
Expand Down Expand Up @@ -248,8 +258,6 @@ void RegisterThread(const std::string& str);
#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

Expand Down
120 changes: 120 additions & 0 deletions include/Error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#pragma once

#include <fmt/core.h>
#include <optional>
#include <string>

/// The Error class represents an error or the absence of an
/// error. It behaves like a bool, depending on context.
///
/// The idea is to use this class to pass around errors, together with
/// [[nodiscard]], in order to make errors displayable for the users and
/// to give errors some context. The only way to construct an error is to come
/// up with an error message with this class, so this is an attempt to enforce
/// this.
///
/// A default constructed Error means "no error" / "success", while
/// the only available non-default constructor is one which takes any format.
/// For example:
///
/// \code{.cpp}
/// Error myfunc() {
/// if (ok) {
/// return {}; // no error
/// } else {
/// return Error("Something went wrong: {}", 42); // error
/// }
/// }
///
/// // ... handling:
///
/// Error err = myfunc();
/// if (err) {
/// // error occurred
/// l::error("Error running myfunc: {}", err.error);
/// } else {
/// // ok
/// }
/// \endcode
struct Error {
/// Constructs a "non-error" / empty error, which is not considered
/// to be an error. Use this as the "no error occurred" return value.
Error() = default;
/// Constructs an error with a message. Accepts fmt::format() arguments.
///
/// Example:
///
/// \code{.cpp}
/// // format with fmt (automatically), all arguments are forwarded to fmt::format
/// return Error("failed to open '{}': {}", file, error);
/// // or just as a constexpr string
/// return Error("failed to open file");
/// \endcode
template<typename... Args>
Error(fmt::format_string<Args...> s, Args&&... args)
: is_error(true)
, error(fmt::format(s, std::forward<Args>(args)...)) { }

/// Whether this error represents an error (true) or success (false).
/// Use operator bool() instead of reading this if possible.
bool is_error { false };
/// The error message. Is a valid string even if is_error is false, but will
/// be "Success".
std::string error { "Success" };

/// Implicit conversion to boolean.
/// True if this Error contains an error, false if not.
operator bool() const { return is_error; }
};

// TODO: Add docs

template<typename T>
struct Result {
/// Constructs an error-value result.
/// Currently, you may have to force this by passing a second
/// empty string argument.
template<typename... Args>
Result(fmt::format_string<Args...> s, Args&&... args)
: is_error(true)
, error(fmt::format(s, std::forward<Args>(args)...)) { }

/// Constructs a value-result via an explicit type.
template<typename S>
Result(S&& value)
: result(std::move(value)) {
}

/// Constructs a value-result via an explicit type.
template<typename S>
explicit Result(const S& value)
: result(value) {
}

/// Constructs a value-result via an implicit conversion.
Result(T&& value)
: result(std::move(value)) {
}

/// Constructs a value-result via an implicit conversion.
explicit Result(const T& value)
: result(value) {
}

/// Converts to bool in context. If it has an error, its considered "not a result",
/// so it returns true only if a value is contained.
operator bool() const { return !is_error; }

/// Accesses the value contained by moving it out.
T&& move() { return std::move(result.value()); }
/// Accesses the value contained by const reference.
const T& value() const { return result.value(); }

/// Holds the optional result value. On error, is nullopt.
std::optional<T> result;
/// Whether this result holds an error.
bool is_error { false };
/// Error message.
std::string error { "Success" };
};

90 changes: 90 additions & 0 deletions include/FileWatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once

/// @file
/// This file holds the FileWatcher interface.

#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/system/detail/error_code.hpp>
#include <boost/thread/scoped_thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <filesystem>
#include <functional>
#include <unordered_map>
#include <unordered_set>

/// The FileWatcher class watches a directory or a file for changes,
/// and then notifies the caller through a signal.
///
/// This is a pretty convoluted implementation, and you may find it difficult
/// to read. This is not intentional, but simplifying this would
/// cost more time than to write this excuse.
///
/// It operates as follows:
///
/// A boost::asio::deadline_timer is waited on asynchronously.
/// Once expired, this timer calls FileWatcher::on_tick.
/// That function then loops through all registered files and directories,
/// taking great care to follow symlinks, and tries to find a file which has changed.
/// It determines this by storing the last known modification time.
/// Once a file is found which has a new modification time, the FileWatcher::sig_file_changed
/// signal is fired, and all connected slots must take care to handle the signal.
class FileWatcher {
public:
/// Constructs the FileWatcher to watch the given files every few seconds, as
/// specified by the seconds argument.
FileWatcher(unsigned seconds);
/// Stops the thread via m_shutdown.
~FileWatcher();

/// Add a file to watch. If this file changes, FileWatcher::sig_file_changed is triggered
/// with the path to the file.
void watch_file(const std::filesystem::path& path);
/// Add a directory to watch. If any file in the directory, or any subdirectories, change,
/// FileWatcher::sig_file_changed is triggered.
void watch_files_in(const std::filesystem::path& dir);

private:
/// Entry point for the timer thread.
void thread_main();

/// Called every time the timer runs out, watches for file changes, then starts
/// a new timer.
void on_tick(const boost::system::error_code&);

/// Checks files for changes, calls FileWatcher::sig_file_changed on change.
void check_files();
/// Checks directories for files which changed, calls FileWatcher::sig_file_changed on change.
void check_directories();
/// Checks a single file for change.
void check_file(const std::filesystem::path& file);

/// Interval in seconds for the timer. Needed to be able to restart the timer over and over.
boost::synchronized_value<boost::posix_time::seconds> m_seconds;
/// If set, the thread handling the file watching will shut down. Set in the destructor.
boost::synchronized_value<bool> m_shutdown { false };
/// Io context handles the scheduling of timers on the thread.
boost::asio::io_context m_io {};
/// Holds all files that are to be checked.
///
/// It uses a boost::hash<> because in the original C++17
/// standard, std::hash of a filesystem path was not defined, and as such
/// some implementations still don't have it.
/// See https://cplusplus.github.io/LWG/issue3657
boost::synchronized_value<std::unordered_set<std::filesystem::path, boost::hash<std::filesystem::path>>> m_files {};
/// Holds all the directories that are to be searched for files to be checked.
///
/// See FileWatcher::m_files for an explanation for the boost::hash.
boost::synchronized_value<std::unordered_set<std::filesystem::path, boost::hash<std::filesystem::path>>> m_dirs {};
/// Holds the last known modification times of all found files.
std::unordered_map<std::filesystem::path, std::filesystem::file_time_type, boost::hash<std::filesystem::path>> m_file_mod_times {};
/// Timer used to time the checks. Restarted every FileWatcher::m_seconds seconds.
boost::synchronized_value<boost::asio::deadline_timer> m_timer;
/// Work guard helps the io_context "sleep" while there is no work to be done - must be reset in the
/// destructor in order to not cause work to be thrown away (though in this case we probably don't care).
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_work_guard = boost::asio::make_work_guard(m_io);
/// Thread on which all watching and timing work runs.
boost::scoped_thread<> m_thread;
};

22 changes: 22 additions & 0 deletions include/HashMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

/// @file
/// HashMap holds hash map implementations and typedefs.
///
/// The idea is that we can easily swap out the implementation
/// in case there is a performance or memory usage concern.

#include <boost/container/flat_map.hpp>
#include <boost/thread/synchronized_value.hpp>

/// A hash map to be used for any kind of small number of key-value pairs.
/// Iterators and pointers may be invalidated on modification.
template<typename K, typename V>
using HashMap = boost::container::flat_map<K, V>;

/// A synchronized hash map is a hash map in which each
/// access is thread-safe. In this case, this is achieved by locking
/// each access with a mutex (which often ends up being a futex in the implementation).
template<typename K, typename V>
using SynchronizedHashMap = boost::synchronized_value<boost::container::flat_map<K, V>>;

25 changes: 15 additions & 10 deletions include/LuaAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,33 @@

#pragma once

#include "TLuaEngine.h"
#include <sol/sol.hpp>
#include <string>
#include <tuple>
#include <utility>

namespace LuaAPI {
int PanicHandler(lua_State* State);
std::string LuaToString(const sol::object Value, size_t Indent = 1, bool QuoteStrings = false);
void Print(sol::variadic_args);
namespace MP {
extern TLuaEngine* Engine;

std::string GetOSName();
std::tuple<int, int, int> GetServerVersion();
std::pair<bool, std::string> TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& Data);
std::pair<bool, std::string> TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data);
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
size_t GetPlayerCount();
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message);
std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue);
bool IsPlayerGuest(int ID);
bool IsPlayerConnected(int ID);
void Sleep(size_t Ms);
void PrintRaw(sol::variadic_args);
std::string JsonEncode(const sol::table& object);
/// Returns the current time in millisecond accuracy.
size_t GetTimeMS();
/// Returns the current time in seconds, with millisecond accuracy (w/ decimal point).
double GetTimeS();
}

namespace Util {
std::string JsonEncode(const sol::object& object);
sol::table JsonDecode(sol::this_state s, const std::string& string);
std::string JsonDiff(const std::string& a, const std::string& b);
std::string JsonDiffApply(const std::string& data, const std::string& patch);
std::string JsonPrettify(const std::string& json);
Expand All @@ -62,5 +65,7 @@ namespace FS {
bool IsDirectory(const std::string& Path);
bool IsFile(const std::string& Path);
std::string ConcatPaths(sol::variadic_args Args);
sol::table ListFiles(sol::this_state s, const std::string& path);
sol::table ListDirectories(sol::this_state s, const std::string& path);
}
}
Loading

0 comments on commit fb0a25e

Please sign in to comment.