diff --git a/library/Core.cpp b/library/Core.cpp index 6f04f5edb..793a63680 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -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); } } } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d36ba6659..f7af9c954 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -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 & parameters, bool interactive_) +command_result Plugin::invoke(color_ostream &out, const std::string & command, std::vector & 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 & parameters, bool interactive) +command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector & 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; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 302080780..9d55da120 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -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() diff --git a/library/include/Core.h b/library/include/Core.h index 1705bcc16..740c35558 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -198,6 +198,7 @@ namespace DFHack std::map misc_data_map; friend class CoreService; + friend class ServerConnection; ServerMain *server; }; diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index e617814cf..86f7a4b2f 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -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 & 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 & 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 commands; + std::vector 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 & parameters, bool interactive = true ); - bool CanInvokeHotkey(std::string &command, df::viewscreen *top); + command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); + bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); Plugin* operator[] (std::size_t index) { if(index >= all_plugins.size()) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 6fd762080..1b468694d 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -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 { diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 76abbe726..26363d582 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -132,6 +132,7 @@ namespace DFHack class DFHACK_EXPORT RPCService { friend class ServerConnection; + friend class Plugin; std::vector functions; std::map 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 { diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 2e2fe2c24..3ebe17a28 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -37,6 +37,7 @@ message CoreErrorNotification { CR_OK = 0; CR_FAILURE = 1; CR_WRONG_USAGE = 2; + CR_NOT_FOUND = 3; }; required ErrorCode code = 1; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index aba5feaa6..fe00fbafb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -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) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 8d68827a4..17c7320aa 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -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}) diff --git a/plugins/proto/.gitignore b/plugins/proto/.gitignore new file mode 100644 index 000000000..befabf79d --- /dev/null +++ b/plugins/proto/.gitignore @@ -0,0 +1,3 @@ +*.pb.cc +*.pb.cc.rule +*.pb.h diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto new file mode 100644 index 000000000..a139ceaa0 --- /dev/null +++ b/plugins/proto/rename.proto @@ -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; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1a1a8cc30..ae6edc652 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -17,6 +17,9 @@ #include "df/assumed_identity.h" #include "df/language_name.h" +#include "RemoteServer.h" +#include "rename.pb.h" + #include 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 ¶meters) { CoreSuspender suspend; @@ -124,29 +207,7 @@ static command_result rename(color_ostream &out, vector ¶meters) 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") {