Several PluginManager improvements

* load/unload/reload are no longer restricted to plugins that exist
  on startup
* Names passed to DFHACK_PLUGIN must match the plugin's filename
  (remotefortressreader vs RemoteFortressReader, counters vs probe)
* "plug" output lists all plugins and state/command information
* Deleted plugins can be reloaded again if they are replaced
* load/unload/reload don't fail silently with broken plugins
* Built-in commands are recognized internally (e.g. "help help"
  does not display "help is not a recognized command"), although help
  for them is not yet implemented
* New command: "type" (bash-like) - shows where/how a command is
  implemented
* "plug" can accept multiple plugin names
* "ls" displays more information about unloaded/unrecognized plugins
* "load all" changed to "load -all" (or "load --all", "load -a", ...)
develop
lethosor 2015-08-14 16:11:23 -04:00
parent 2aba2da56d
commit 4fc6cb6f17
6 changed files with 477 additions and 222 deletions

@ -386,12 +386,12 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
std::vector<std::string> possible; std::vector<std::string> possible;
auto plug_mgr = Core::getInstance().getPluginManager(); 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++) for (size_t j = 0; j < plug->size(); j++)
{ {
const PluginCommand &pcmd = plug->operator[](j); const PluginCommand &pcmd = (*plug)[j];
if (pcmd.isHotkeyCommand()) if (pcmd.isHotkeyCommand())
continue; continue;
if (pcmd.name.substr(0, first.size()) == first) 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"; 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<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first_, vector<string> &parts)
{ {
std::string first = first_; std::string first = first_;
@ -499,8 +536,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
first[i] = '/'; first[i] = '/';
} }
} }
// let's see what we actually got // let's see what we actually got
if(first=="help" || first == "?" || first == "man") string builtin = getBuiltinCommand(first);
if (builtin == "help")
{ {
if(!parts.size()) 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|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n" " help COMMAND - Usage help for the given command.\n"
" ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\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" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" keybinding - Modify bindings of commands to keys\n" " keybinding - Modify bindings of commands to keys\n"
"Plugin management (useful for developers):\n" "Plugin management (useful for developers):\n"
" plug [PLUGIN|v] - List plugin state and description.\n" " plug [PLUGIN|v] - List plugin state and description.\n"
" load PLUGIN|all - Load a plugin by name or load all possible 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" " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload 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()); con.print("\nDFHack version %s.\n", Version::dfhack_version());
} }
else if (parts.size() == 1) 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]); Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (plug) { if (plug) {
for (size_t j = 0; j < plug->size();j++) 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()); con.printerr("Unknown command: %s\n", parts[0].c_str());
return CR_FAILURE;
} }
else else
{ {
con.printerr("not implemented yet\n"); 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()) bool all = false;
{ bool load = (builtin == "load");
string & plugname = parts[0]; bool unload = (builtin == "unload");
if(plugname == "all") if (parts.size())
{
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())
{ {
string & plugname = parts[0]; for (auto p = parts.begin(); p != parts.end(); p++)
if(plugname == "all")
{ {
for(size_t i = 0; i < plug_mgr->size();i++) if (p->size() && (*p)[0] == '-')
{ {
Plugin * plug = (plug_mgr->operator[](i)); if (p->find('a') != string::npos)
plug->reload(con); all = true;
} }
} }
else if (all)
{ {
Plugin * plug = plug_mgr->getPluginByName(plugname); if (load)
if(!plug) plug_mgr->loadAll();
{ else if (unload)
con.printerr("No such plugin\n"); plug_mgr->unloadAll();
}
else else
{ plug_mgr->reloadAll();
plug->reload(con); return CR_OK;
}
}
}
}
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);
}
} }
else for (auto p = parts.begin(); p != parts.end(); p++)
{ {
Plugin * plug = plug_mgr->getPluginByName(plugname); if (!p->size() || (*p)[0] == '-')
if(!plug) continue;
{ if (load)
con.printerr("No such plugin\n"); plug_mgr->load(*p);
} else if (unload)
plug_mgr->unload(*p);
else else
{ plug_mgr->reload(*p);
plug->unload(con);
}
} }
} }
else
con.printerr("%s: no arguments\n", builtin.c_str());
} }
else if( first == "enable" || first == "disable" ) else if( builtin == "enable" || builtin == "disable" )
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool enable = (first == "enable"); bool enable = (builtin == "enable");
if(parts.size()) 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) if(!plug)
{ {
@ -691,14 +696,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
else if (!plug->can_set_enabled()) else if (!plug->can_set_enabled())
{ {
res = CR_NOT_IMPLEMENTED; 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 else
{ {
res = plug->set_enabled(con, enable); res = plug->set_enabled(con, enable);
if (res != CR_OK || plug->is_enabled() != 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 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; if (!plug->can_be_enabled()) continue;
con.print( 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; bool all = false;
if (parts.size() && parts[0] == "-a") 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()) if(parts.size())
{ {
string & plugname = parts[0]; string & plugname = parts[0];
const Plugin * plug = plug_mgr->getPluginByName(plugname); const Plugin * plug = (*plug_mgr)[plugname];
if(!plug) 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++) 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( con.print(
"builtin:\n" "builtin:\n"
" help|?|man - This text or help specific to a plugin.\n" " help|?|man - This text or help specific to a plugin.\n"
" ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\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" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" kill-lua - Stop an active Lua script\n" " kill-lua - Stop an active Lua script\n"
" keybinding - Modify bindings of commands to keys\n" " keybinding - Modify bindings of commands to keys\n"
" script FILENAME - Run the commands specified in a file.\n" " script FILENAME - Run the commands specified in a file.\n"
" sc-script - Automatically run specified scripts on state change events\n" " sc-script - Automatically run specified scripts on state change events\n"
" plug [PLUGIN|v] - List plugin state and detailed description.\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" " 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" " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload 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" " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n"
" type COMMAND - Display information about where a command is implemented\n"
"\n" "\n"
"plugins:\n" "plugins:\n"
); );
std::set <sortable> out; std::set <sortable> 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()) if(!plug->size())
continue; continue;
for (size_t j = 0; j < plug->size();j++) for (size_t j = 0; j < plug->size();j++)
@ -795,17 +809,87 @@ 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");
vector<string> plugins;
if (parts.size())
plugins = parts;
else
plugins = plug_mgr->listPlugins();
for (auto f = plugins.begin(); f != plugins.end(); ++f)
{ {
const Plugin * plug = (plug_mgr->operator[](i)); const Plugin * plug = plug_mgr->getPluginByName(*f);
if(!plug->size()) if (!plug)
continue; continue;
con.print("%s\n", plug->getName().c_str()); 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,
f->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")) if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add"))
{ {
@ -853,12 +937,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
<< Gui::getFocusString(Core::getTopViewscreen()) << endl; << Gui::getFocusString(Core::getTopViewscreen()) << endl;
} }
} }
else if(first == "fpause") else if (builtin == "fpause")
{ {
World::SetPauseState(true); World::SetPauseState(true);
con.print("The game was forced to pause!\n"); con.print("The game was forced to pause!\n");
} }
else if(first == "cls") else if (builtin == "cls")
{ {
if (con.is_console()) if (con.is_console())
((Console&)con).clear(); ((Console&)con).clear();
@ -868,11 +952,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_NEEDS_CONSOLE; return CR_NEEDS_CONSOLE;
} }
} }
else if(first == "die") else if (builtin == "die")
{ {
_exit(666); _exit(666);
} }
else if(first == "kill-lua") else if (builtin == "kill-lua")
{ {
bool force = false; bool force = false;
for (auto it = parts.begin(); it != parts.end(); ++it) for (auto it = parts.begin(); it != parts.end(); ++it)
@ -883,7 +967,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
if (!Lua::Interrupt(force)) if (!Lua::Interrupt(force))
con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); 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) if(parts.size() == 1)
{ {
@ -896,7 +980,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
else if(first=="hide") else if (builtin=="hide")
{ {
if (!getConsole().hide()) if (!getConsole().hide())
{ {
@ -905,7 +989,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
return CR_OK; return CR_OK;
} }
else if(first=="show") else if (builtin=="show")
{ {
if (!getConsole().show()) if (!getConsole().show())
{ {
@ -914,7 +998,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
return CR_OK; return CR_OK;
} }
else if(first == "sc-script") else if (builtin == "sc-script")
{ {
if (parts.size() < 1) if (parts.size() < 1)
{ {
@ -1338,7 +1422,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(this); plug_mgr->init();
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;

@ -52,6 +52,31 @@ using namespace tthread;
#include <assert.h> #include <assert.h>
#define MUTEX_GUARD(lock) auto lock_##__LINE__ = make_mutex_guard(lock);
template <typename T>
tthread::lock_guard<T> make_mutex_guard (T *mutex)
{
return tthread::lock_guard<T>(*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 struct Plugin::RefLock
{ {
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_lib = 0;
plugin_init = 0; plugin_init = 0;
plugin_globals = 0; plugin_globals = 0;
@ -191,6 +210,24 @@ Plugin::~Plugin()
delete access; 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) bool Plugin::load(color_ostream &con)
{ {
{ {
@ -199,8 +236,10 @@ bool Plugin::load(color_ostream &con)
{ {
return true; 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; return false;
} }
state = PS_LOADING; state = PS_LOADING;
@ -208,16 +247,23 @@ bool Plugin::load(color_ostream &con)
// enter suspend // enter suspend
CoreSuspender suspend; CoreSuspender suspend;
// open the library, etc // open the library, etc
fprintf(stderr, "loading plugin %s\n", filename.c_str()); fprintf(stderr, "loading plugin %s\n", name.c_str());
DFLibrary * plug = OpenPlugin(filename.c_str()); if (!Filesystem::isfile(path))
{
con.printerr("Plugin %s does not exist on disk\n", name.c_str());
RefAutolock lock(access);
state = PS_DELETED;
return false;
}
DFLibrary * plug = OpenPlugin(path.c_str());
if(!plug) if(!plug)
{ {
con.printerr("Can't load plugin %s\n", filename.c_str()); con.printerr("Can't load plugin %s\n", name.c_str());
RefAutolock lock(access); RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
return false; 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) \ #define plugin_check_symbol(sym) \
if (!LookupPlugin(plug, sym)) \ if (!LookupPlugin(plug, sym)) \
{ \ { \
@ -226,14 +272,20 @@ bool Plugin::load(color_ostream &con)
return false; \ return false; \
} }
plugin_check_symbol("name") plugin_check_symbol("plugin_name")
plugin_check_symbol("version") plugin_check_symbol("plugin_version")
plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals") plugin_check_symbol("plugin_globals")
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); if (name != *plug_name)
const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "git_description"); {
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"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
const char *dfhack_version = Version::dfhack_version(); const char *dfhack_version = Version::dfhack_version();
const char *dfhack_git_desc = Version::git_description(); const char *dfhack_git_desc = Version::git_description();
@ -252,10 +304,7 @@ bool Plugin::load(color_ostream &con)
*plug_name, plug_git_desc, dfhack_git_desc); *plug_name, plug_git_desc, dfhack_git_desc);
} }
else else
{
con.printerr("Warning: Plugin %s missing git information\n", *plug_name); con.printerr("Warning: Plugin %s missing git information\n", *plug_name);
plug_git_desc = "unknown";
}
bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev");
if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS")) if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS"))
{ {
@ -292,7 +341,6 @@ bool Plugin::load(color_ostream &con)
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports");
index_lua(plug); index_lua(plug);
this->name = *plug_name;
plugin_lib = plug; plugin_lib = plug;
commands.clear(); commands.clear();
if(plugin_init(con,commands) == CR_OK) if(plugin_init(con,commands) == CR_OK)
@ -303,6 +351,7 @@ bool Plugin::load(color_ostream &con)
if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled)
con.printerr("Plugin %s has no enabled var!\n", name.c_str()); 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); fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc);
fflush(stderr);
return true; return true;
} }
else else
@ -312,6 +361,7 @@ bool Plugin::load(color_ostream &con)
plugin_onupdate = 0; plugin_onupdate = 0;
reset_lua(); reset_lua();
plugin_abort_load; plugin_abort_load;
state = PS_BROKEN;
return false; return false;
} }
} }
@ -364,11 +414,13 @@ bool Plugin::unload(color_ostream &con)
return false; return false;
} }
} }
else if(state == PS_UNLOADED) else if(state == PS_UNLOADED || state == PS_DELETED)
{ {
access->unlock(); access->unlock();
return true; return true;
} }
else if (state == PS_BROKEN)
con.printerr("Plugin %s is broken - cannot be unloaded\n", name.c_str());
access->unlock(); access->unlock();
return false; return false;
} }
@ -741,64 +793,144 @@ bool PluginExports::bind(DFLibrary *lib)
return true; return true;
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core) : core(core)
{ {
plugin_mutex = new recursive_mutex();
cmdlist_mutex = new mutex(); cmdlist_mutex = new mutex();
ruby = NULL; ruby = NULL;
} }
PluginManager::~PluginManager() 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(); all_plugins.clear();
delete plugin_mutex;
delete cmdlist_mutex; delete cmdlist_mutex;
} }
void PluginManager::init(Core * core) void PluginManager::init()
{ {
#ifdef LINUX_BUILD loadAll();
string path = core->getHackPath() + "plugins/"; }
#ifdef _DARWIN
const string searchstr = ".plug.dylib"; bool PluginManager::addPlugin(string name)
#else {
const string searchstr = ".plug.so"; if (all_plugins.find(name) != all_plugins.end())
#endif {
#else Core::printerr("Plugin already exists: %s\n", name.c_str());
string path = core->getHackPath() + "plugins\\"; return false;
const string searchstr = ".plug.dll"; }
#endif string path = getPluginPath(name);
vector <string> filez; if (!Filesystem::isfile(path))
Filesystem::listdir(path, filez);
for(size_t i = 0; i < filez.size();i++)
{ {
if(hasEnding(filez[i],searchstr)) 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<string> PluginManager::listPlugins()
{
vector<string> results;
vector<string> files;
Filesystem::listdir(getPluginPath(), files);
for (auto file = files.begin(); file != files.end(); ++file)
{
if (hasEnding(*file, plugin_suffix))
{ {
Plugin * p = new Plugin(core, path + filez[i], filez[i], this); string shortname = file->substr(0, file->find(plugin_suffix));
all_plugins.push_back(p); results.push_back(shortname);
// make all plugins load by default (until a proper design emerges).
p->load(core->getConsole());
} }
} }
return results;
}
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 load: %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) if (!load(*f))
return all_plugins[i]; 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) Plugin *PluginManager::getPluginByCommand(const std::string &command)
{ {
tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex); tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex);
map <string, Plugin *>::iterator iter = belongs.find(command); map <string, Plugin *>::iterator iter = command_map.find(command);
if (iter != belongs.end()) if (iter != command_map.end())
return iter->second; return iter->second;
else else
return NULL; return NULL;
@ -829,18 +961,14 @@ bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *
void PluginManager::OnUpdate(color_ostream &out) void PluginManager::OnUpdate(color_ostream &out)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for (auto it = begin(); it != end(); ++it)
{ it->second->on_update(out);
all_plugins[i]->on_update(out);
}
} }
void PluginManager::OnStateChange(color_ostream &out, state_change_event event) void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for (auto it = begin(); it != end(); ++it)
{ it->second->on_state_change(out, event);
all_plugins[i]->on_state_change(out, event);
}
} }
// FIXME: doesn't check name collisions! // FIXME: doesn't check name collisions!
@ -848,15 +976,15 @@ void PluginManager::registerCommands( Plugin * p )
{ {
cmdlist_mutex->lock(); cmdlist_mutex->lock();
vector <PluginCommand> & cmds = p->commands; vector <PluginCommand> & 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; 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", fprintf(stderr, "Plugin %s re-implements command \"%s\" (from plugin %s)\n",
p->getName().c_str(), name.c_str(), belongs[name]->getName().c_str()); p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str());
} }
belongs[name] = p; command_map[name] = p;
} }
if (p->plugin_eval_ruby) if (p->plugin_eval_ruby)
ruby = p; ruby = p;
@ -870,9 +998,34 @@ void PluginManager::unregisterCommands( Plugin * p )
vector <PluginCommand> & cmds = p->commands; vector <PluginCommand> & cmds = p->commands;
for(size_t i = 0; i < cmds.size();i++) 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) if (p->plugin_eval_ruby)
ruby = NULL; ruby = NULL;
cmdlist_mutex->unlock(); 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<std::string, Plugin*>::iterator PluginManager::begin()
{
return all_plugins.begin();
}
std::map<std::string, Plugin*>::iterator PluginManager::end()
{
return all_plugins.end();
}

@ -139,22 +139,25 @@ namespace DFHack
struct RefLock; struct RefLock;
struct RefAutolock; struct RefAutolock;
struct RefAutoinc; struct RefAutoinc;
enum plugin_state
{
PS_UNLOADED,
PS_LOADED,
PS_BROKEN,
PS_LOADING,
PS_UNLOADING
};
friend class PluginManager; friend class PluginManager;
friend class RPCService; 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(); ~Plugin();
command_result on_update(color_ostream &out); command_result on_update(color_ostream &out);
command_result on_state_change(color_ostream &out, state_change_event event); command_result on_state_change(color_ostream &out, state_change_event event);
void detach_connection(RPCService *svc); void detach_connection(RPCService *svc);
public: 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 load(color_ostream &out);
bool unload(color_ostream &out); bool unload(color_ostream &out);
bool reload(color_ostream &out); bool reload(color_ostream &out);
@ -183,6 +186,10 @@ namespace DFHack
{ {
return name; return name;
} }
plugin_state getState()
{
return state;
}
void open_lua(lua_State *state, int table); void open_lua(lua_State *state, int table);
@ -196,7 +203,7 @@ namespace DFHack
RefLock * access; RefLock * access;
std::vector <PluginCommand> commands; std::vector <PluginCommand> commands;
std::vector <RPCService*> services; std::vector <RPCService*> services;
std::string filename; std::string path;
std::string name; std::string name;
DFLibrary * plugin_lib; DFLibrary * plugin_lib;
PluginManager * parent; PluginManager * parent;
@ -247,34 +254,41 @@ namespace DFHack
friend class Plugin; friend class Plugin;
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void init(Core* core); void init();
void OnUpdate(color_ostream &out); void OnUpdate(color_ostream &out);
void OnStateChange(color_ostream &out, state_change_event event); void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
void unregisterCommands( Plugin * p ); void unregisterCommands( Plugin * p );
// PUBLIC METHODS // PUBLIC METHODS
public: public:
Plugin *getPluginByName (const std::string & name); std::vector<std::string> listPlugins();
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); Plugin *getPluginByCommand (const std::string &command);
void *getPluginExports(const std::string &name); void *getPluginExports(const std::string &name);
command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index) Plugin* operator[] (const std::string name);
{ std::size_t size();
if(index >= all_plugins.size())
return 0;
return all_plugins[index];
};
std::size_t size()
{
return all_plugins.size();
}
Plugin *ruby; Plugin *ruby;
std::map<std::string, Plugin*>::iterator begin();
std::map<std::string, Plugin*>::iterator end();
// DATA // DATA
private: private:
Core *core;
bool addPlugin(std::string name);
tthread::recursive_mutex * plugin_mutex;
tthread::mutex * cmdlist_mutex; tthread::mutex * cmdlist_mutex;
std::map <std::string, Plugin *> belongs; std::map <std::string, Plugin*> command_map;
std::vector <Plugin *> all_plugins; std::map <std::string, Plugin*> all_plugins;
std::string plugin_path; std::string plugin_path;
}; };
@ -287,10 +301,10 @@ namespace DFHack
} }
}; };
#define DFHACK_PLUGIN_AUX(plugin_name, is_dev) \ #define DFHACK_PLUGIN_AUX(m_plugin_name, is_dev) \
DFhackDataExport const char * name = plugin_name;\ DFhackDataExport const char * plugin_name = m_plugin_name;\
DFhackDataExport const char * version = DFHack::Version::dfhack_version();\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\
DFhackDataExport const char * git_description = DFHack::Version::git_description();\ DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\
DFhackDataExport Plugin *plugin_self = NULL;\ DFhackDataExport Plugin *plugin_self = NULL;\
std::vector<std::string> _plugin_globals;\ std::vector<std::string> _plugin_globals;\
DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \ DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \
@ -298,9 +312,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. /// 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 #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 #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 #endif
#define DFHACK_PLUGIN_IS_ENABLED(varname) \ #define DFHACK_PLUGIN_IS_ENABLED(varname) \
@ -330,7 +344,11 @@ namespace DFHack
#define DFHACK_LUA_EVENT(name) { #name, &name##_event } #define DFHACK_LUA_EVENT(name) { #name, &name##_event }
#define DFHACK_LUA_END { NULL, NULL } #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__) = \ static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \
(plugin_globals->push_back(#global_name), 0); (plugin_globals->push_back(#global_name), 0);
#define REQUIRE_GLOBAL(global_name) \
using df::global::global_name; \
REQUIRE_GLOBAL_NO_USE(global_name)

@ -31,7 +31,7 @@ command_result df_counters (color_ostream &out, vector <string> & parameters)
return CR_OK; return CR_OK;
} }
DFHACK_PLUGIN("probe"); DFHACK_PLUGIN("counters");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
@ -44,4 +44,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
return CR_OK; return CR_OK;
} }

@ -66,7 +66,7 @@ using namespace df::enums;
using namespace RemoteFortressReader; using namespace RemoteFortressReader;
using namespace std; using namespace std;
DFHACK_PLUGIN("RemoteFortressReader"); DFHACK_PLUGIN("remotefortressreader");
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
// Here go all the command declarations... // Here go all the command declarations...

@ -343,7 +343,7 @@ static command_result stockflow_cmd(color_ostream &out, vector <string> & parame
desired = true; desired = true;
fast = true; fast = true;
} else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") { } 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; return CR_OK;
} else if (parameters[0] == "list") { } else if (parameters[0] == "list") {
if (!enabled) { if (!enabled) {
@ -417,7 +417,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
enabled = true; enabled = true;
} }
commands.push_back(PluginCommand(name, tagline, stockflow_cmd, false, usage)); commands.push_back(PluginCommand(plugin_name, tagline, stockflow_cmd, false, usage));
return CR_OK; return CR_OK;
} }