From 976fa18d721df59d8cb4a0c5b500dec2b3634dcc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Mar 2012 14:11:46 +0400 Subject: [PATCH] A number of interface tweaks in RemoteClient. - Associate a default output stream with the whole connection. If not explicitly specified in the constructor, uses stdout. - Add methods that use this default stream to RemoteFunction. - Add easily usable wrappers for CoreSuspend and CoreResume. --- library/Core.cpp | 2 +- library/PluginManager.cpp | 2 +- library/RemoteClient.cpp | 83 ++++++++++++++++++++++++-------- library/dfhack-run.cpp | 4 +- library/include/RemoteClient.h | 65 +++++++++++++++++++++++-- library/proto/CoreProtocol.proto | 9 ++++ 6 files changed, 136 insertions(+), 29 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 3f76036bd..afaf23683 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -187,7 +187,7 @@ void fHKthread(void * iodata) args.erase(args.begin()); command_result cr = plug_mgr->InvokeCommand(out, first, args); - if(cr == CR_WOULD_BREAK) + if(cr == CR_NEEDS_CONSOLE) { out.printerr("It isn't possible to run an interactive command outside the console.\n"); } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index f7af9c954..565a0c2a8 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -291,7 +291,7 @@ command_result Plugin::invoke(color_ostream &out, const std::string & command, s { // running interactive things from some other source than the console would break it if(!out.is_console() && cmd.interactive) - cr = CR_WOULD_BREAK; + cr = CR_NEEDS_CONSOLE; else if (cmd.guard) { // Execute hotkey commands in a way where they can diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index bec935efd..3a497ba8c 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -87,16 +87,29 @@ void color_ostream_proxy::decode(CoreTextNotification *data) } } -RemoteClient::RemoteClient() +RemoteClient::RemoteClient(color_ostream *default_output) + : p_default_output(default_output) { active = false; socket = new CActiveSocket(); + suspend_ready = false; + + if (!p_default_output) + { + delete_output = true; + p_default_output = new color_ostream_wrapper(std::cout); + } + else + delete_output = false; } RemoteClient::~RemoteClient() { disconnect(); delete socket; + + if (delete_output) + delete p_default_output; } bool DFHack::readFullBuffer(CSimpleSocket *socket, void *buf, int size) @@ -138,13 +151,13 @@ bool RemoteClient::connect(int port) if (!socket->Initialize()) { - std::cerr << "Socket init failed." << endl; + default_output().printerr("Socket init failed.\n"); return false; } if (!socket->Open((const uint8 *)"localhost", port)) { - std::cerr << "Could not connect to localhost:" << port << endl; + default_output().printerr("Could not connect to localhost: %d\n", port); return false; } @@ -156,14 +169,14 @@ bool RemoteClient::connect(int port) if (socket->Send((uint8*)&header, sizeof(header)) != sizeof(header)) { - std::cerr << "Could not send header." << endl; + default_output().printerr("Could not send handshake header.\n"); socket->Close(); return active = false; } if (!readFullBuffer(socket, &header, sizeof(header))) { - std::cerr << "Could not read header." << endl; + default_output().printerr("Could not read handshake header.\n"); socket->Close(); return active = false; } @@ -171,7 +184,7 @@ bool RemoteClient::connect(int port) if (memcmp(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)) || header.version != 1) { - std::cerr << "Invalid handshake response." << endl; + default_output().printerr("Invalid handshake response.\n"); socket->Close(); return active = false; } @@ -195,7 +208,7 @@ void RemoteClient::disconnect() header.id = RPC_REQUEST_QUIT; header.size = 0; if (socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header)) - std::cerr << "Could not send the disconnect message." << endl; + default_output().printerr("Could not send the disconnect message.\n"); } socket->Close(); @@ -222,7 +235,6 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, if (bind_call(out) != CR_OK) return false; - function->p_client = this; function->id = bind_call.out()->assigned_id(); return true; @@ -246,6 +258,35 @@ command_result RemoteClient::run_command(color_ostream &out, const std::string & return runcmd_call(out); } +int RemoteClient::suspend_game() +{ + if (!active) + return -1; + + if (!suspend_ready) { + suspend_ready = true; + + suspend_call.bind(this, "CoreSuspend"); + resume_call.bind(this, "CoreResume"); + } + + if (suspend_call(default_output()) == CR_OK) + return suspend_call.out()->value(); + else + return -1; +} + +int RemoteClient::resume_game() +{ + if (!suspend_ready) + return -1; + + if (resume_call(default_output()) == CR_OK) + return resume_call.out()->value(); + else + return -1; +} + void RPCFunctionBase::reset(bool free) { if (free) @@ -266,11 +307,11 @@ void RPCFunctionBase::reset(bool free) bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, const std::string &name, const std::string &proto) { - if (p_client == client) - return true; - - if (p_client) + if (isValid()) { + if (p_client == client && this->name == name && this->proto == proto) + return true; + out.printerr("Function already bound to %s::%s\n", this->proto.c_str(), this->name.c_str()); return false; @@ -278,6 +319,7 @@ bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, this->name = name; this->proto = proto; + this->p_client = client; return client->bind(out, this, name, proto); } @@ -305,9 +347,10 @@ bool DFHack::sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageL command_result RemoteFunctionBase::execute(color_ostream &out, const message_type *input, message_type *output) { - if (!p_client) + if (!isValid()) { - out.printerr("Calling an unbound RPC function.\n"); + out.printerr("Calling an unbound RPC function %s::%s.\n", + this->proto.c_str(), this->name.c_str()); return CR_NOT_IMPLEMENTED; } @@ -315,14 +358,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { out.printerr("In call to %s::%s: invalid socket.\n", this->proto.c_str(), this->name.c_str()); - return CR_FAILURE; + return CR_LINK_FAILURE; } if (!sendRemoteMessage(p_client->socket, id, input)) { out.printerr("In call to %s::%s: I/O error in send.\n", this->proto.c_str(), this->name.c_str()); - return CR_FAILURE; + return CR_LINK_FAILURE; } color_ostream_proxy text_decoder(out); @@ -337,7 +380,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { out.printerr("In call to %s::%s: I/O error in receive header.\n", this->proto.c_str(), this->name.c_str()); - return CR_FAILURE; + return CR_LINK_FAILURE; } //out.print("Received %d:%d\n", header.id, header.size); @@ -349,7 +392,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { out.printerr("In call to %s::%s: invalid received size %d.\n", this->proto.c_str(), this->name.c_str(), header.size); - return CR_FAILURE; + return CR_LINK_FAILURE; } std::auto_ptr buf(new uint8_t[header.size]); @@ -358,7 +401,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", this->proto.c_str(), this->name.c_str(), header.size); - return CR_FAILURE; + return CR_LINK_FAILURE; } switch (header.id) { @@ -367,7 +410,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { out.printerr("In call to %s::%s: error parsing received result.\n", this->proto.c_str(), this->name.c_str()); - return CR_FAILURE; + return CR_LINK_FAILURE; } return CR_OK; diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index 16c6087ec..f79e49a3c 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -68,7 +68,7 @@ int main (int argc, char *argv[]) } // Connect to DFHack - RemoteClient client; + RemoteClient client(&out); if (!client.connect()) return 2; @@ -77,7 +77,7 @@ int main (int argc, char *argv[]) for (int i = 2; i < argc; i++) args.push_back(argv[i]); - command_result rv = client.run_command(out, argv[1], args); + command_result rv = client.run_command(argv[1], args); if (rv != CR_OK) { if (rv == CR_NOT_IMPLEMENTED) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index c14ee56fc..7858223f1 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -41,7 +41,8 @@ namespace DFHack enum command_result { - CR_WOULD_BREAK = -2, // Attempt to call interactive command without console + CR_LINK_FAILURE = -3, // RPC call failed due to I/O or protocol error + CR_NEEDS_CONSOLE = -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 @@ -146,10 +147,13 @@ namespace DFHack class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase { public: + bool bind(RemoteClient *client, const std::string &name, + const std::string &proto = std::string()); bool bind(color_ostream &out, RemoteClient *client, const std::string &name, const std::string &proto = std::string()); - bool isValid() { return (p_client != NULL); } + + bool isValid() { return (id >= 0); } protected: friend class RemoteClient; @@ -158,6 +162,7 @@ namespace DFHack : RPCFunctionBase(in, out), p_client(NULL), id(-1) {} + inline color_ostream &default_ostream(); command_result execute(color_ostream &out, const message_type *input, message_type *output); std::string name, proto; @@ -175,9 +180,17 @@ namespace DFHack RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {} + command_result operator() () { + return p_client ? RemoteFunctionBase::execute(default_ostream(), in(), out()) + : CR_NOT_IMPLEMENTED; + } command_result operator() (color_ostream &stream) { return RemoteFunctionBase::execute(stream, in(), out()); } + command_result operator() (const In *input, Out *output) { + return p_client ? RemoteFunctionBase::execute(default_ostream(), input, output) + : CR_NOT_IMPLEMENTED; + } command_result operator() (color_ostream &stream, const In *input, Out *output) { return RemoteFunctionBase::execute(stream, input, output); } @@ -191,9 +204,17 @@ namespace DFHack RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &EmptyMessage::default_instance()) {} + command_result operator() () { + return p_client ? RemoteFunctionBase::execute(default_ostream(), in(), out()) + : CR_NOT_IMPLEMENTED; + } command_result operator() (color_ostream &stream) { return RemoteFunctionBase::execute(stream, in(), out()); } + command_result operator() (const In *input) { + return p_client ? RemoteFunctionBase::execute(default_ostream(), input, out()) + : CR_NOT_IMPLEMENTED; + } command_result operator() (color_ostream &stream, const In *input) { return RemoteFunctionBase::execute(stream, input, out()); } @@ -211,22 +232,56 @@ namespace DFHack const std::string &name, const std::string &proto); public: - RemoteClient(); + RemoteClient(color_ostream *default_output = NULL); ~RemoteClient(); static int GetDefaultPort(); + color_ostream &default_output() { return *p_default_output; }; + bool connect(int port = -1); void disconnect(); + command_result run_command(const std::string &cmd, const std::vector &args) { + return run_command(default_output(), cmd, args); + } command_result run_command(color_ostream &out, const std::string &cmd, const std::vector &args); + // For executing multiple calls in rapid succession. + // Best used via RemoteSuspender. + int suspend_game(); + int resume_game(); + private: - bool active; - CActiveSocket * socket; + bool active, delete_output; + CActiveSocket *socket; + color_ostream *p_default_output; RemoteFunction bind_call; RemoteFunction runcmd_call; + + bool suspend_ready; + RemoteFunction suspend_call, resume_call; + }; + + inline color_ostream &RemoteFunctionBase::default_ostream() { + return p_client->default_output(); + } + + inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name, + const std::string &proto) { + return bind(client->default_output(), client, name, proto); + } + + class RemoteSuspender { + RemoteClient *client; + public: + RemoteSuspender(RemoteClient *client) : client(client) { + if (!client || client->suspend_game() <= 0) client = NULL; + } + ~RemoteSuspender() { + if (client) client->resume_game(); + } }; } diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index efe38c3b1..6f3b84510 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -32,6 +32,7 @@ message CoreTextNotification { message CoreErrorNotification { enum ErrorCode { + CR_LINK_FAILURE = -3; CR_WOULD_BREAK = -2; CR_NOT_IMPLEMENTED = -1; CR_OK = 0; @@ -49,10 +50,18 @@ message IntMessage { required int32 value = 1; } +message IntListMessage { + repeated int32 value = 1; +} + message StringMessage { required string value = 1; } +message StringListMessage { + repeated string value = 1; +} + message CoreBindRequest { required string method = 1; required string input_msg = 2;