Browse Source

Add 'plugin reload' subcommand; various bug fixes and cleanup

master
Jessica James 3 years ago
parent
commit
37c9f20429
  1. 10
      src/Bot/include/Jupiter_Bot.h
  2. 73
      src/Bot/src/Main.cpp
  3. 2
      src/Jupiter
  4. 9
      src/Plugins/IRC.Core/IRC_Core.cpp
  5. 3
      src/Plugins/IRC.Core/IRC_Core.h
  6. 67
      src/Plugins/PluginManager/PluginManager.cpp
  7. 2
      src/jessilib

10
src/Bot/include/Jupiter_Bot.h

@ -44,16 +44,18 @@
#include <chrono> #include <chrono>
/** Forward declaration */ namespace Jupiter {
namespace Jupiter { class Config; } /** Forward declarations */
class Config;
namespace Jupiter
{
/** Application config file */ /** Application config file */
JUPITER_BOT_API extern Jupiter::Config *g_config; JUPITER_BOT_API extern Jupiter::Config *g_config;
/** Application start time */ /** Application start time */
JUPITER_BOT_API extern std::chrono::steady_clock::time_point g_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 #endif // __cplusplus

73
src/Bot/src/Main.cpp

@ -28,6 +28,7 @@
#include "Jupiter/Socket.h" #include "Jupiter/Socket.h"
#include "Jupiter/Plugin.h" #include "Jupiter/Plugin.h"
#include "Jupiter/Timer.h" #include "Jupiter/Timer.h"
#include "jessilib/word_split.hpp"
#include "IRC_Bot.h" #include "IRC_Bot.h"
#include "Console_Command.h" #include "Console_Command.h"
#include "IRC_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<std::vector, Jupiter::ReferenceString>(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<double>(std::chrono::duration_cast<std::chrono::microseconds>(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() { [[noreturn]] void main_loop() {
Jupiter::ReferenceString command; Jupiter::ReferenceString command;
size_t index; 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<int>(configs_directory.size()), configs_directory.ptr()); printf("Plugin configs will be loaded from \"%.*s\"." ENDL, static_cast<int>(configs_directory.size()), configs_directory.ptr());
} }
puts("Loading plugins..."); initialize_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<double>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - load_start).count()) / 1000.0;
if (load_success)
printf("\"%.*s\" loaded successfully (%fms)." ENDL, static_cast<int>(plugin.size()), plugin.ptr(), time_taken);
else
fprintf(stderr, "WARNING: Failed to load plugin \"%.*s\" (%fms)!" ENDL, static_cast<int>(plugin.size()), plugin.ptr(), time_taken);
}
// OnPostInitialize
for (const auto& plugin : Jupiter::plugins) {
plugin->OnPostInitialize();
}
}
printf("Initialization completed in %f milliseconds." ENDL, static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - Jupiter::g_start_time).count()) / 1000.0 ); printf("Initialization completed in %f milliseconds." ENDL, static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - Jupiter::g_start_time).count()) / 1000.0 );

2
src/Jupiter

@ -1 +1 @@
Subproject commit 703aa97c533ab7d942d6d9ef07bbfc8c0d0dfb35 Subproject commit b0e7aad5e3769ec8f248c123e8142f0f57f38b58

9
src/Plugins/IRC.Core/IRC_Core.cpp

@ -24,9 +24,14 @@
using namespace Jupiter::literals; using namespace Jupiter::literals;
IRCCorePlugin::~IRCCorePlugin() { IRCCorePlugin::~IRCCorePlugin() {
// Destroy all IRC connections on plugin unload
while (serverManager->size()) {
serverManager->freeServer(size_t{0});
}
} }
bool IRCCorePlugin::initialize() { bool IRCCorePlugin::initialize() {
// TODO: initialize() isn't bringing in generic commands from already-loaded plugins
const Jupiter::ReadableString &serverList = this->config.get("Servers"_jrs); const Jupiter::ReadableString &serverList = this->config.get("Servers"_jrs);
if (serverList != nullptr) { if (serverList != nullptr) {
serverManager->setConfig(this->config); serverManager->setConfig(this->config);
@ -53,12 +58,12 @@ int IRCCorePlugin::think() {
} }
void IRCCorePlugin::OnGenericCommandAdd(Jupiter::GenericCommand &in_command) { 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) { void IRCCorePlugin::OnGenericCommandRemove(Jupiter::GenericCommand &in_command) {
for (auto itr = m_wrapped_commands.begin(); itr != m_wrapped_commands.end(); ++itr) { 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); m_wrapped_commands.erase(itr);
return; return;
} }

3
src/Plugins/IRC.Core/IRC_Core.h

@ -58,7 +58,8 @@ public:
~IRCCorePlugin(); ~IRCCorePlugin();
private: private:
std::vector<GenericCommandWrapperIRCCommand> m_wrapped_commands; // Wrapping in unique_ptr to workaround erroneous firing of events when vector resizes
std::vector<std::unique_ptr<GenericCommandWrapperIRCCommand>> m_wrapped_commands;
}; };
#endif // _IRC_CORE_H_HEADER #endif // _IRC_CORE_H_HEADER

67
src/Plugins/PluginManager/PluginManager.cpp

@ -18,9 +18,13 @@
#include <cstring> #include <cstring>
#include "Jupiter/Functions.h" #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" #include "PluginManager.h"
using namespace Jupiter::literals; using namespace Jupiter::literals;
using namespace std::literals;
// Plugin Generic Command // Plugin Generic Command
PluginGenericCommand::PluginGenericCommand() { PluginGenericCommand::PluginGenericCommand() {
@ -42,36 +46,77 @@ Jupiter::GenericCommand::ResponseLine *PluginGenericCommand::trigger(const Jupit
return result; return result;
} }
if (parameters.matchi("load *")) { auto find_plugin = [](std::string_view name) {
if (Jupiter::Plugin::load(Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE)) == nullptr) { for (const auto& plugin : Jupiter::plugins) {
std::string_view plugin_name = plugin->getName();
if (jessilib::equalsi(plugin_name, name)) {
return plugin;
}
}
return static_cast<Jupiter::Plugin*>(nullptr);
};
auto parameters_view = static_cast<std::string_view>(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("Error: Failed to load plugin."_jrs, GenericCommand::DisplayType::PublicError);
} }
return result->set("Plugin successfully loaded."_jrs, GenericCommand::DisplayType::PublicSuccess); return result->set("Plugin successfully loaded."_jrs, GenericCommand::DisplayType::PublicSuccess);
} }
if (parameters.matchi("unload *")) if (jessilib::starts_withi(parameters_view, "unload "sv)) {
{ auto plugin = find_plugin(split_params.second);
Jupiter::ReferenceString pluginName = Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE); if (plugin == nullptr) {
if (Jupiter::Plugin::get(pluginName) == nullptr) {
return result->set("Error: Plugin does not exist."_jrs, GenericCommand::DisplayType::PublicError); 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("Error: Failed to unload plugin."_jrs, GenericCommand::DisplayType::PublicError);
} }
return result->set("Plugin successfully unloaded."_jrs, GenericCommand::DisplayType::PublicSuccess); return result->set("Plugin successfully unloaded."_jrs, GenericCommand::DisplayType::PublicSuccess);
} }
return result->set("Error: Invalid Syntax. Syntax: plugin {[list], <load> <plugin>, <unload> <plugin>}"_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], <load> <plugin>, <unload> <plugin>, <reload> [all|plugin]}"_jrs, GenericCommand::DisplayType::PrivateError);
} }
const Jupiter::ReadableString &PluginGenericCommand::getHelp(const Jupiter::ReadableString &parameters) const Jupiter::ReadableString &PluginGenericCommand::getHelp(const Jupiter::ReadableString &parameters) {
{
static STRING_LITERAL_AS_NAMED_REFERENCE(loadHelp, "Loads a plugin by file name. Do not include a file extension. Syntax: plugin load <plugin>"); static STRING_LITERAL_AS_NAMED_REFERENCE(loadHelp, "Loads a plugin by file name. Do not include a file extension. Syntax: plugin load <plugin>");
static STRING_LITERAL_AS_NAMED_REFERENCE(unloadHelp, "Unloads a plugin by name. Syntax: plugin unload <plugin>"); static STRING_LITERAL_AS_NAMED_REFERENCE(unloadHelp, "Unloads a plugin by name. Syntax: plugin unload <plugin>");
static STRING_LITERAL_AS_NAMED_REFERENCE(listHelp, "Lists all of the plugins currently loaded. Syntax: plugin [list]"); 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], <load> <plugin>, <unload> <plugin>}"); static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Manages plugins. Syntax: plugin {[list], <load> <plugin>, <unload> <plugin>, <reload> [plugin]}");
if (parameters.equalsi(STRING_LITERAL_AS_REFERENCE("load"))) { if (parameters.equalsi(STRING_LITERAL_AS_REFERENCE("load"))) {
return loadHelp; return loadHelp;

2
src/jessilib

@ -1 +1 @@
Subproject commit 467e2b885382286809fd68a013dbde0a5385bc95 Subproject commit 9d5d8b0abba01eaccd15d79ef63d2f7d13e94b50
Loading…
Cancel
Save