From 43429eadb3768c3c80276dc806aa1f46a5e90b29 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Tue, 6 Feb 2024 00:11:31 +0100 Subject: [PATCH] fixup --- include/LuaPlugin.h | 6 +- include/Plugin.h | 2 +- include/PluginManager.h | 9 +-- include/Value.h | 2 +- src/LuaPlugin.cpp | 163 +++++++++++++++++++++++++++++----------- src/Value.cpp | 17 ++++- 6 files changed, 143 insertions(+), 56 deletions(-) diff --git a/include/LuaPlugin.h b/include/LuaPlugin.h index 7c15478..8e3d636 100644 --- a/include/LuaPlugin.h +++ b/include/LuaPlugin.h @@ -82,7 +82,7 @@ public: /// Path to the folder containing this lua plugin. virtual std::filesystem::path path() const override; /// Dispatches the event to the thread which runs all lua. - virtual std::shared_future> handle_event(const std::string& event_name, const std::shared_ptr& args) override; + virtual std::shared_future> handle_event(const std::string& event_name, const std::shared_ptr& args) override; /// Returns the memory usage of this thread, updated at the slowest every 5 seconds. virtual size_t memory_usage() const override; @@ -95,9 +95,9 @@ private: boost::asio::io_context m_io; /// Event handlers which are legacy-style (by name) - HashMap m_event_handlers_named {}; + HashMap> m_event_handlers_named {}; /// Event handlers which are functions (v4 style) - HashMap m_event_handlers {}; + HashMap> m_event_handlers {}; /// Main (and usually only) lua state of this plugin. /// ONLY access this from the m_thread thread. diff --git a/include/Plugin.h b/include/Plugin.h index f3f9e11..e44a9a1 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -58,7 +58,7 @@ public: /// Instructs the plugin to handle the given event, with the given arguments. /// Returns a future with a result if this event will be handled by the plugin, otherwise must return /// std::nullopt. - virtual std::shared_future> handle_event(const std::string& event_name, const std::shared_ptr& args) = 0; + virtual std::shared_future> handle_event(const std::string& event_name, const std::shared_ptr& args) = 0; /// Returns how much memory this state thinks it uses. /// diff --git a/include/PluginManager.h b/include/PluginManager.h index f2918f6..4786175 100644 --- a/include/PluginManager.h +++ b/include/PluginManager.h @@ -24,9 +24,9 @@ public: /// so try not to wait on these futures without a timeout. /// /// This function should not block. - std::vector>> trigger_event(const std::string& event_name, const std::shared_ptr& args) { + std::vector>> trigger_event(const std::string& event_name, const std::shared_ptr& args) { // results will go in here - std::vector>> results; + std::vector>> results; // synchronize practically grabs a lock to the mutex, this is (as the name suggests) // a synchronization point. technically, it could dead-lock if something that is called // in this locked context tries to lock the m_plugins mutex. @@ -40,9 +40,9 @@ public: (void)name; // ignore the name // propagates the event to the plugin, this returns a future // we assume that at this point no plugin-specific code has executed - auto maybe_result = plugin->handle_event(event_name, args); + auto result = plugin->handle_event(event_name, args); // if the plugin had no handler, this result has no value, and we can ignore it - results.push_back(maybe_result); + results.push_back(std::move(result)); } return results; } @@ -67,4 +67,3 @@ private: /// All plugins as pointers to allow inheritance. SynchronizedHashMap m_plugins; }; - diff --git a/include/Value.h b/include/Value.h index 1f1b857..ebc93a5 100644 --- a/include/Value.h +++ b/include/Value.h @@ -265,5 +265,5 @@ private: /// "invalid provider" means "provider of values for invalid sol values". If nullptr, then /// any invalid value (such as a function) will be resolved to an error instead and the function will /// fail. -Result sol_obj_to_value(const sol::object&, const std::function(const sol::object&)>& invalid_provider = nullptr); +Result sol_obj_to_value(const sol::object&, const std::function(const sol::object&)>& invalid_provider = nullptr, size_t max_depth = 500); diff --git a/src/LuaPlugin.cpp b/src/LuaPlugin.cpp index 1ea6920..7f82062 100644 --- a/src/LuaPlugin.cpp +++ b/src/LuaPlugin.cpp @@ -34,11 +34,11 @@ static int lua_panic_handler(lua_State* state) { return 1; } -#define beammp_lua_debugf(...) beammp_debugf("[lua:{}] {}", name(), fmt::format(__VA_ARGS__)) -#define beammp_lua_infof(...) beammp_infof("[lua:{}] {}", name(), fmt::format(__VA_ARGS__)) -#define beammp_lua_warnf(...) beammp_warnf("[lua:{}] {}", name(), fmt::format(__VA_ARGS__)) -#define beammp_lua_errorf(...) beammp_errorf("[lua:{}] {}", name(), fmt::format(__VA_ARGS__)) -#define beammp_lua_tracef(...) beammp_tracef("[lua:{}] {}", name(), fmt::format(__VA_ARGS__)) +#define beammp_lua_debugf(...) beammp_debugf("[{}] {}", name(), fmt::format(__VA_ARGS__)) +#define beammp_lua_infof(...) beammp_infof("[{}] {}", name(), fmt::format(__VA_ARGS__)) +#define beammp_lua_warnf(...) beammp_warnf("[{}] {}", name(), fmt::format(__VA_ARGS__)) +#define beammp_lua_errorf(...) beammp_errorf("[{}] {}", name(), fmt::format(__VA_ARGS__)) +#define beammp_lua_tracef(...) beammp_tracef("[{}] {}", name(), fmt::format(__VA_ARGS__)) static constexpr const char* ERR_HANDLER = "__beammp_lua_error_handler"; @@ -131,10 +131,59 @@ Error LuaPlugin::initialize_libraries() { glob["MP"]["GetPluginPath"] = [this] { return std::filesystem::absolute(m_path).string(); }; - glob["MP"]["RegisterEvent"] = [this] (const std::string& event_name, const std::string& handler) { - + glob["MP"]["RegisterEvent"] = [this](const std::string& event_name, const sol::object& handler) { + if (handler.get_type() == sol::type::string) { + m_event_handlers_named[event_name].push_back(handler.as()); + } else if (handler.get_type() == sol::type::function) { + auto fn = handler.as(); + fn.set_error_handler(m_state.globals()[ERR_HANDLER]); + m_event_handlers[event_name].push_back(fn); + } else { + beammp_lua_errorf("Invalid call to MP.RegisterEvent for event '{}': Expected string or function as second argument", event_name); + } + }; + glob["MP"]["Post"] = [this](const sol::protected_function& fn) { + boost::asio::post(m_io, [fn] { + fn(); + }); + }; + glob["MP"]["TriggerLocalEvent"] = [this](const std::string& event_name, sol::variadic_args args) -> sol::table { + std::vector args_vec(args.begin(), args.end()); + ValueTuple values {}; + values.reserve(args_vec.size()); + for (const auto& obj : args_vec) { + auto res = sol_obj_to_value(obj); + if (res) [[likely]] { + values.emplace_back(res.move()); + } else { + beammp_lua_errorf("Can't serialize an argument across boundaries (for passing to a MP.TriggerLocalEvent): {}", res.error); + values.emplace_back(Null {}); + } + } + auto results = handle_event(event_name, std::make_shared(std::move(values))); + auto result = m_state.create_table(); + result["__INTERNAL"] = results; + // waits for a specific number of milliseconds for a result to be available, returns false if timed out, true if ready. + result["WaitForMS"] = [](const sol::object& self, int milliseconds) { + std::shared_future> future = self.as()["__INTERNAL"]; + auto status = future.wait_for(std::chrono::milliseconds(milliseconds)); + return status == std::future_status::ready; + }; + // waits indefinitely for all results to be available + result["Wait"] = [](const sol::table& self) { + std::shared_future> future = self["__INTERNAL"]; + future.wait(); + }; + // waits indefinitely for all results to be available, then returns them + result["Results"] = [this](const sol::table& self) -> sol::table { + std::shared_future> future = self["__INTERNAL"]; + future.wait(); + Value results = future.get(); + auto lua_res = boost::apply_visitor(ValueToLuaVisitor(m_state), results); + return lua_res; + }; + return result; }; - glob["MP"]["ScheduleCallRepeat"] = [this](size_t ms, sol::function fn, sol::variadic_args args) { return l_mp_schedule_call_repeat(ms, fn, args); }; @@ -145,7 +194,7 @@ Error LuaPlugin::initialize_libraries() { // this has to be post()-ed, otherwise the call will not cancel (not sure why) boost::asio::post(m_io, [this, timer] { if (!timer) { - beammp_lua_errorf("uct.cancel_scheduled_call: timer already cancelled"); + beammp_lua_errorf("MP.cancel_scheduled_call: timer already cancelled"); return; } beammp_lua_debugf("Cancelling timer"); @@ -156,8 +205,8 @@ Error LuaPlugin::initialize_libraries() { }; glob["MP"]["GetOSName"] = &LuaAPI::MP::GetOSName; - //glob["MP"]["GetTimeMS"] = &LuaAPI::MP::GetTimeMS; - //glob["MP"]["GetTimeS"] = &LuaAPI::MP::GetTimeS; + // glob["MP"]["GetTimeMS"] = &LuaAPI::MP::GetTimeMS; + // glob["MP"]["GetTimeS"] = &LuaAPI::MP::GetTimeS; glob.create_named("Util"); glob["Util"]["JsonEncode"] = &LuaAPI::Util::JsonEncode; @@ -420,40 +469,70 @@ std::filesystem::path LuaPlugin::path() const { return m_path; } -std::shared_future> LuaPlugin::handle_event(const std::string& event_name, const std::shared_ptr& args) { - std::shared_ptr>> promise = std::make_shared>>(); - std::shared_future> future { promise->get_future() }; - boost::asio::post(m_io, [this, event_name, args, promise, future] { - try { - if (m_state.globals()[event_name].valid() && m_state.globals()[event_name].get_type() == sol::type::function) { - auto fn = m_state.globals().get(event_name); - fn.set_error_handler(m_state.globals()[ERR_HANDLER]); - auto lua_args = boost::apply_visitor(ValueToLuaVisitor(m_state), *args); - sol::protected_function_result res; +std::shared_future> LuaPlugin::handle_event(const std::string& event_name, const std::shared_ptr& args) { + std::shared_ptr>> promise = std::make_shared>>(); + std::shared_future> futures { promise->get_future() }; + boost::asio::post(m_io, [this, event_name, args, promise, futures] { + std::vector results {}; + if (m_event_handlers_named.contains(event_name)) { + auto handler_names = m_event_handlers_named.at(event_name); + for (const auto& handler_name : handler_names) { + try { + if (m_state.globals()[handler_name].valid() && m_state.globals()[handler_name].get_type() == sol::type::function) { + auto fn = m_state.globals().get(handler_name); + fn.set_error_handler(m_state.globals()[ERR_HANDLER]); + auto lua_args = boost::apply_visitor(ValueToLuaVisitor(m_state), *args); + sol::protected_function_result res; - if (args->which() == VALUE_TYPE_IDX_TUPLE) { - res = fn(sol::as_args(lua_args.as>())); - } else { - res = fn(lua_args); - } - if (res.valid()) { - auto maybe_res = sol_obj_to_value(res.get()); - if (maybe_res) { - promise->set_value(maybe_res.move()); - return; + if (args->which() == VALUE_TYPE_IDX_TUPLE) { + res = fn(sol::as_args(lua_args.as>())); + } else { + res = fn(lua_args); + } + if (res.valid()) { + auto maybe_res = sol_obj_to_value(res.get()); + if (maybe_res) { + results.emplace_back(maybe_res.move()); + } else { + beammp_lua_errorf("Error using return value from event handler '{}' for event '{}': {}", handler_name, event_name, maybe_res.error); + } + } } else { - beammp_lua_errorf("Error using return value from event handler '{}': {}", event_name, maybe_res.error); + beammp_lua_errorf("Invalid event handler '{}' for event '{}': Handler either doesn't exist or isn't a global function", handler_name, event_name); } + } catch (const std::exception& e) { + beammp_lua_errorf("Error finding and running event handler for event '{}': {}. It was called with argument(s): {}", event_name, e.what(), boost::apply_visitor(ValueToStringVisitor(), *args)); } - } else { // TODO: CONTINUE HERE - beammp_lua_tracef("No handler for event '{}'", event_name); } - } catch (const std::exception& e) { - beammp_lua_errorf("Error finding and running event handler for event '{}': {}. It was called with argument(s): {}", event_name, e.what(), boost::apply_visitor(ValueToStringVisitor(), *args)); } - promise->set_value(std::nullopt); + if (m_event_handlers.contains(event_name)) { + auto handlers = m_event_handlers.at(event_name); + for (const auto& fn : handlers) { + try { + auto lua_args = boost::apply_visitor(ValueToLuaVisitor(m_state), *args); + sol::protected_function_result res; + + if (args->which() == VALUE_TYPE_IDX_TUPLE) { + res = fn(sol::as_args(lua_args.as>())); + } else { + res = fn(lua_args); + } + if (res.valid()) { + auto maybe_res = sol_obj_to_value(res.get()); + if (maybe_res) { + results.emplace_back(maybe_res.move()); + } else { + beammp_lua_errorf("Error using return value from event handler '<>' for event '{}': {}", fn.pointer(), event_name, maybe_res.error); + } + } + } catch (const std::exception& e) { + beammp_lua_errorf("Error finding and running event handler for event '{}': {}. It was called with argument(s): {}", event_name, e.what(), boost::apply_visitor(ValueToStringVisitor(), *args)); + } + } + } + promise->set_value(std::move(results)); }); - return future; + return futures; } size_t LuaPlugin::memory_usage() const { @@ -486,7 +565,7 @@ void LuaPlugin::l_print(const sol::variadic_args& args) { void LuaPlugin::l_mp_schedule_call_helper(const boost::system::error_code& err, std::shared_ptr timer, const sol::function& fn, std::shared_ptr args) { if (err) { - beammp_lua_debugf("uct.schedule_call_repeat: {}", err.what()); + beammp_lua_debugf("MP.schedule_call_repeat: {}", err.what()); return; } timer->timer.expires_from_now(timer->interval); @@ -513,13 +592,13 @@ void LuaPlugin::l_mp_schedule_call_once(size_t ms, const sol::function& fn, sol: if (res) [[likely]] { tuple->emplace_back(res.move()); } else { - beammp_lua_errorf("Can't serialize an argument across boundaries (for passing to a uct.schedule_call_* later): ", res.error); + beammp_lua_errorf("Can't serialize an argument across boundaries (for passing to a MP.schedule_call_* later): ", res.error); tuple->emplace_back(Null {}); } } timer->timer.async_wait([this, timer, fn, tuple](const auto& err) { if (err) { - beammp_lua_debugf("uct.schedule_call_once: {}", err.what()); + beammp_lua_debugf("MP.schedule_call_once: {}", err.what()); return; } sol::protected_function prot(fn); @@ -546,7 +625,7 @@ std::shared_ptr LuaPlugin::l_mp_schedule_call_repeat(size_t ms, const sol if (res) [[likely]] { tuple->emplace_back(res.move()); } else { - beammp_lua_errorf("Can't serialize an argument across boundaries (for passing to a uct.schedule_call_* later): ", res.error); + beammp_lua_errorf("Can't serialize an argument across boundaries (for passing to a MP.schedule_call_* later): ", res.error); tuple->emplace_back(Null {}); } } diff --git a/src/Value.cpp b/src/Value.cpp index 2dfbce9..46151b8 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -1,4 +1,5 @@ #include "Value.h" +#include "Common.h" #include "boost/json/value_from.hpp" #include "sol/as_args.hpp" #include "sol/types.hpp" @@ -185,7 +186,12 @@ TEST_CASE("ValueToStringVisitor") { } } -Result sol_obj_to_value(const sol::object& obj, const std::function(const sol::object&)>& invalid_provider) { +static Result sol_obj_to_value_impl(const sol::object& obj, const std::function(const sol::object&)>& invalid_provider, size_t max_depth, size_t depth) { + if (depth == max_depth) { + beammp_errorf("Maximum depth reached for sol_obj_to_value_impl, assuming recursion and returning null for this branch"); + return { Null {} }; + } + ++depth; switch (obj.get_type()) { case sol::type::none: case sol::type::lua_nil: @@ -262,7 +268,7 @@ Result sol_obj_to_value(const sol::object& obj, const std::function("Failed to construct hash-map: Can't use non-string and non-number object as key for table{}", ""); } - auto maybe_val = sol_obj_to_value(v, invalid_provider); + auto maybe_val = sol_obj_to_value_impl(v, invalid_provider, max_depth, depth); if (maybe_val) [[likely]] { map.emplace(key, maybe_val.move()); } else { @@ -277,7 +283,7 @@ Result sol_obj_to_value(const sol::object& obj, const std::function= array.size()) { array.resize(size_t(i) + 1, Null {}); } - auto maybe_val = sol_obj_to_value(v, invalid_provider); + auto maybe_val = sol_obj_to_value_impl(v, invalid_provider, max_depth, depth); if (maybe_val) [[likely]] { array[size_t(i)] = maybe_val.move(); } else { @@ -299,6 +305,10 @@ Result sol_obj_to_value(const sol::object& obj, const std::function("Unknown type, can't convert to value.{}", ""); } +Result sol_obj_to_value(const sol::object& obj, const std::function(const sol::object&)>& invalid_provider, size_t max_depth) { + return sol_obj_to_value_impl(obj, invalid_provider, max_depth, 0); +} + TEST_CASE("sol_obj_to_value") { sol::state state {}; SUBCASE("nil") { @@ -467,4 +477,3 @@ sol::object ValueToLuaVisitor::operator()(const HashMap& map } return table; } -