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;
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<string> &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 <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())
continue;
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));
if(!plug->size())
const Plugin * plug = plug_mgr->getPluginByName(*f);
if (!plug)
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"))
{
@ -853,12 +937,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 +952,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 +967,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 +980,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 +989,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 +998,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 +1422,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;

@ -52,6 +52,31 @@ using namespace tthread;
#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
{
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,23 @@ 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());
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)
{
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);
state = PS_BROKEN;
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 +272,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 +304,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 +341,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 +351,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
@ -312,6 +361,7 @@ bool Plugin::load(color_ostream &con)
plugin_onupdate = 0;
reset_lua();
plugin_abort_load;
state = PS_BROKEN;
return false;
}
}
@ -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,144 @@ 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 <string> 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))
{
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);
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;
}
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)
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<tthread::mutex> lock(*cmdlist_mutex);
map <string, Plugin *>::iterator iter = belongs.find(command);
if (iter != belongs.end())
map <string, Plugin *>::iterator iter = command_map.find(command);
if (iter != command_map.end())
return iter->second;
else
return NULL;
@ -829,18 +961,14 @@ 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!
@ -848,15 +976,15 @@ void PluginManager::registerCommands( Plugin * p )
{
cmdlist_mutex->lock();
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;
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());
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)
ruby = p;
@ -870,9 +998,34 @@ void PluginManager::unregisterCommands( Plugin * p )
vector <PluginCommand> & 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<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 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 <PluginCommand> commands;
std::vector <RPCService*> services;
std::string filename;
std::string path;
std::string name;
DFLibrary * plugin_lib;
PluginManager * parent;
@ -247,34 +254,41 @@ 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);
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);
void *getPluginExports(const std::string &name);
command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & 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<std::string, Plugin*>::iterator begin();
std::map<std::string, Plugin*>::iterator end();
// DATA
private:
Core *core;
bool addPlugin(std::string name);
tthread::recursive_mutex * plugin_mutex;
tthread::mutex * cmdlist_mutex;
std::map <std::string, Plugin *> belongs;
std::vector <Plugin *> all_plugins;
std::map <std::string, Plugin*> command_map;
std::map <std::string, Plugin*> all_plugins;
std::string plugin_path;
};
@ -287,10 +301,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<std::string> _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.
#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 +344,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)

@ -31,7 +31,7 @@ command_result df_counters (color_ostream &out, vector <string> & parameters)
return CR_OK;
}
DFHACK_PLUGIN("probe");
DFHACK_PLUGIN("counters");
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 )
{
return CR_OK;
}
}

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

@ -343,7 +343,7 @@ static command_result stockflow_cmd(color_ostream &out, vector <string> & 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 <Plugin
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;
}