879 lines
24 KiB
C++
879 lines
24 KiB
C++
/*
|
|
https://github.com/peterix/dfhack
|
|
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any
|
|
damages arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
purpose, including commercial applications, and to alter it and
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must
|
|
not claim that you wrote the original software. If you use this
|
|
software in a product, an acknowledgment in the product documentation
|
|
would be appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
must not be misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
distribution.
|
|
*/
|
|
|
|
#include "modules/EventManager.h"
|
|
#include "modules/Filesystem.h"
|
|
#include "Internal.h"
|
|
#include "Core.h"
|
|
#include "MemAccess.h"
|
|
#include "PluginManager.h"
|
|
#include "RemoteServer.h"
|
|
#include "Console.h"
|
|
#include "Types.h"
|
|
#include "VersionInfo.h"
|
|
|
|
#include "DataDefs.h"
|
|
#include "MiscUtils.h"
|
|
#include "DFHackVersion.h"
|
|
|
|
#include "LuaWrapper.h"
|
|
#include "LuaTools.h"
|
|
|
|
using namespace DFHack;
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
using namespace std;
|
|
|
|
#include "tinythread.h"
|
|
using namespace tthread;
|
|
|
|
#include <assert.h>
|
|
|
|
struct Plugin::RefLock
|
|
{
|
|
RefLock()
|
|
{
|
|
refcount = 0;
|
|
wakeup = new condition_variable();
|
|
mut = new mutex();
|
|
}
|
|
~RefLock()
|
|
{
|
|
delete wakeup;
|
|
delete mut;
|
|
}
|
|
void lock()
|
|
{
|
|
mut->lock();
|
|
}
|
|
void unlock()
|
|
{
|
|
mut->unlock();
|
|
}
|
|
void lock_add()
|
|
{
|
|
mut->lock();
|
|
refcount ++;
|
|
mut->unlock();
|
|
}
|
|
void lock_sub()
|
|
{
|
|
mut->lock();
|
|
if (--refcount == 0)
|
|
wakeup->notify_one();
|
|
mut->unlock();
|
|
}
|
|
void wait()
|
|
{
|
|
while(refcount)
|
|
{
|
|
wakeup->wait(*mut);
|
|
}
|
|
}
|
|
condition_variable * wakeup;
|
|
mutex * mut;
|
|
int refcount;
|
|
};
|
|
|
|
struct Plugin::RefAutolock
|
|
{
|
|
RefLock * lock;
|
|
RefAutolock(RefLock * lck):lock(lck){ lock->lock(); };
|
|
~RefAutolock(){ lock->unlock(); };
|
|
};
|
|
|
|
struct Plugin::RefAutoinc
|
|
{
|
|
RefLock * lock;
|
|
RefAutoinc(RefLock * lck):lock(lck){ lock->lock_add(); };
|
|
~RefAutoinc(){ lock->lock_sub(); };
|
|
};
|
|
|
|
struct Plugin::LuaCommand {
|
|
Plugin *owner;
|
|
std::string name;
|
|
int (*command)(lua_State *state);
|
|
|
|
LuaCommand(Plugin *owner, std::string name)
|
|
: owner(owner), name(name), command(NULL) {}
|
|
};
|
|
|
|
struct Plugin::LuaFunction {
|
|
Plugin *owner;
|
|
std::string name;
|
|
function_identity_base *identity;
|
|
bool silent;
|
|
|
|
LuaFunction(Plugin *owner, std::string name)
|
|
: owner(owner), name(name), identity(NULL), silent(false) {}
|
|
};
|
|
|
|
struct Plugin::LuaEvent : public Lua::Event::Owner {
|
|
LuaFunction handler;
|
|
Lua::Notification *event;
|
|
bool active;
|
|
int count;
|
|
|
|
LuaEvent(Plugin *owner, std::string name)
|
|
: handler(owner,name), event(NULL), active(false), count(0)
|
|
{
|
|
handler.silent = true;
|
|
}
|
|
|
|
void on_count_changed(int new_cnt, int delta) {
|
|
RefAutoinc lock(handler.owner->access);
|
|
count = new_cnt;
|
|
if (event)
|
|
event->on_count_changed(new_cnt, delta);
|
|
}
|
|
void on_invoked(lua_State *state, int nargs, bool from_c) {
|
|
RefAutoinc lock(handler.owner->access);
|
|
if (event)
|
|
event->on_invoked(state, nargs, from_c);
|
|
}
|
|
};
|
|
|
|
Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * 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;
|
|
plugin_shutdown = 0;
|
|
plugin_status = 0;
|
|
plugin_onupdate = 0;
|
|
plugin_onstatechange = 0;
|
|
plugin_rpcconnect = 0;
|
|
plugin_enable = 0;
|
|
plugin_is_enabled = 0;
|
|
state = PS_UNLOADED;
|
|
access = new RefLock();
|
|
}
|
|
|
|
Plugin::~Plugin()
|
|
{
|
|
if(state == PS_LOADED)
|
|
{
|
|
unload(Core::getInstance().getConsole());
|
|
}
|
|
delete access;
|
|
}
|
|
|
|
bool Plugin::load(color_ostream &con)
|
|
{
|
|
{
|
|
RefAutolock lock(access);
|
|
if(state == PS_LOADED)
|
|
{
|
|
return true;
|
|
}
|
|
else if(state != PS_UNLOADED)
|
|
{
|
|
return false;
|
|
}
|
|
state = PS_LOADING;
|
|
}
|
|
// enter suspend
|
|
CoreSuspender suspend;
|
|
// open the library, etc
|
|
fprintf(stderr, "loading plugin %s\n", filename.c_str());
|
|
DFLibrary * plug = OpenPlugin(filename.c_str());
|
|
if(!plug)
|
|
{
|
|
con.printerr("Can't load plugin %s\n", filename.c_str());
|
|
RefAutolock lock(access);
|
|
state = PS_BROKEN;
|
|
return false;
|
|
}
|
|
#define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN
|
|
#define plugin_check_symbol(sym) \
|
|
if (!LookupPlugin(plug, sym)) \
|
|
{ \
|
|
con.printerr("Plugin %s: missing symbol: %s\n", name.c_str(), sym); \
|
|
plugin_abort_load; \
|
|
return false; \
|
|
}
|
|
|
|
plugin_check_symbol("name")
|
|
plugin_check_symbol("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");
|
|
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
|
|
const char *dfhack_version = Version::dfhack_version();
|
|
const char *dfhack_git_desc = Version::git_description();
|
|
const char *plug_git_desc = plug_git_desc_ptr ? *plug_git_desc_ptr : "unknown";
|
|
if (strcmp(dfhack_version, *plug_version) != 0)
|
|
{
|
|
con.printerr("Plugin %s was not built for this version of DFHack.\n"
|
|
"Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, dfhack_version);
|
|
plugin_abort_load;
|
|
return false;
|
|
}
|
|
if (plug_git_desc_ptr)
|
|
{
|
|
if (strcmp(dfhack_git_desc, plug_git_desc) != 0)
|
|
con.printerr("Warning: Plugin %s compiled for DFHack %s, running DFHack %s\n",
|
|
*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"))
|
|
{
|
|
con.print("Skipping dev plugin: %s\n", *plug_name);
|
|
plugin_abort_load;
|
|
return false;
|
|
}
|
|
*plug_self = this;
|
|
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
|
|
std::vector<std::string>* plugin_globals = *((std::vector<std::string>**) LookupPlugin(plug, "plugin_globals"));
|
|
if (plugin_globals->size())
|
|
{
|
|
std::vector<std::string> missing_globals;
|
|
for (auto it = plugin_globals->begin(); it != plugin_globals->end(); ++it)
|
|
{
|
|
if (!Core::getInstance().vinfo->getAddress(it->c_str()))
|
|
missing_globals.push_back(*it);
|
|
}
|
|
if (missing_globals.size())
|
|
{
|
|
con.printerr("Plugin %s is missing required globals: %s\n",
|
|
*plug_name, join_strings(", ", missing_globals).c_str());
|
|
plugin_abort_load;
|
|
return false;
|
|
}
|
|
}
|
|
plugin_status = (command_result (*)(color_ostream &, std::string &)) LookupPlugin(plug, "plugin_status");
|
|
plugin_onupdate = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_onupdate");
|
|
plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown");
|
|
plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange");
|
|
plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect");
|
|
plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable");
|
|
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
|
|
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)
|
|
{
|
|
RefAutolock lock(access);
|
|
state = PS_LOADED;
|
|
parent->registerCommands(this);
|
|
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);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
con.printerr("Plugin %s has failed to initialize properly.\n", name.c_str());
|
|
plugin_is_enabled = 0;
|
|
plugin_onupdate = 0;
|
|
reset_lua();
|
|
plugin_abort_load;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Plugin::unload(color_ostream &con)
|
|
{
|
|
// get the mutex
|
|
access->lock();
|
|
// if we are actually loaded
|
|
if(state == PS_LOADED)
|
|
{
|
|
EventManager::unregisterAll(this);
|
|
// notify the plugin about an attempt to shutdown
|
|
if (plugin_onstatechange &&
|
|
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND)
|
|
{
|
|
con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str());
|
|
access->unlock();
|
|
return false;
|
|
}
|
|
// wait for all calls to finish
|
|
access->wait();
|
|
state = PS_UNLOADING;
|
|
access->unlock();
|
|
// enter suspend
|
|
CoreSuspender suspend;
|
|
access->lock();
|
|
// notify plugin about shutdown, if it has a shutdown function
|
|
command_result cr = CR_OK;
|
|
if(plugin_shutdown)
|
|
cr = plugin_shutdown(con);
|
|
// cleanup...
|
|
plugin_is_enabled = 0;
|
|
plugin_onupdate = 0;
|
|
reset_lua();
|
|
parent->unregisterCommands(this);
|
|
commands.clear();
|
|
if(cr == CR_OK)
|
|
{
|
|
ClosePlugin(plugin_lib);
|
|
state = PS_UNLOADED;
|
|
access->unlock();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
con.printerr("Plugin %s has failed to shutdown!\n",name.c_str());
|
|
state = PS_BROKEN;
|
|
access->unlock();
|
|
return false;
|
|
}
|
|
}
|
|
else if(state == PS_UNLOADED)
|
|
{
|
|
access->unlock();
|
|
return true;
|
|
}
|
|
access->unlock();
|
|
return false;
|
|
}
|
|
|
|
bool Plugin::reload(color_ostream &out)
|
|
{
|
|
if(state != PS_LOADED)
|
|
return false;
|
|
if(!unload(out))
|
|
return false;
|
|
if(!load(out))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
command_result Plugin::invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
|
|
{
|
|
Core & c = Core::getInstance();
|
|
command_result cr = CR_NOT_IMPLEMENTED;
|
|
access->lock_add();
|
|
if(state == PS_LOADED)
|
|
{
|
|
for (size_t i = 0; i < commands.size();i++)
|
|
{
|
|
PluginCommand &cmd = commands[i];
|
|
if(cmd.name == command)
|
|
{
|
|
// running interactive things from some other source than the console would break it
|
|
if(!out.is_console() && cmd.interactive)
|
|
cr = CR_NEEDS_CONSOLE;
|
|
else if (cmd.guard)
|
|
{
|
|
// Execute hotkey commands in a way where they can
|
|
// expect their guard conditions to be matched,
|
|
// so as to avoid duplicating checks.
|
|
// This means suspending the core beforehand.
|
|
CoreSuspender suspend(&c);
|
|
df::viewscreen *top = c.getTopViewscreen();
|
|
|
|
if (!cmd.guard(top))
|
|
{
|
|
out.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str());
|
|
cr = CR_WRONG_USAGE;
|
|
}
|
|
else
|
|
{
|
|
cr = cmd.function(out, parameters);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cr = cmd.function(out, parameters);
|
|
}
|
|
if (cr == CR_WRONG_USAGE && !cmd.usage.empty())
|
|
out << "Usage:\n" << cmd.usage << flush;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
access->lock_sub();
|
|
return cr;
|
|
}
|
|
|
|
bool Plugin::can_invoke_hotkey(const std::string & command, df::viewscreen *top )
|
|
{
|
|
Core & c = Core::getInstance();
|
|
bool cr = false;
|
|
access->lock_add();
|
|
if(state == PS_LOADED)
|
|
{
|
|
for (size_t i = 0; i < commands.size();i++)
|
|
{
|
|
PluginCommand &cmd = commands[i];
|
|
if(cmd.name == command)
|
|
{
|
|
if (cmd.interactive)
|
|
cr = false;
|
|
else if (cmd.guard)
|
|
cr = cmd.guard(top);
|
|
else
|
|
cr = Gui::default_hotkey(top);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
access->lock_sub();
|
|
return cr;
|
|
}
|
|
|
|
command_result Plugin::on_update(color_ostream &out)
|
|
{
|
|
// Check things that are implicitly protected by the suspend lock
|
|
if (!plugin_onupdate)
|
|
return CR_NOT_IMPLEMENTED;
|
|
if (plugin_is_enabled && !*plugin_is_enabled)
|
|
return CR_OK;
|
|
// Grab mutex and call the thing
|
|
command_result cr = CR_NOT_IMPLEMENTED;
|
|
access->lock_add();
|
|
if(state == PS_LOADED && plugin_onupdate)
|
|
{
|
|
cr = plugin_onupdate(out);
|
|
Lua::Core::Reset(out, "plugin_onupdate");
|
|
}
|
|
access->lock_sub();
|
|
return cr;
|
|
}
|
|
|
|
command_result Plugin::set_enabled(color_ostream &out, bool enable)
|
|
{
|
|
command_result cr = CR_NOT_IMPLEMENTED;
|
|
access->lock_add();
|
|
if(state == PS_LOADED && plugin_is_enabled && plugin_enable)
|
|
{
|
|
cr = plugin_enable(out, enable);
|
|
|
|
if (cr == CR_OK && enable != is_enabled())
|
|
cr = CR_FAILURE;
|
|
}
|
|
access->lock_sub();
|
|
return cr;
|
|
}
|
|
|
|
command_result Plugin::on_state_change(color_ostream &out, state_change_event event)
|
|
{
|
|
command_result cr = CR_NOT_IMPLEMENTED;
|
|
access->lock_add();
|
|
if(state == PS_LOADED && plugin_onstatechange)
|
|
{
|
|
cr = plugin_onstatechange(out, event);
|
|
Lua::Core::Reset(out, "plugin_onstatechange");
|
|
}
|
|
access->lock_sub();
|
|
return cr;
|
|
}
|
|
|
|
RPCService *Plugin::rpc_connect(color_ostream &out)
|
|
{
|
|
RPCService *rv = NULL;
|
|
|
|
access->lock_add();
|
|
|
|
if(state == PS_LOADED && plugin_rpcconnect)
|
|
{
|
|
rv = plugin_rpcconnect(out);
|
|
}
|
|
|
|
if (rv)
|
|
{
|
|
// Retain the access reference
|
|
assert(!rv->holder);
|
|
services.push_back(rv);
|
|
rv->holder = this;
|
|
return rv;
|
|
}
|
|
else
|
|
{
|
|
access->lock_sub();
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void Plugin::detach_connection(RPCService *svc)
|
|
{
|
|
int idx = linear_index(services, svc);
|
|
|
|
assert(svc->holder == this && idx >= 0);
|
|
|
|
vector_erase_at(services, idx);
|
|
access->lock_sub();
|
|
}
|
|
|
|
Plugin::plugin_state Plugin::getState() const
|
|
{
|
|
return state;
|
|
}
|
|
|
|
PluginExports *Plugin::getExports()
|
|
{
|
|
if (!plugin_get_exports)
|
|
return NULL;
|
|
PluginExports *exports = plugin_get_exports();
|
|
if (!exports->bind(plugin_lib))
|
|
return NULL;
|
|
return exports;
|
|
};
|
|
|
|
void Plugin::index_lua(DFLibrary *lib)
|
|
{
|
|
if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands"))
|
|
{
|
|
for (; cmdlist->name; ++cmdlist)
|
|
{
|
|
auto &cmd = lua_commands[cmdlist->name];
|
|
if (!cmd) cmd = new LuaCommand(this,cmdlist->name);
|
|
cmd->command = cmdlist->command;
|
|
}
|
|
}
|
|
if (auto funlist = (FunctionReg*)LookupPlugin(lib, "plugin_lua_functions"))
|
|
{
|
|
for (; funlist->name; ++funlist)
|
|
{
|
|
auto &cmd = lua_functions[funlist->name];
|
|
if (!cmd) cmd = new LuaFunction(this,funlist->name);
|
|
cmd->identity = funlist->identity;
|
|
}
|
|
}
|
|
if (auto evlist = (EventReg*)LookupPlugin(lib, "plugin_lua_events"))
|
|
{
|
|
for (; evlist->name; ++evlist)
|
|
{
|
|
auto &cmd = lua_events[evlist->name];
|
|
if (!cmd) cmd = new LuaEvent(this,evlist->name);
|
|
cmd->handler.identity = evlist->event->get_handler();
|
|
cmd->event = evlist->event;
|
|
if (cmd->active)
|
|
{
|
|
cmd->event->bind(Lua::Core::State, cmd);
|
|
if (cmd->count > 0)
|
|
cmd->event->on_count_changed(cmd->count, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Plugin::reset_lua()
|
|
{
|
|
for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it)
|
|
it->second->command = NULL;
|
|
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
|
|
it->second->identity = NULL;
|
|
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
|
|
{
|
|
it->second->handler.identity = NULL;
|
|
it->second->event = NULL;
|
|
}
|
|
}
|
|
|
|
int Plugin::lua_is_enabled(lua_State *state)
|
|
{
|
|
auto obj = (Plugin*)lua_touserdata(state, lua_upvalueindex(1));
|
|
|
|
RefAutoinc lock(obj->access);
|
|
if (obj->state == PS_LOADED && obj->plugin_is_enabled)
|
|
lua_pushboolean(state, obj->is_enabled());
|
|
else
|
|
lua_pushnil(state);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Plugin::lua_set_enabled(lua_State *state)
|
|
{
|
|
lua_settop(state, 1);
|
|
bool val = lua_toboolean(state, 1);
|
|
|
|
auto obj = (Plugin*)lua_touserdata(state, lua_upvalueindex(1));
|
|
RefAutoinc lock(obj->access);
|
|
|
|
color_ostream *out = Lua::GetOutput(state);
|
|
|
|
if (obj->state == PS_LOADED && obj->plugin_enable)
|
|
lua_pushboolean(state, obj->set_enabled(*out, val) == CR_OK);
|
|
else
|
|
luaL_error(state, "plugin %s unloaded, cannot enable or disable", obj->name.c_str());
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Plugin::lua_cmd_wrapper(lua_State *state)
|
|
{
|
|
auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1));
|
|
|
|
RefAutoinc lock(cmd->owner->access);
|
|
|
|
if (!cmd->command)
|
|
luaL_error(state, "plugin command %s() has been unloaded",
|
|
(cmd->owner->name+"."+cmd->name).c_str());
|
|
|
|
return Lua::CallWithCatch(state, cmd->command, cmd->name.c_str());
|
|
}
|
|
|
|
int Plugin::lua_fun_wrapper(lua_State *state)
|
|
{
|
|
auto cmd = (LuaFunction*)lua_touserdata(state, UPVAL_CONTAINER_ID);
|
|
|
|
RefAutoinc lock(cmd->owner->access);
|
|
|
|
if (!cmd->identity)
|
|
{
|
|
if (cmd->silent)
|
|
return 0;
|
|
|
|
luaL_error(state, "plugin function %s() has been unloaded",
|
|
(cmd->owner->name+"."+cmd->name).c_str());
|
|
}
|
|
|
|
return LuaWrapper::method_wrapper_core(state, cmd->identity);
|
|
}
|
|
|
|
void Plugin::open_lua(lua_State *state, int table)
|
|
{
|
|
table = lua_absindex(state, table);
|
|
|
|
RefAutolock lock(access);
|
|
|
|
if (plugin_is_enabled)
|
|
{
|
|
lua_pushlightuserdata(state, this);
|
|
lua_pushcclosure(state, lua_is_enabled, 1);
|
|
lua_setfield(state, table, "isEnabled");
|
|
}
|
|
if (plugin_enable)
|
|
{
|
|
lua_pushlightuserdata(state, this);
|
|
lua_pushcclosure(state, lua_set_enabled, 1);
|
|
lua_setfield(state, table, "setEnabled");
|
|
}
|
|
|
|
for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it)
|
|
{
|
|
lua_pushlightuserdata(state, it->second);
|
|
lua_pushcclosure(state, lua_cmd_wrapper, 1);
|
|
lua_setfield(state, table, it->first.c_str());
|
|
}
|
|
|
|
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
|
|
{
|
|
push_function(state, it->second);
|
|
lua_setfield(state, table, it->first.c_str());
|
|
}
|
|
|
|
if (Lua::IsCoreContext(state))
|
|
{
|
|
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
|
|
{
|
|
Lua::Event::Make(state, it->second, it->second);
|
|
|
|
push_function(state, &it->second->handler);
|
|
Lua::Event::SetPrivateCallback(state, -2);
|
|
|
|
it->second->active = true;
|
|
if (it->second->event)
|
|
it->second->event->bind(Lua::Core::State, it->second);
|
|
|
|
lua_setfield(state, table, it->first.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Plugin::push_function(lua_State *state, LuaFunction *fn)
|
|
{
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN);
|
|
lua_pushlightuserdata(state, NULL);
|
|
lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str());
|
|
lua_pushlightuserdata(state, fn);
|
|
lua_pushcclosure(state, lua_fun_wrapper, 4);
|
|
}
|
|
|
|
bool PluginExports::bind(DFLibrary *lib)
|
|
{
|
|
for (auto it = bindings.begin(); it != bindings.end(); ++it)
|
|
{
|
|
std::string name = it->first;
|
|
void** dest = it->second;
|
|
*dest = LookupPlugin(lib, name.c_str());
|
|
if (!*dest)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
PluginManager::PluginManager(Core * core)
|
|
{
|
|
cmdlist_mutex = new mutex();
|
|
ruby = NULL;
|
|
}
|
|
|
|
PluginManager::~PluginManager()
|
|
{
|
|
for(size_t i = 0; i < all_plugins.size();i++)
|
|
{
|
|
delete all_plugins[i];
|
|
}
|
|
all_plugins.clear();
|
|
delete cmdlist_mutex;
|
|
}
|
|
|
|
void PluginManager::init(Core * core)
|
|
{
|
|
#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++)
|
|
{
|
|
if(hasEnding(filez[i],searchstr))
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
|
|
Plugin *PluginManager::getPluginByName (const std::string & name)
|
|
{
|
|
for(size_t i = 0; i < all_plugins.size(); i++)
|
|
{
|
|
if(name == all_plugins[i]->name)
|
|
return all_plugins[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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())
|
|
return iter->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void *PluginManager::getPluginExports(const std::string &name)
|
|
{
|
|
Plugin *plug = getPluginByName(name);
|
|
if (!plug)
|
|
return NULL;
|
|
if (plug->getState() != Plugin::plugin_state::PS_LOADED)
|
|
return NULL;
|
|
return plug->getExports();
|
|
}
|
|
|
|
// FIXME: handle name collisions...
|
|
command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
|
|
{
|
|
Plugin *plugin = getPluginByCommand(command);
|
|
return plugin ? plugin->invoke(out, command, parameters) : CR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
|
|
{
|
|
Plugin *plugin = getPluginByCommand(command);
|
|
return plugin ? plugin->can_invoke_hotkey(command, top) : true;
|
|
}
|
|
|
|
void PluginManager::OnUpdate(color_ostream &out)
|
|
{
|
|
for(size_t i = 0; i < all_plugins.size(); i++)
|
|
{
|
|
all_plugins[i]->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);
|
|
}
|
|
}
|
|
|
|
// FIXME: doesn't check name collisions!
|
|
void PluginManager::registerCommands( Plugin * p )
|
|
{
|
|
cmdlist_mutex->lock();
|
|
vector <PluginCommand> & cmds = p->commands;
|
|
for(size_t i = 0; i < cmds.size();i++)
|
|
{
|
|
std::string name = cmds[i].name;
|
|
if (belongs.find(name) != belongs.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());
|
|
}
|
|
belongs[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 <PluginCommand> & cmds = p->commands;
|
|
for(size_t i = 0; i < cmds.size();i++)
|
|
{
|
|
belongs.erase(cmds[i].name);
|
|
}
|
|
if (p->plugin_eval_ruby)
|
|
ruby = NULL;
|
|
cmdlist_mutex->unlock();
|
|
}
|