Add support for exporting functions from plugins, with example in rename.

TODO: test by actually calling them remotely.
develop
Alexander Gavrilov 2012-03-15 13:01:23 +04:00
parent e7851f5abd
commit 87f925e72e
13 changed files with 248 additions and 67 deletions

@ -165,7 +165,7 @@ void fHKthread(void * iodata)
string first = args[0];
args.erase(args.begin());
command_result cr = plug_mgr->InvokeCommand(out, first, args, false);
command_result cr = plug_mgr->InvokeCommand(out, first, args);
if(cr == CR_WOULD_BREAK)
{
@ -272,7 +272,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->load();
plug->load(con);
}
}
else
@ -284,7 +284,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->load();
plug->load(con);
}
}
}
@ -299,7 +299,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->reload();
plug->reload(con);
}
}
else
@ -311,7 +311,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->reload();
plug->reload(con);
}
}
}
@ -326,7 +326,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->unload();
plug->unload(con);
}
}
else
@ -338,7 +338,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->unload();
plug->unload(con);
}
}
}

@ -26,9 +26,11 @@ distribution.
#include "Core.h"
#include "MemAccess.h"
#include "PluginManager.h"
#include "RemoteServer.h"
#include "Console.h"
#include "DataDefs.h"
#include "MiscUtils.h"
using namespace DFHack;
@ -146,6 +148,7 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f
plugin_status = 0;
plugin_onupdate = 0;
plugin_onstatechange = 0;
plugin_rpcconnect = 0;
state = PS_UNLOADED;
access = new RefLock();
}
@ -154,12 +157,12 @@ Plugin::~Plugin()
{
if(state == PS_LOADED)
{
unload();
unload(Core::getInstance().getConsole());
}
delete access;
}
bool Plugin::load()
bool Plugin::load(color_ostream &con)
{
RefAutolock lock(access);
if(state == PS_BROKEN)
@ -170,8 +173,6 @@ bool Plugin::load()
{
return true;
}
Core & c = Core::getInstance();
Console & con = c.getConsole();
DFLibrary * plug = OpenPlugin(filename.c_str());
if(!plug)
{
@ -208,6 +209,7 @@ bool Plugin::load()
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");
this->name = *plug_name;
plugin_lib = plug;
if(plugin_init(con,commands) == CR_OK)
@ -225,10 +227,8 @@ bool Plugin::load()
}
}
bool Plugin::unload()
bool Plugin::unload(color_ostream &con)
{
Core & c = Core::getInstance();
Console & con = c.getConsole();
// get the mutex
access->lock();
// if we are actually loaded
@ -266,18 +266,18 @@ bool Plugin::unload()
return false;
}
bool Plugin::reload()
bool Plugin::reload(color_ostream &out)
{
if(state != PS_LOADED)
return false;
if(!unload())
if(!unload(out))
return false;
if(!load())
if(!load(out))
return false;
return true;
}
command_result Plugin::invoke(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive_)
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;
@ -290,7 +290,7 @@ command_result Plugin::invoke(color_ostream &out, std::string & command, std::ve
if(cmd.name == command)
{
// running interactive things from some other source than the console would break it
if(!(interactive_ && out.is_console()) && cmd.interactive)
if(!out.is_console() && cmd.interactive)
cr = CR_WOULD_BREAK;
else if (cmd.guard)
{
@ -325,7 +325,7 @@ command_result Plugin::invoke(color_ostream &out, std::string & command, std::ve
return cr;
}
bool Plugin::can_invoke_hotkey( std::string & command, df::viewscreen *top )
bool Plugin::can_invoke_hotkey(const std::string & command, df::viewscreen *top )
{
Core & c = Core::getInstance();
bool cr = false;
@ -375,6 +375,42 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
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;
@ -399,7 +435,7 @@ PluginManager::PluginManager(Core * core)
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();
p->load(core->getConsole());
}
}
}
@ -435,13 +471,13 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command)
}
// FIXME: handle name collisions...
command_result PluginManager::InvokeCommand(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive)
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, interactive) : CR_NOT_IMPLEMENTED;
return plugin ? plugin->invoke(out, command, parameters) : CR_NOT_IMPLEMENTED;
}
bool PluginManager::CanInvokeHotkey(std::string &command, df::viewscreen *top)
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
{
Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->can_invoke_hotkey(command, top) : false;

@ -75,7 +75,7 @@ command_result CoreService::BindMethod(color_ostream &stream,
const dfproto::CoreBindRequest *in,
dfproto::CoreBindReply *out)
{
ServerFunctionBase *fn = connection()->findFunction(in->plugin(), in->method());
ServerFunctionBase *fn = connection()->findFunction(stream, in->plugin(), in->method());
if (!fn)
{
@ -104,7 +104,7 @@ command_result CoreService::RunCommand(color_ostream &stream,
for (int i = 0; i < in->arguments_size(); i++)
args.push_back(in->arguments(i));
return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args, false);
return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args);
}
RPCService::RPCService()
@ -115,6 +115,9 @@ RPCService::RPCService()
RPCService::~RPCService()
{
if (holder)
holder->detach_connection(this);
for (size_t i = 0; i < functions.size(); i++)
delete functions[i];
}
@ -163,12 +166,37 @@ ServerConnection::~ServerConnection()
delete core_service;
}
ServerFunctionBase *ServerConnection::findFunction(const std::string &plugin, const std::string &name)
ServerFunctionBase *ServerConnection::findFunction(color_ostream &out, const std::string &plugin, const std::string &name)
{
RPCService *svc;
if (plugin.empty())
return core_service->getFunction(name);
svc = core_service;
else
return NULL; // todo: add plugin api support
{
svc = plugin_services[plugin];
if (!svc)
{
Plugin *plug = Core::getInstance().plug_mgr->getPluginByName(plugin);
if (!plug)
{
out.printerr("No such plugin: %s\n", plugin.c_str());
return NULL;
}
svc = plug->rpc_connect(out);
if (!svc)
{
out.printerr("Plugin %s doesn't export any RPC methods.\n", plugin.c_str());
return NULL;
}
plugin_services[plugin] = svc;
}
}
return svc->getFunction(name);
}
void ServerConnection::connection_ostream::flush_proxy()

@ -198,6 +198,7 @@ namespace DFHack
std::map<std::string,void*> misc_data_map;
friend class CoreService;
friend class ServerConnection;
ServerMain *server;
};

@ -48,6 +48,7 @@ namespace DFHack
class Core;
class PluginManager;
class virtual_identity;
class RPCService;
enum state_change_event
{
@ -106,17 +107,23 @@ namespace DFHack
PS_BROKEN
};
friend class PluginManager;
friend class RPCService;
Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, 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:
bool load();
bool unload();
bool reload();
command_result invoke(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive );
bool can_invoke_hotkey( std::string & command, df::viewscreen *top );
bool load(color_ostream &out);
bool unload(color_ostream &out);
bool reload(color_ostream &out);
command_result invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool can_invoke_hotkey(const std::string & command, df::viewscreen *top );
plugin_state getState () const;
RPCService *rpc_connect(color_ostream &out);
const PluginCommand& operator[] (std::size_t index) const
{
return commands[index];
@ -132,6 +139,7 @@ namespace DFHack
private:
RefLock * access;
std::vector <PluginCommand> commands;
std::vector <RPCService*> services;
std::string filename;
std::string name;
DFLibrary * plugin_lib;
@ -142,6 +150,7 @@ namespace DFHack
command_result (*plugin_shutdown)(color_ostream &);
command_result (*plugin_onupdate)(color_ostream &);
command_result (*plugin_onstatechange)(color_ostream &, state_change_event);
RPCService* (*plugin_rpcconnect)(color_ostream &);
};
class DFHACK_EXPORT PluginManager
{
@ -158,8 +167,8 @@ namespace DFHack
public:
Plugin *getPluginByName (const std::string & name);
Plugin *getPluginByCommand (const std::string &command);
command_result InvokeCommand(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive = true );
bool CanInvokeHotkey(std::string &command, df::viewscreen *top);
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())

@ -37,11 +37,12 @@ namespace DFHack
enum command_result
{
CR_WOULD_BREAK = -2,
CR_NOT_IMPLEMENTED = -1,
CR_OK = 0,
CR_FAILURE = 1,
CR_WRONG_USAGE = 2
CR_WOULD_BREAK = -2, // Attempt to call interactive command without console
CR_NOT_IMPLEMENTED = -1, // Command not implemented, or plugin not loaded
CR_OK = 0, // Success
CR_FAILURE = 1, // Failure
CR_WRONG_USAGE = 2, // Wrong arguments or ui state
CR_NOT_FOUND = 3 // Target object not found (for RPC mainly)
};
enum DFHackReplyCode : int16_t {

@ -132,6 +132,7 @@ namespace DFHack
class DFHACK_EXPORT RPCService {
friend class ServerConnection;
friend class Plugin;
std::vector<ServerFunctionBase*> functions;
std::map<std::string, ServerFunctionBase*> lookup;
@ -225,7 +226,7 @@ namespace DFHack
ServerConnection(CActiveSocket *socket);
~ServerConnection();
ServerFunctionBase *findFunction(const std::string &plugin, const std::string &name);
ServerFunctionBase *findFunction(color_ostream &out, const std::string &plugin, const std::string &name);
};
class DFHACK_EXPORT ServerMain {

@ -37,6 +37,7 @@ message CoreErrorNotification {
CR_OK = 0;
CR_FAILURE = 1;
CR_WRONG_USAGE = 2;
CR_NOT_FOUND = 3;
};
required ErrorCode code = 1;

@ -45,6 +45,21 @@ if (BUILD_DWARFEXPORT)
add_subdirectory (dwarfexport)
endif()
# Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}")
STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}")
ADD_CUSTOM_COMMAND(
OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}
COMMAND protoc-bin -I=${dfhack_SOURCE_DIR}/proto/ -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/
--cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/
${PROJECT_PROTOS}
DEPENDS protoc-bin ${PROJECT_PROTOS}
)
# Plugins
OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON)
if (BUILD_SUPPORTED)
DFHACK_PLUGIN(reveal reveal.cpp)
@ -70,7 +85,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(seedwatch seedwatch.cpp)
DFHACK_PLUGIN(initflags initflags.cpp)
DFHACK_PLUGIN(stockpiles stockpiles.cpp)
DFHACK_PLUGIN(rename rename.cpp)
DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(workflow workflow.cpp)
DFHACK_PLUGIN(showmood showmood.cpp)

@ -8,6 +8,7 @@ ENDIF()
include_directories("${dfhack_SOURCE_DIR}/library/include")
include_directories("${dfhack_SOURCE_DIR}/library/proto")
include_directories("${dfhack_SOURCE_DIR}/library/depends/xgetopt")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/proto")
MACRO(CAR var)
SET(${var} ${ARGV1})
@ -58,21 +59,28 @@ ENDMACRO()
MACRO(DFHACK_PLUGIN)
PARSE_ARGUMENTS(PLUGIN
"LINK_LIBRARIES;DEPENDS"
"LINK_LIBRARIES;DEPENDS;PROTOBUFS"
"SOME_OPT"
${ARGN}
)
CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS})
CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS})
FOREACH(pbuf ${PLUGIN_PROTOBUFS})
SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
ENDFOREACH()
ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
IDE_FOLDER(${PLUGIN_NAME} "Plugins")
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack ${PLUGIN_LINK_LIBRARIES})
IF(UNIX)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.so PREFIX "")
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "-include Export.h")
ELSE()
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dll)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"")
ENDIF()
install(TARGETS ${PLUGIN_NAME}
LIBRARY DESTINATION ${DFHACK_PLUGIN_DESTINATION}
RUNTIME DESTINATION ${DFHACK_PLUGIN_DESTINATION})

@ -0,0 +1,3 @@
*.pb.cc
*.pb.cc.rule
*.pb.h

@ -0,0 +1,17 @@
package dfproto;
option optimize_for = LITE_RUNTIME;
message RenameSquadRq {
required int32 squad_id = 1;
optional string nickname = 2;
optional string alias = 3;
}
message RenameUnitRq {
required int32 unit_id = 1;
optional string nickname = 2;
optional string profession = 3;
}

@ -17,6 +17,9 @@
#include "df/assumed_identity.h"
#include "df/language_name.h"
#include "RemoteServer.h"
#include "rename.pb.h"
#include <stdlib.h>
using std::vector;
@ -24,6 +27,7 @@ using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using namespace dfproto;
using df::global::ui;
using df::global::world;
@ -79,6 +83,85 @@ static df::squad *getSquadByIndex(unsigned idx)
return df::squad::find(entity->squads[idx]);
}
void setUnitNickname(df::unit *unit, const std::string &nick)
{
// There are >=3 copies of the name, and the one
// in the unit is not the authoritative one.
// This is the reason why military units often
// lose nicknames set from Dwarf Therapist.
set_nickname(&unit->name, nick);
if (unit->status.current_soul)
set_nickname(&unit->status.current_soul->name, nick);
df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id);
if (figure)
{
set_nickname(&figure->name, nick);
// v0.34.01: added the vampire's assumed identity
if (figure->info && figure->info->reputation)
{
auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity);
if (identity)
{
auto id_hfig = df::historical_figure::find(identity->histfig_id);
if (id_hfig)
{
// Even DF doesn't do this bit, because it's apparently
// only used for demons masquerading as gods, so you
// can't ever change their nickname in-game.
set_nickname(&id_hfig->name, nick);
}
else
set_nickname(&identity->name, nick);
}
}
}
}
static command_result RenameSquad(color_ostream &stream, const RenameSquadRq *in)
{
CoreSuspender suspend;
df::squad *squad = df::squad::find(in->squad_id());
if (!squad)
return CR_NOT_FOUND;
if (in->has_nickname())
set_nickname(&squad->name, in->nickname());
if (in->has_alias())
squad->alias = in->alias();
return CR_OK;
}
static command_result RenameUnit(color_ostream &stream, const RenameUnitRq *in)
{
CoreSuspender suspend;
df::unit *unit = df::unit::find(in->unit_id());
if (!unit)
return CR_NOT_FOUND;
if (in->has_nickname())
setUnitNickname(unit, in->nickname());
if (in->has_profession())
unit->custom_profession = in->profession();
return CR_OK;
}
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{
RPCService *svc = new RPCService();
svc->addFunction("RenameSquad", RenameSquad);
svc->addFunction("RenameUnit", RenameUnit);
return svc;
}
static command_result rename(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -124,29 +207,7 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
if (!unit)
return CR_WRONG_USAGE;
// There are 3 copies of the name, and the one
// in the unit is not the authoritative one.
// This is the reason why military units often
// lose nicknames set from Dwarf Therapist.
set_nickname(&unit->name, parameters[1]);
if (unit->status.current_soul)
set_nickname(&unit->status.current_soul->name, parameters[1]);
df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id);
if (figure)
{
set_nickname(&figure->name, parameters[1]);
// v0.34.01: added the vampire's assumed identity
if (figure->info && figure->info->reputation)
{
auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity);
if (identity)
set_nickname(&identity->name, parameters[1]);
}
}
setUnitNickname(unit, parameters[1]);
}
else if (cmd == "unit-profession")
{