diff --git a/src/Bot/include/Jupiter_Bot.h b/src/Bot/include/Jupiter_Bot.h index 94e5f6a..ea168ca 100644 --- a/src/Bot/include/Jupiter_Bot.h +++ b/src/Bot/include/Jupiter_Bot.h @@ -44,16 +44,18 @@ #include -/** Forward declaration */ -namespace Jupiter { class Config; } +namespace Jupiter { + /** Forward declarations */ + class Config; -namespace Jupiter -{ /** Application config file */ JUPITER_BOT_API extern Jupiter::Config *g_config; /** Application start time */ JUPITER_BOT_API extern std::chrono::steady_clock::time_point g_start_time; + + /** Reinitialize all application plugins, as if at program startup */ + JUPITER_BOT_API void reinitialize_plugins(); } #endif // __cplusplus diff --git a/src/Bot/src/Main.cpp b/src/Bot/src/Main.cpp index 6966360..7215ca0 100644 --- a/src/Bot/src/Main.cpp +++ b/src/Bot/src/Main.cpp @@ -28,6 +28,7 @@ #include "Jupiter/Socket.h" #include "Jupiter/Plugin.h" #include "Jupiter/Timer.h" +#include "jessilib/word_split.hpp" #include "IRC_Bot.h" #include "Console_Command.h" #include "IRC_Command.h" @@ -83,6 +84,48 @@ void inputLoop() { } } +void initialize_plugins() { + std::cout << "Loading plugins..." << std::endl; + std::string_view plugin_list_str = Jupiter::g_config->get("Plugins"_jrs); + if (plugin_list_str.empty()) { + std::cout << "No plugins to load!" << std::endl; + } + else { + // initialize plugins + auto plugin_names = jessilib::word_split(plugin_list_str, WHITESPACE_SV); + std::cout << "Attempting to load " << plugin_names.size() << " plugins..." << std::endl; + + for (const auto& plugin_name : plugin_names) { + std::chrono::steady_clock::time_point load_start = std::chrono::steady_clock::now(); + bool load_success = Jupiter::Plugin::load(plugin_name) != nullptr; + double time_taken = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - load_start).count()) / 1000.0; + + if (load_success) { + std::cout << "\"" << plugin_name << "\" loaded successfully (" << time_taken << "ms)." << std::endl; + } + else { + std::cerr << "WARNING: Failed to load plugin \"" << plugin_name << "\" (" << time_taken << "ms)!" << std::endl; + } + } + + // OnPostInitialize + for (const auto& plugin : Jupiter::plugins) { + plugin->OnPostInitialize(); + } + } +} + +namespace Jupiter { +void reinitialize_plugins() { + // Uninitialize back -> front + while (!Jupiter::plugins.empty()) { + Jupiter::Plugin::free(Jupiter::plugins.size() - 1); + } + + initialize_plugins(); +} +} // namespace Jupiter + [[noreturn]] void main_loop() { Jupiter::ReferenceString command; size_t index; @@ -182,35 +225,7 @@ int main(int argc, const char **args) { printf("Plugin configs will be loaded from \"%.*s\"." ENDL, static_cast(configs_directory.size()), configs_directory.ptr()); } - puts("Loading plugins..."); - const Jupiter::ReadableString &pluginList = o_config.get("Plugins"_jrs); - if (pluginList.isEmpty()) - puts("No plugins to load!"); - else { - // initialize plugins - unsigned int nPlugins = pluginList.wordCount(WHITESPACE); - printf("Attempting to load %u plugins..." ENDL, nPlugins); - - bool load_success; - - for (unsigned int i = 0; i < nPlugins; i++) { - Jupiter::ReferenceString plugin = Jupiter::ReferenceString::getWord(pluginList, i, WHITESPACE); - - load_start = std::chrono::steady_clock::now(); - load_success = Jupiter::Plugin::load(plugin) != nullptr; - time_taken = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - load_start).count()) / 1000.0; - - if (load_success) - printf("\"%.*s\" loaded successfully (%fms)." ENDL, static_cast(plugin.size()), plugin.ptr(), time_taken); - else - fprintf(stderr, "WARNING: Failed to load plugin \"%.*s\" (%fms)!" ENDL, static_cast(plugin.size()), plugin.ptr(), time_taken); - } - - // OnPostInitialize - for (const auto& plugin : Jupiter::plugins) { - plugin->OnPostInitialize(); - } - } + initialize_plugins(); printf("Initialization completed in %f milliseconds." ENDL, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - Jupiter::g_start_time).count()) / 1000.0 ); diff --git a/src/Jupiter b/src/Jupiter index 703aa97..b0e7aad 160000 --- a/src/Jupiter +++ b/src/Jupiter @@ -1 +1 @@ -Subproject commit 703aa97c533ab7d942d6d9ef07bbfc8c0d0dfb35 +Subproject commit b0e7aad5e3769ec8f248c123e8142f0f57f38b58 diff --git a/src/Plugins/IRC.Core/IRC_Core.cpp b/src/Plugins/IRC.Core/IRC_Core.cpp index 20707ef..ec57428 100644 --- a/src/Plugins/IRC.Core/IRC_Core.cpp +++ b/src/Plugins/IRC.Core/IRC_Core.cpp @@ -24,9 +24,14 @@ using namespace Jupiter::literals; IRCCorePlugin::~IRCCorePlugin() { + // Destroy all IRC connections on plugin unload + while (serverManager->size()) { + serverManager->freeServer(size_t{0}); + } } bool IRCCorePlugin::initialize() { + // TODO: initialize() isn't bringing in generic commands from already-loaded plugins const Jupiter::ReadableString &serverList = this->config.get("Servers"_jrs); if (serverList != nullptr) { serverManager->setConfig(this->config); @@ -53,12 +58,12 @@ int IRCCorePlugin::think() { } void IRCCorePlugin::OnGenericCommandAdd(Jupiter::GenericCommand &in_command) { - m_wrapped_commands.emplace_back(in_command); + m_wrapped_commands.emplace_back(new GenericCommandWrapperIRCCommand{in_command}); } void IRCCorePlugin::OnGenericCommandRemove(Jupiter::GenericCommand &in_command) { for (auto itr = m_wrapped_commands.begin(); itr != m_wrapped_commands.end(); ++itr) { - if (&itr->getGenericCommand() == &in_command) { + if (&(*itr)->getGenericCommand() == &in_command) { m_wrapped_commands.erase(itr); return; } diff --git a/src/Plugins/IRC.Core/IRC_Core.h b/src/Plugins/IRC.Core/IRC_Core.h index bf2192e..8a58dbc 100644 --- a/src/Plugins/IRC.Core/IRC_Core.h +++ b/src/Plugins/IRC.Core/IRC_Core.h @@ -58,7 +58,8 @@ public: ~IRCCorePlugin(); private: - std::vector m_wrapped_commands; + // Wrapping in unique_ptr to workaround erroneous firing of events when vector resizes + std::vector> m_wrapped_commands; }; #endif // _IRC_CORE_H_HEADER \ No newline at end of file diff --git a/src/Plugins/PluginManager/PluginManager.cpp b/src/Plugins/PluginManager/PluginManager.cpp index 809304e..20cff87 100644 --- a/src/Plugins/PluginManager/PluginManager.cpp +++ b/src/Plugins/PluginManager/PluginManager.cpp @@ -18,9 +18,13 @@ #include #include "Jupiter/Functions.h" +#include "Jupiter/Timer.h" // This timer implementation isn't great, but it'll work for delaying reloads +#include "jessilib/word_split.hpp" +#include "jessilib/unicode.hpp" #include "PluginManager.h" using namespace Jupiter::literals; +using namespace std::literals; // Plugin Generic Command PluginGenericCommand::PluginGenericCommand() { @@ -42,36 +46,77 @@ Jupiter::GenericCommand::ResponseLine *PluginGenericCommand::trigger(const Jupit return result; } - if (parameters.matchi("load *")) { - if (Jupiter::Plugin::load(Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE)) == nullptr) { + auto find_plugin = [](std::string_view name) { + for (const auto& plugin : Jupiter::plugins) { + std::string_view plugin_name = plugin->getName(); + if (jessilib::equalsi(plugin_name, name)) { + return plugin; + } + } + + return static_cast(nullptr); + }; + + auto parameters_view = static_cast(parameters); + auto split_params = jessilib::word_split_once_view(parameters_view, WHITESPACE_SV); + if (jessilib::starts_withi(parameters_view, "load "sv)) { + if (Jupiter::Plugin::load(split_params.second) == nullptr) { return result->set("Error: Failed to load plugin."_jrs, GenericCommand::DisplayType::PublicError); } return result->set("Plugin successfully loaded."_jrs, GenericCommand::DisplayType::PublicSuccess); } - if (parameters.matchi("unload *")) - { - Jupiter::ReferenceString pluginName = Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE); - if (Jupiter::Plugin::get(pluginName) == nullptr) { + if (jessilib::starts_withi(parameters_view, "unload "sv)) { + auto plugin = find_plugin(split_params.second); + if (plugin == nullptr) { return result->set("Error: Plugin does not exist."_jrs, GenericCommand::DisplayType::PublicError); } - if (Jupiter::Plugin::free(pluginName) == false) { + if (!Jupiter::Plugin::free(plugin)) { return result->set("Error: Failed to unload plugin."_jrs, GenericCommand::DisplayType::PublicError); } return result->set("Plugin successfully unloaded."_jrs, GenericCommand::DisplayType::PublicSuccess); } - return result->set("Error: Invalid Syntax. Syntax: plugin {[list], , }"_jrs, GenericCommand::DisplayType::PrivateError); + + if (jessilib::starts_withi(parameters_view, "reload"sv)) { + if (split_params.second.empty() + || split_params.second == "*") { + // Reinitialize all plugins on next tick + new Jupiter::Timer(1, std::chrono::milliseconds{0}, [](unsigned int, void*) { + Jupiter::reinitialize_plugins(); + }, true); + + return result->set("Triggering full plugin reload..."_jrs, GenericCommand::DisplayType::PublicSuccess); + } + else { + // A specific plugin + auto plugin = find_plugin(split_params.second); + if (plugin == nullptr) { + return result->set("Error: Plugin does not exist."_jrs, GenericCommand::DisplayType::PublicError); + } + + std::string_view plugin_name = plugin->getName(); + if (!Jupiter::Plugin::free(plugin)) { + return result->set("Error: Failed to unload plugin."_jrs, GenericCommand::DisplayType::PublicError); + } + + if (Jupiter::Plugin::load(plugin_name) == nullptr) { + return result->set("Error: Failed to load plugin."_jrs, GenericCommand::DisplayType::PublicError); + } + + return result->set("Plugin successfully reloaded."_jrs, GenericCommand::DisplayType::PublicSuccess); + } + } + return result->set("Error: Invalid Syntax. Syntax: plugin {[list], , , [all|plugin]}"_jrs, GenericCommand::DisplayType::PrivateError); } -const Jupiter::ReadableString &PluginGenericCommand::getHelp(const Jupiter::ReadableString ¶meters) -{ +const Jupiter::ReadableString &PluginGenericCommand::getHelp(const Jupiter::ReadableString ¶meters) { static STRING_LITERAL_AS_NAMED_REFERENCE(loadHelp, "Loads a plugin by file name. Do not include a file extension. Syntax: plugin load "); static STRING_LITERAL_AS_NAMED_REFERENCE(unloadHelp, "Unloads a plugin by name. Syntax: plugin unload "); static STRING_LITERAL_AS_NAMED_REFERENCE(listHelp, "Lists all of the plugins currently loaded. Syntax: plugin [list]"); - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Manages plugins. Syntax: plugin {[list], , }"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Manages plugins. Syntax: plugin {[list], , , [plugin]}"); if (parameters.equalsi(STRING_LITERAL_AS_REFERENCE("load"))) { return loadHelp; diff --git a/src/jessilib b/src/jessilib index 467e2b8..9d5d8b0 160000 --- a/src/jessilib +++ b/src/jessilib @@ -1 +1 @@ -Subproject commit 467e2b885382286809fd68a013dbde0a5385bc95 +Subproject commit 9d5d8b0abba01eaccd15d79ef63d2f7d13e94b50