diff --git a/NEWS b/NEWS index f364bdb3e..02eb33839 100644 --- a/NEWS +++ b/NEWS @@ -9,12 +9,14 @@ DFHack Future Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Multiple contexts can now be specified when adding keybindings + Plugin system is no longer restricted to plugins that exist on startup Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() Lua viewscreens can choose not to intercept the OPTIONS keybinding New internal commands kill-lua: Interrupt running Lua scripts + type: Show where a command is implemented New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) @@ -40,6 +42,7 @@ DFHack Future - fixed assigning quality to items - made "esc" work properly gui/gm-editor handles lua tables properly + help: now recognizes built-in commands, like "help" manipulator: fixed crash when selecting custom professions when none are found remotefortressreader: fixed crash when attempting to send map info when no map was loaded search: fixed crash in unit list after cancelling a job @@ -74,6 +77,9 @@ DFHack Future - "lever pull" can be used to pull the currently-selected lever memview: Fixed display issue nyan: Can now be stopped with dfhack-run + plug: + - lists all plugins + - shows state and number of commands in plugins quicksave: Restricted to fortress mode remotefortressreader: Exposes more information search: diff --git a/library/Core.cpp b/library/Core.cpp index d48f45199..3cb86896a 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -386,12 +386,12 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: std::vector possible; auto plug_mgr = Core::getInstance().getPluginManager(); - for(size_t i = 0; i < plug_mgr->size(); i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); + const Plugin * plug = it->second; for (size_t j = 0; j < plug->size(); j++) { - const PluginCommand &pcmd = plug->operator[](j); + const PluginCommand &pcmd = (*plug)[j]; if (pcmd.isHotkeyCommand()) continue; if (pcmd.name.substr(0, first.size()) == first) @@ -485,6 +485,43 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } +string getBuiltinCommand(std::string cmd) +{ + std::string builtin = ""; + if (cmd == "ls" || + cmd == "help" || + cmd == "type" || + cmd == "load" || + cmd == "unload" || + cmd == "reload" || + cmd == "enable" || + cmd == "disable" || + cmd == "plug" || + cmd == "type" || + cmd == "keybinding" || + cmd == "fpause" || + cmd == "cls" || + cmd == "die" || + cmd == "kill-lua" || + cmd == "script" || + cmd == "hide" || + cmd == "show" || + cmd == "sc-script" + ) + builtin = cmd; + + else if (cmd == "?" || cmd == "man") + builtin = "help"; + + else if (cmd == "dir") + builtin = "ls"; + + else if (cmd == "clear") + builtin = "cls"; + + return builtin; +} + command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; @@ -499,8 +536,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v first[i] = '/'; } } + // let's see what we actually got - if(first=="help" || first == "?" || first == "man") + string builtin = getBuiltinCommand(first); + if (builtin == "help") { if(!parts.size()) { @@ -516,21 +555,26 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v " help|?|man - This text.\n" " help COMMAND - Usage help for the given command.\n" " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls - Clear the console.\n" + " cls|clear - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" " keybinding - Modify bindings of commands to keys\n" "Plugin management (useful for developers):\n" " plug [PLUGIN|v] - List plugin state and description.\n" - " load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" + " load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n" + " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n" + " reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n" ); con.print("\nDFHack version %s.\n", Version::dfhack_version()); } else if (parts.size() == 1) { + if (getBuiltinCommand(parts[0]).size()) + { + con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl; + return CR_NOT_IMPLEMENTED; + } Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); if (plug) { for (size_t j = 0; j < plug->size();j++) @@ -564,97 +608,58 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } con.printerr("Unknown command: %s\n", parts[0].c_str()); + return CR_FAILURE; } else { con.printerr("not implemented yet\n"); + return CR_NOT_IMPLEMENTED; } } - else if( first == "load" ) + else if (builtin == "load" || builtin == "unload" || builtin == "reload") { - if(parts.size()) - { - string & plugname = parts[0]; - if(plugname == "all") - { - for(size_t i = 0; i < plug_mgr->size();i++) - { - Plugin * plug = (plug_mgr->operator[](i)); - plug->load(con); - } - } - else - { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } - else - { - plug->load(con); - } - } - } - } - else if( first == "reload" ) - { - if(parts.size()) + bool all = false; + bool load = (builtin == "load"); + bool unload = (builtin == "unload"); + if (parts.size()) { - string & plugname = parts[0]; - if(plugname == "all") + for (auto p = parts.begin(); p != parts.end(); p++) { - for(size_t i = 0; i < plug_mgr->size();i++) + if (p->size() && (*p)[0] == '-') { - Plugin * plug = (plug_mgr->operator[](i)); - plug->reload(con); + if (p->find('a') != string::npos) + all = true; } } - else + if (all) { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } + if (load) + plug_mgr->loadAll(); + else if (unload) + plug_mgr->unloadAll(); else - { - plug->reload(con); - } - } - } - } - else if( first == "unload" ) - { - if(parts.size()) - { - string & plugname = parts[0]; - if(plugname == "all") - { - for(size_t i = 0; i < plug_mgr->size();i++) - { - Plugin * plug = (plug_mgr->operator[](i)); - plug->unload(con); - } + plug_mgr->reloadAll(); + return CR_OK; } - else + for (auto p = parts.begin(); p != parts.end(); p++) { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } + if (!p->size() || (*p)[0] == '-') + continue; + if (load) + plug_mgr->load(*p); + else if (unload) + plug_mgr->unload(*p); else - { - plug->unload(con); - } + plug_mgr->reload(*p); } } + else + con.printerr("%s: no arguments\n", builtin.c_str()); } - else if( first == "enable" || first == "disable" ) + else if( builtin == "enable" || builtin == "disable" ) { CoreSuspender suspend; - bool enable = (first == "enable"); + bool enable = (builtin == "enable"); if(parts.size()) { @@ -673,7 +678,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } - Plugin * plug = plug_mgr->getPluginByName(part); + Plugin * plug = (*plug_mgr)[part]; if(!plug) { @@ -691,14 +696,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v else if (!plug->can_set_enabled()) { res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); + con.printerr("Cannot %s plugin: %s\n", builtin.c_str(), part.c_str()); } else { res = plug->set_enabled(con, enable); if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); + con.printerr("Could not %s plugin: %s\n", builtin.c_str(), part.c_str()); } } @@ -706,9 +711,9 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else { - for(size_t i = 0; i < plug_mgr->size();i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - Plugin * plug = (plug_mgr->operator[](i)); + Plugin * plug = it->second; if (!plug->can_be_enabled()) continue; con.print( @@ -720,7 +725,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } } - else if(first == "ls" || first == "dir") + else if (builtin == "ls" || builtin == "dir") { bool all = false; if (parts.size() && parts[0] == "-a") @@ -731,10 +736,18 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(parts.size()) { string & plugname = parts[0]; - const Plugin * plug = plug_mgr->getPluginByName(plugname); + const Plugin * plug = (*plug_mgr)[plugname]; if(!plug) { - con.printerr("There's no plugin called %s!\n",plugname.c_str()); + con.printerr("There's no plugin called %s!\n", plugname.c_str()); + } + else if (plug->getState() != Plugin::PS_LOADED) + { + con.printerr("Plugin %s is not loaded.\n", plugname.c_str()); + } + else if (!plug->size()) + { + con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str()); } else for (size_t j = 0; j < plug->size();j++) { @@ -749,27 +762,28 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { con.print( "builtin:\n" - " help|?|man - This text or help specific to a plugin.\n" - " ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " kill-lua - Stop an active Lua script\n" - " keybinding - Modify bindings of commands to keys\n" - " script FILENAME - Run the commands specified in a file.\n" - " sc-script - Automatically run specified scripts on state change events\n" - " plug [PLUGIN|v] - List plugin state and detailed description.\n" - " load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" - " enable/disable PLUGIN - Enable or disable a plugin if supported.\n" + " help|?|man - This text or help specific to a plugin.\n" + " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately\n" + " kill-lua - Stop an active Lua script\n" + " keybinding - Modify bindings of commands to keys\n" + " script FILENAME - Run the commands specified in a file.\n" + " sc-script - Automatically run specified scripts on state change events\n" + " plug [PLUGIN|v] - List plugin state and detailed description.\n" + " load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n" + " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n" + " reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n" + " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n" + " type COMMAND - Display information about where a command is implemented\n" "\n" "plugins:\n" ); std::set out; - for(size_t i = 0; i < plug_mgr->size();i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); + const Plugin * plug = it->second; if(!plug->size()) continue; for (size_t j = 0; j < plug->size();j++) @@ -795,17 +809,85 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } } - else if(first == "plug") + else if (builtin == "plug") { - for(size_t i = 0; i < plug_mgr->size();i++) + const char *header_format = "%25s %10s %4s\n"; + const char *row_format = "%25s %10s %4i\n"; + con.print(header_format, "Name", "State", "Cmds"); + + plug_mgr->refresh(); + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); - if(!plug->size()) + const Plugin * plug = it->second; + if (!plug) continue; - con.print("%s\n", plug->getName().c_str()); + if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) + continue; + color_value color; + switch (plug->getState()) + { + case Plugin::PS_LOADED: + color = COLOR_LIGHTGREEN; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; + } + con.color(color); + con.print(row_format, + plug->getName().c_str(), + Plugin::getStateDescription(plug->getState()), + plug->size() + ); + con.color(COLOR_RESET); + } + } + else if (builtin == "type") + { + if (!parts.size()) + { + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; + } + con << parts[0]; + string builtin_cmd = getBuiltinCommand(parts[0]); + Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin_cmd.size()) + { + con << " is a built-in command"; + if (builtin_cmd != parts[0]) + con << " (aliased to " << builtin_cmd << ")"; + con << "." << std::endl; + } + else if (plug) + { + con << " is part of plugin " << plug->getName() << "." << std::endl; + } + else if (findScript(this->p->getPath(), parts[0] + ".lua").size()) + { + con << " is a Lua script." << std::endl; + } + else if (findScript(this->p->getPath(), parts[0] + ".rb").size()) + { + con << " is a Ruby script." << std::endl; + } + else + { + con << " is not recognized." << std::endl; + return CR_FAILURE; } } - else if(first == "keybinding") + else if (builtin == "keybinding") { if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) { @@ -853,12 +935,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v << Gui::getFocusString(Core::getTopViewscreen()) << endl; } } - else if(first == "fpause") + else if (builtin == "fpause") { World::SetPauseState(true); con.print("The game was forced to pause!\n"); } - else if(first == "cls") + else if (builtin == "cls") { if (con.is_console()) ((Console&)con).clear(); @@ -868,11 +950,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_NEEDS_CONSOLE; } } - else if(first == "die") + else if (builtin == "die") { _exit(666); } - else if(first == "kill-lua") + else if (builtin == "kill-lua") { bool force = false; for (auto it = parts.begin(); it != parts.end(); ++it) @@ -883,7 +965,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if (!Lua::Interrupt(force)) con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); } - else if(first == "script") + else if (builtin == "script") { if(parts.size() == 1) { @@ -896,7 +978,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } - else if(first=="hide") + else if (builtin=="hide") { if (!getConsole().hide()) { @@ -905,7 +987,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } return CR_OK; } - else if(first=="show") + else if (builtin=="show") { if (!getConsole().show()) { @@ -914,7 +996,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } return CR_OK; } - else if(first == "sc-script") + else if (builtin == "sc-script") { if (parts.size() < 1) { @@ -1338,7 +1420,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); - plug_mgr->init(this); + plug_mgr->init(); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c23cfe22a..d601b198a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -39,6 +39,7 @@ distribution. #include "DataIdentity.h" #include "DataFuncs.h" #include "DFHackVersion.h" +#include "PluginManager.h" #include "modules/World.h" #include "modules/Gui.h" diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 8c0e5ef1a..7992373a9 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -49,6 +49,7 @@ distribution. #include "MiscUtils.h" #include "DFHackVersion.h" +#include "PluginManager.h" #include "df/job.h" #include "df/job_item.h" diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 410c25f88..0904ff8f9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -40,6 +40,7 @@ distribution. #include "LuaTools.h" #include "DataFuncs.h" +#include "PluginManager.h" #include "MiscUtils.h" #include diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 418b5a57b..6a2b52c71 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -52,6 +52,31 @@ using namespace tthread; #include +#define MUTEX_GUARD(lock) auto lock_##__LINE__ = make_mutex_guard(lock); +template +tthread::lock_guard make_mutex_guard (T *mutex) +{ + return tthread::lock_guard(*mutex); +} + +#if defined(_LINUX) + static const string plugin_suffix = ".plug.so"; +#elif defined(_DARWIN) + static const string plugin_suffix = ".plug.dylib"; +#else + static const string plugin_suffix = ".plug.dll"; +#endif + +static string getPluginPath() +{ + return Core::getInstance().getHackPath() + "plugins/"; +} + +static string getPluginPath (std::string name) +{ + return getPluginPath() + name + plugin_suffix; +} + struct Plugin::RefLock { RefLock() @@ -156,18 +181,12 @@ struct Plugin::LuaEvent : public Lua::Event::Owner { } }; -Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) +Plugin::Plugin(Core * core, const std::string & path, + const std::string &name, PluginManager * pm) + :name(name), + path(path), + parent(pm) { - filename = filepath; - parent = pm; - name.reserve(_filename.size()); - for(size_t i = 0; i < _filename.size();i++) - { - char ch = _filename[i]; - if(ch == '.') - break; - name.append(1,ch); - } plugin_lib = 0; plugin_init = 0; plugin_globals = 0; @@ -191,6 +210,24 @@ Plugin::~Plugin() delete access; } +const char *Plugin::getStateDescription (plugin_state state) +{ + switch (state) + { +#define map(s, desc) case s: return desc; break; + map(PS_LOADED, "loaded") + map(PS_UNLOADED, "unloaded") + map(PS_BROKEN, "broken") + map(PS_LOADING, "loading") + map(PS_UNLOADING, "unloading") + map(PS_DELETED, "deleted") +#undef map + default: + return "unknown"; + break; + } +} + bool Plugin::load(color_ostream &con) { { @@ -199,8 +236,10 @@ bool Plugin::load(color_ostream &con) { return true; } - else if(state != PS_UNLOADED) + else if(state != PS_UNLOADED && state != PS_DELETED) { + if (state == PS_BROKEN) + con.printerr("Plugin %s is broken - cannot be loaded\n", name.c_str()); return false; } state = PS_LOADING; @@ -208,16 +247,24 @@ bool Plugin::load(color_ostream &con) // enter suspend CoreSuspender suspend; // open the library, etc - fprintf(stderr, "loading plugin %s\n", filename.c_str()); - DFLibrary * plug = OpenPlugin(filename.c_str()); + fprintf(stderr, "loading plugin %s\n", name.c_str()); + DFLibrary * plug = OpenPlugin(path.c_str()); if(!plug) { - con.printerr("Can't load plugin %s\n", filename.c_str()); RefAutolock lock(access); - state = PS_BROKEN; - return false; + if (!Filesystem::isfile(path)) + { + con.printerr("Plugin %s does not exist on disk\n", name.c_str()); + state = PS_DELETED; + return false; + } + else { + con.printerr("Can't load plugin %s\n", name.c_str()); + state = PS_UNLOADED; + return false; + } } - #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN + #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED #define plugin_check_symbol(sym) \ if (!LookupPlugin(plug, sym)) \ { \ @@ -226,14 +273,20 @@ bool Plugin::load(color_ostream &con) return false; \ } - plugin_check_symbol("name") - plugin_check_symbol("version") + plugin_check_symbol("plugin_name") + plugin_check_symbol("plugin_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") - const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); - const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); - const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "git_description"); + const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name"); + if (name != *plug_name) + { + con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name); + plugin_abort_load; + return false; + } + const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); + const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); const char *dfhack_version = Version::dfhack_version(); const char *dfhack_git_desc = Version::git_description(); @@ -252,10 +305,7 @@ bool Plugin::load(color_ostream &con) *plug_name, plug_git_desc, dfhack_git_desc); } else - { con.printerr("Warning: Plugin %s missing git information\n", *plug_name); - plug_git_desc = "unknown"; - } bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS")) { @@ -292,7 +342,6 @@ bool Plugin::load(color_ostream &con) plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); index_lua(plug); - this->name = *plug_name; plugin_lib = plug; commands.clear(); if(plugin_init(con,commands) == CR_OK) @@ -303,6 +352,7 @@ bool Plugin::load(color_ostream &con) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) con.printerr("Plugin %s has no enabled var!\n", name.c_str()); fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc); + fflush(stderr); return true; } else @@ -364,11 +414,13 @@ bool Plugin::unload(color_ostream &con) return false; } } - else if(state == PS_UNLOADED) + else if(state == PS_UNLOADED || state == PS_DELETED) { access->unlock(); return true; } + else if (state == PS_BROKEN) + con.printerr("Plugin %s is broken - cannot be unloaded\n", name.c_str()); access->unlock(); return false; } @@ -741,64 +793,155 @@ bool PluginExports::bind(DFLibrary *lib) return true; } -PluginManager::PluginManager(Core * core) +PluginManager::PluginManager(Core * core) : core(core) { + plugin_mutex = new recursive_mutex(); cmdlist_mutex = new mutex(); ruby = NULL; } PluginManager::~PluginManager() { - for(size_t i = 0; i < all_plugins.size();i++) + for (auto it = begin(); it != end(); ++it) { - delete all_plugins[i]; + Plugin *p = it->second; + delete p; } all_plugins.clear(); + delete plugin_mutex; delete cmdlist_mutex; } -void PluginManager::init(Core * core) +void PluginManager::init() { -#ifdef LINUX_BUILD - string path = core->getHackPath() + "plugins/"; -#ifdef _DARWIN - const string searchstr = ".plug.dylib"; -#else - const string searchstr = ".plug.so"; -#endif -#else - string path = core->getHackPath() + "plugins\\"; - const string searchstr = ".plug.dll"; -#endif - vector filez; - Filesystem::listdir(path, filez); - for(size_t i = 0; i < filez.size();i++) + loadAll(); +} + +bool PluginManager::addPlugin(string name) +{ + if (all_plugins.find(name) != all_plugins.end()) + { + Core::printerr("Plugin already exists: %s\n", name.c_str()); + return false; + } + string path = getPluginPath(name); + if (!Filesystem::isfile(path)) + { + Core::printerr("Plugin does not exist: %s\n", name.c_str()); + return false; + } + Plugin * p = new Plugin(core, path, name, this); + all_plugins[name] = p; + return true; +} + +vector PluginManager::listPlugins() +{ + vector results; + vector files; + Filesystem::listdir(getPluginPath(), files); + for (auto file = files.begin(); file != files.end(); ++file) { - if(hasEnding(filez[i],searchstr)) + if (hasEnding(*file, plugin_suffix)) { - Plugin * p = new Plugin(core, path + filez[i], filez[i], this); - all_plugins.push_back(p); - // make all plugins load by default (until a proper design emerges). - p->load(core->getConsole()); + string shortname = file->substr(0, file->find(plugin_suffix)); + results.push_back(shortname); } } + return results; +} + +void PluginManager::refresh() +{ + MUTEX_GUARD(plugin_mutex); + auto files = listPlugins(); + for (auto f = files.begin(); f != files.end(); ++f) + { + if (!(*this)[*f]) + addPlugin(*f); + } +} + +bool PluginManager::load (const string &name) +{ + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name] && !addPlugin(name)) + return false; + Plugin *p = (*this)[name]; + if (!p) + { + Core::printerr("Plugin failed to register: %s\n", name.c_str()); + return false; + } + return p->load(core->getConsole()); } -Plugin *PluginManager::getPluginByName (const std::string & name) +bool PluginManager::loadAll() { - for(size_t i = 0; i < all_plugins.size(); i++) + MUTEX_GUARD(plugin_mutex); + auto files = listPlugins(); + bool ok = true; + // load all plugins in hack/plugins + for (auto f = files.begin(); f != files.end(); ++f) { - if(name == all_plugins[i]->name) - return all_plugins[i]; + if (!load(*f)) + ok = false; } - return 0; + return ok; +} + +bool PluginManager::unload (const string &name) +{ + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name]) + { + Core::printerr("Plugin does not exist: %s\n", name.c_str()); + return false; + } + return (*this)[name]->unload(core->getConsole()); +} + +bool PluginManager::unloadAll() +{ + MUTEX_GUARD(plugin_mutex); + bool ok = true; + // only try to unload plugins that are in all_plugins + for (auto it = begin(); it != end(); ++it) + { + if (!unload(it->first)) + ok = false; + } + return ok; +} + +bool PluginManager::reload (const string &name) +{ + // equivalent to "unload(name); load(name);" if plugin is recognized, + // "load(name);" otherwise + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name]) + return load(name); + if (!unload(name)) + return false; + return load(name); +} + +bool PluginManager::reloadAll() +{ + MUTEX_GUARD(plugin_mutex); + bool ok = true; + if (!unloadAll()) + ok = false; + if (!loadAll()) + ok = false; + return ok; } Plugin *PluginManager::getPluginByCommand(const std::string &command) { tthread::lock_guard lock(*cmdlist_mutex); - map ::iterator iter = belongs.find(command); - if (iter != belongs.end()) + map ::iterator iter = command_map.find(command); + if (iter != command_map.end()) return iter->second; else return NULL; @@ -829,50 +972,70 @@ bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen * void PluginManager::OnUpdate(color_ostream &out) { - for(size_t i = 0; i < all_plugins.size(); i++) - { - all_plugins[i]->on_update(out); - } + for (auto it = begin(); it != end(); ++it) + it->second->on_update(out); } void PluginManager::OnStateChange(color_ostream &out, state_change_event event) { - for(size_t i = 0; i < all_plugins.size(); i++) - { - all_plugins[i]->on_state_change(out, event); - } + for (auto it = begin(); it != end(); ++it) + it->second->on_state_change(out, event); } -// FIXME: doesn't check name collisions! void PluginManager::registerCommands( Plugin * p ) { cmdlist_mutex->lock(); vector & cmds = p->commands; - for(size_t i = 0; i < cmds.size();i++) + for (size_t i = 0; i < cmds.size();i++) { std::string name = cmds[i].name; - if (belongs.find(name) != belongs.end()) + if (command_map.find(name) != command_map.end()) { - fprintf(stderr, "Plugin %s re-implements command \"%s\" (from plugin %s)\n", - p->getName().c_str(), name.c_str(), belongs[name]->getName().c_str()); + core->printerr("Plugin %s re-implements command \"%s\" (from plugin %s)\n", + p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str()); + continue; } - belongs[name] = p; + command_map[name] = p; } if (p->plugin_eval_ruby) ruby = p; cmdlist_mutex->unlock(); } -// FIXME: doesn't check name collisions! void PluginManager::unregisterCommands( Plugin * p ) { cmdlist_mutex->lock(); vector & cmds = p->commands; for(size_t i = 0; i < cmds.size();i++) { - belongs.erase(cmds[i].name); + command_map.erase(cmds[i].name); } if (p->plugin_eval_ruby) ruby = NULL; cmdlist_mutex->unlock(); } + +Plugin *PluginManager::operator[] (std::string name) +{ + if (all_plugins.find(name) == all_plugins.end()) + { + if (Filesystem::isfile(getPluginPath(name))) + addPlugin(name); + } + return (all_plugins.find(name) != all_plugins.end()) ? all_plugins[name] : NULL; +} + +size_t PluginManager::size() +{ + return all_plugins.size(); +} + +std::map::iterator PluginManager::begin() +{ + return all_plugins.begin(); +} + +std::map::iterator PluginManager::end() +{ + return all_plugins.end(); +} diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index ab694c4a8..4604ed451 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -30,7 +30,6 @@ distribution. #include #include "DataDefs.h" -#include "PluginManager.h" #include #include @@ -39,7 +38,10 @@ distribution. * Internal header file of the lua wrapper. */ -namespace DFHack { namespace LuaWrapper { + +namespace DFHack { + struct FunctionReg; +namespace LuaWrapper { struct LuaToken; /** @@ -232,7 +234,7 @@ namespace DFHack { namespace LuaWrapper { /** * Wrap functions and add them to the table on the top of the stack. */ - using DFHack::FunctionReg; + typedef DFHack::FunctionReg FunctionReg; void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); int method_wrapper_core(lua_State *state, function_identity_base *id); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 680262bad..aba6aaa0f 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -139,22 +139,25 @@ namespace DFHack struct RefLock; struct RefAutolock; struct RefAutoinc; - enum plugin_state - { - PS_UNLOADED, - PS_LOADED, - PS_BROKEN, - PS_LOADING, - PS_UNLOADING - }; friend class PluginManager; friend class RPCService; - Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); + Plugin(DFHack::Core* core, const std::string& filepath, + const std::string &plug_name, PluginManager * pm); ~Plugin(); command_result on_update(color_ostream &out); command_result on_state_change(color_ostream &out, state_change_event event); void detach_connection(RPCService *svc); public: + enum plugin_state + { + PS_UNLOADED, + PS_LOADED, + PS_BROKEN, + PS_LOADING, + PS_UNLOADING, + PS_DELETED + }; + static const char *getStateDescription (plugin_state state); bool load(color_ostream &out); bool unload(color_ostream &out); bool reload(color_ostream &out); @@ -183,6 +186,10 @@ namespace DFHack { return name; } + plugin_state getState() + { + return state; + } void open_lua(lua_State *state, int table); @@ -196,7 +203,7 @@ namespace DFHack RefLock * access; std::vector commands; std::vector services; - std::string filename; + std::string path; std::string name; DFLibrary * plugin_lib; PluginManager * parent; @@ -247,34 +254,44 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); - void init(Core* core); + void init(); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); void unregisterCommands( Plugin * p ); // PUBLIC METHODS public: - Plugin *getPluginByName (const std::string & name); + // list names of all plugins present in hack/plugins + std::vector listPlugins(); + // create Plugin instances for any plugins in hack/plugins that aren't present in all_plugins + void refresh(); + + bool load (const std::string &name); + bool loadAll(); + bool unload (const std::string &name); + bool unloadAll(); + bool reload (const std::string &name); + bool reloadAll(); + + Plugin *getPluginByName (const std::string &name) { return (*this)[name]; } Plugin *getPluginByCommand (const std::string &command); void *getPluginExports(const std::string &name); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); - Plugin* operator[] (std::size_t index) - { - if(index >= all_plugins.size()) - return 0; - return all_plugins[index]; - }; - std::size_t size() - { - return all_plugins.size(); - } + Plugin* operator[] (const std::string name); + std::size_t size(); Plugin *ruby; + + std::map::iterator begin(); + std::map::iterator end(); // DATA private: + Core *core; + bool addPlugin(std::string name); + tthread::recursive_mutex * plugin_mutex; tthread::mutex * cmdlist_mutex; - std::map belongs; - std::vector all_plugins; + std::map command_map; + std::map all_plugins; std::string plugin_path; }; @@ -287,10 +304,10 @@ namespace DFHack } }; -#define DFHACK_PLUGIN_AUX(plugin_name, is_dev) \ - DFhackDataExport const char * name = plugin_name;\ - DFhackDataExport const char * version = DFHack::Version::dfhack_version();\ - DFhackDataExport const char * git_description = DFHack::Version::git_description();\ +#define DFHACK_PLUGIN_AUX(m_plugin_name, is_dev) \ + DFhackDataExport const char * plugin_name = m_plugin_name;\ + DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ + DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\ DFhackDataExport Plugin *plugin_self = NULL;\ std::vector _plugin_globals;\ DFhackDataExport std::vector* plugin_globals = &_plugin_globals; \ @@ -298,9 +315,9 @@ namespace DFHack /// You have to include DFHACK_PLUGIN("plugin_name") in every plugin you write - just once. Ideally at the top of the main file. #ifdef DEV_PLUGIN -#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, true) +#define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, true) #else -#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, false) +#define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, false) #endif #define DFHACK_PLUGIN_IS_ENABLED(varname) \ @@ -330,7 +347,11 @@ namespace DFHack #define DFHACK_LUA_EVENT(name) { #name, &name##_event } #define DFHACK_LUA_END { NULL, NULL } -#define REQUIRE_GLOBAL(global_name) \ - using df::global::global_name; \ + +#define REQUIRE_GLOBAL_NO_USE(global_name) \ static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \ (plugin_globals->push_back(#global_name), 0); + +#define REQUIRE_GLOBAL(global_name) \ + using df::global::global_name; \ + REQUIRE_GLOBAL_NO_USE(global_name) diff --git a/plugins/devel/counters.cpp b/plugins/devel/counters.cpp index 332945677..c03aaed72 100644 --- a/plugins/devel/counters.cpp +++ b/plugins/devel/counters.cpp @@ -31,7 +31,7 @@ command_result df_counters (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("probe"); +DFHACK_PLUGIN("counters"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -44,4 +44,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parame desired = true; fast = true; } else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") { - out.print("%s: %s\nUsage:\n%s", name, tagline, usage); + out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); return CR_OK; } else if (parameters[0] == "list") { if (!enabled) { @@ -417,7 +417,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector