From 6839fde5555c975c2180a6367472f9954150b373 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 3 Jul 2018 15:37:07 -0500 Subject: [PATCH 1/6] Remove execute permission on files that are not executables. --- library/proto/Basic.proto | 0 library/proto/BasicApi.proto | 0 plugins/seedwatch.cpp | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 library/proto/Basic.proto mode change 100755 => 100644 library/proto/BasicApi.proto mode change 100755 => 100644 plugins/seedwatch.cpp diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto old mode 100755 new mode 100644 diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto old mode 100755 new mode 100644 diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp old mode 100755 new mode 100644 From df9150f30211e040c5322479d349eae11a190025 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 3 Jul 2018 15:36:41 -0500 Subject: [PATCH 2/6] Add documentation for RPC functions supported by RemoteFortressReader and ISOWorldRemote. --- plugins/proto/RemoteFortressReader.proto | 36 ++++++++++++++++++++++++ plugins/proto/isoworldremote.proto | 3 ++ 2 files changed, 39 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 3298d42fb..21233ae62 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -5,6 +5,42 @@ option optimize_for = LITE_RUNTIME; import "ItemdefInstrument.proto"; +// RPC GetMaterialList : EmptyMessage -> MaterialList +// RPC GetGrowthList : EmptyMessage -> MaterialList +// RPC GetBlockList : BlockRequest -> BlockList +// RPC CheckHashes : EmptyMessage -> EmptyMessage +// RPC GetTiletypeList : EmptyList -> TiletypeList +// RPC GetPlantList : BlockRequest -> PlantList +// RPC GetUnitList : EmptyMessage -> UnitList +// RPC GetUnitListInside : BlockRequest -> UnitList +// RPC GetViewInfo : EmptyMessage -> ViewInfo +// RPC GetMapInfo : EmptyMessage -> MapInfo +// RPC ResetMapHashes : EmptyMessage -> EmptyMessage +// RPC GetItemList : EmptyMessage -> MaterialList +// RPC GetBuildingDefList : EmptyMessage -> BuildingList +// RPC GetWorldMap : EmptyMessage -> WorldMap +// RPC GetWorldMapNew : EmptyMessage -> WorldMap +// RPC GetRegionMaps : EmptyMessage -> RegionMaps +// RPC GetRegionMapsNew : EmptyMessage -> RegionMaps +// RPC GetCreatureRaws : EmptyMessage -> CreatureRawList +// RPC GetPartialCreatureRaws : ListRequest -> CreatureRawList +// RPC GetWorldMapCenter : EmptyMessage -> WorldMap +// RPC GetPlantRaws : EmptyMessage -> PlantRawList +// RPC GetPartialPlantRaws : ListRequest -> PlantRawList +// RPC CopyScreen : EmptyMessage -> ScreenCapture +// RPC PassKeyboardEvent : KeyboardEvent -> EmptyMessage +// RPC SendDigCommand : DigCommand -> EmptyMessage +// RPC SetPauseState : SingleBool -> EmptyMessage +// RPC GetPauseState : EmptyMessage -> SingleBool +// RPC GetVersionInfo : EmptyMessage -> VersionInfo +// RPC GetReports : EmptyMessage -> Status +// RPC MoveCommand : MoveCommandParams -> EmptyMessage +// RPC JumpCommand : MoveCommandParams -> EmptyMessage +// RPC MenuQuery : EmptyMessage -> MenuContents +// RPC MovementSelectCommand : IntMessage -> EmptyMessage +// RPC MiscMoveCommand : MiscMoveParams -> EmptyMessage +// RPC GetLanguage : EmptyMessage -> Language + //We use shapes, etc, because the actual tiletypes may differ between DF versions. enum TiletypeShape { diff --git a/plugins/proto/isoworldremote.proto b/plugins/proto/isoworldremote.proto index d160d9968..3bbe27042 100644 --- a/plugins/proto/isoworldremote.proto +++ b/plugins/proto/isoworldremote.proto @@ -50,6 +50,7 @@ message EmbarkTile { optional bool is_valid = 7; } +// RPC GetEmbarkTile : TileRequest -> EmbarkTile message TileRequest { optional int32 want_x = 1; optional int32 want_y = 2; @@ -59,6 +60,7 @@ message MapRequest { optional string save_folder = 1; } +// RPC GetEmbarkInfo : MapRequest -> MapReply message MapReply { required bool available = 1; optional int32 region_x = 2; @@ -69,6 +71,7 @@ message MapReply { optional int32 current_season = 7; } +// RPC GetRawNames : MapRequest -> RawNames message RawNames { required bool available = 1; repeated string inorganic = 2; From 4a0682133e70e542a5cda1ac44452bc82693db58 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 3 Jul 2018 15:54:30 -0500 Subject: [PATCH 3/6] Annotate plugin proto files with the name of the plugin they are associated with. --- plugins/proto/AdventureControl.proto | 4 +++- plugins/proto/ItemdefInstrument.proto | 4 +++- plugins/proto/RemoteFortressReader.proto | 4 +++- plugins/proto/isoworldremote.proto | 2 ++ plugins/proto/rename.proto | 2 ++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto index 7d3d128d6..0bbcc8da8 100644 --- a/plugins/proto/AdventureControl.proto +++ b/plugins/proto/AdventureControl.proto @@ -5,6 +5,8 @@ option optimize_for = LITE_RUNTIME; import "RemoteFortressReader.proto"; +// Plugin: RemoteFortressReader + enum AdvmodeMenu { Default = 0; @@ -102,4 +104,4 @@ message MenuContents message MiscMoveParams { optional MiscMoveType type = 1; -} \ No newline at end of file +} diff --git a/plugins/proto/ItemdefInstrument.proto b/plugins/proto/ItemdefInstrument.proto index c92491ace..02d4c7a90 100644 --- a/plugins/proto/ItemdefInstrument.proto +++ b/plugins/proto/ItemdefInstrument.proto @@ -3,6 +3,8 @@ package ItemdefInstrument; //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization option optimize_for = LITE_RUNTIME; +// Plugin: RemoteFortressReader + message InstrumentFlags { optional bool indefinite_pitch = 1; @@ -101,4 +103,4 @@ message InstrumentDef repeated string tuning_parm = 17; repeated InstrumentRegister registers = 18; optional string description = 19; -} \ No newline at end of file +} diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 21233ae62..14d046a9d 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -3,6 +3,8 @@ package RemoteFortressReader; //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization option optimize_for = LITE_RUNTIME; +// Plugin: RemoteFortressReader + import "ItemdefInstrument.proto"; // RPC GetMaterialList : EmptyMessage -> MaterialList @@ -1104,4 +1106,4 @@ message Wave { optional Coord dest = 1; optional Coord pos = 2; -} \ No newline at end of file +} diff --git a/plugins/proto/isoworldremote.proto b/plugins/proto/isoworldremote.proto index 3bbe27042..23abdab73 100644 --- a/plugins/proto/isoworldremote.proto +++ b/plugins/proto/isoworldremote.proto @@ -3,6 +3,8 @@ package isoworldremote; //Describes a very basic material structure for the map embark option optimize_for = LITE_RUNTIME; +// Plugin: isoworldremote + enum BasicMaterial { AIR = 0; OTHER = 1; diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index 810091fc7..d6059a6b0 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -2,6 +2,8 @@ package dfproto; option optimize_for = LITE_RUNTIME; +// Plugin: rename + // RPC RenameSquad : RenameSquadIn -> EmptyMessage message RenameSquadIn { required int32 squad_id = 1; From dabe04cbf1b67c89eb220b117aa2d4017e94f288 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 3 Jul 2018 16:00:48 -0500 Subject: [PATCH 4/6] Rename bind argument proto -> plugin to avoid confusion. --- library/RemoteClient.cpp | 34 +++++++++++++++++----------------- library/include/RemoteClient.h | 12 ++++++------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 25355530f..7735df028 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -236,7 +236,7 @@ void RemoteClient::disconnect() } bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, - const std::string &name, const std::string &proto) + const std::string &name, const std::string &plugin) { if (!active || !socket->IsSocketValid()) return false; @@ -247,8 +247,8 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, auto in = bind_call.in(); in->set_method(name); - if (!proto.empty()) - in->set_plugin(proto); + if (!plugin.empty()) + in->set_plugin(plugin); in->set_input_msg(function->p_in_template->GetTypeName()); in->set_output_msg(function->p_out_template->GetTypeName()); } @@ -326,23 +326,23 @@ void RPCFunctionBase::reset(bool free) } bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, - const std::string &name, const std::string &proto) + const std::string &name, const std::string &plugin) { if (isValid()) { - if (p_client == client && this->name == name && this->proto == proto) + if (p_client == client && this->name == name && this->plugin == plugin) return true; out.printerr("Function already bound to %s::%s\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); return false; } this->name = name; - this->proto = proto; + this->plugin = plugin; this->p_client = client; - return client->bind(out, this, name, proto); + return client->bind(out, this, name, plugin); } bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg, bool size_ready) @@ -371,14 +371,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!isValid()) { out.printerr("Calling an unbound RPC function %s::%s.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); return CR_NOT_IMPLEMENTED; } if (!p_client->socket->IsSocketValid()) { out.printerr("In call to %s::%s: invalid socket.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); return CR_LINK_FAILURE; } @@ -387,14 +387,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE) { out.printerr("In call to %s::%s: message too large: %d.\n", - this->proto.c_str(), this->name.c_str(), send_size); + this->plugin.c_str(), this->name.c_str(), send_size); return CR_LINK_FAILURE; } if (!sendRemoteMessage(p_client->socket, id, input, true)) { out.printerr("In call to %s::%s: I/O error in send.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); return CR_LINK_FAILURE; } @@ -409,7 +409,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!readFullBuffer(p_client->socket, &header, sizeof(header))) { out.printerr("In call to %s::%s: I/O error in receive header.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); return CR_LINK_FAILURE; } @@ -421,7 +421,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) { out.printerr("In call to %s::%s: invalid received size %d.\n", - this->proto.c_str(), this->name.c_str(), header.size); + this->plugin.c_str(), this->name.c_str(), header.size); return CR_LINK_FAILURE; } @@ -430,7 +430,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!readFullBuffer(p_client->socket, buf, header.size)) { 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); + this->plugin.c_str(), this->name.c_str(), header.size); return CR_LINK_FAILURE; } @@ -439,7 +439,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!output->ParseFromArray(buf, header.size)) { out.printerr("In call to %s::%s: error parsing received result.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); delete[] buf; return CR_LINK_FAILURE; } @@ -453,7 +453,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, text_decoder.decode(&text_data); else out.printerr("In call to %s::%s: received invalid text data.\n", - this->proto.c_str(), this->name.c_str()); + this->plugin.c_str(), this->name.c_str()); break; default: diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index cf07bba8b..e71b985cd 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -150,10 +150,10 @@ namespace DFHack class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase { public: bool bind(RemoteClient *client, const std::string &name, - const std::string &proto = std::string()); + const std::string &plugin = std::string()); bool bind(color_ostream &out, RemoteClient *client, const std::string &name, - const std::string &proto = std::string()); + const std::string &plugin = std::string()); bool isValid() { return (id >= 0); } @@ -167,7 +167,7 @@ namespace DFHack inline color_ostream &default_ostream(); command_result execute(color_ostream &out, const message_type *input, message_type *output); - std::string name, proto; + std::string name, plugin; RemoteClient *p_client; int16_t id; }; @@ -227,7 +227,7 @@ namespace DFHack friend class RemoteFunctionBase; bool bind(color_ostream &out, RemoteFunctionBase *function, - const std::string &name, const std::string &proto); + const std::string &name, const std::string &plugin); public: RemoteClient(color_ostream *default_output = NULL); @@ -269,8 +269,8 @@ namespace DFHack } inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name, - const std::string &proto) { - return bind(client->default_output(), client, name, proto); + const std::string &plugin) { + return bind(client->default_output(), client, name, plugin); } class RemoteSuspender { From 5db34339a5797c5b6fedcc31ca9b139a5830c8d5 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 3 Jul 2018 18:17:04 -0500 Subject: [PATCH 5/6] Fix typo --- plugins/proto/RemoteFortressReader.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 14d046a9d..af971d55d 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -11,7 +11,7 @@ import "ItemdefInstrument.proto"; // RPC GetGrowthList : EmptyMessage -> MaterialList // RPC GetBlockList : BlockRequest -> BlockList // RPC CheckHashes : EmptyMessage -> EmptyMessage -// RPC GetTiletypeList : EmptyList -> TiletypeList +// RPC GetTiletypeList : EmptyMessage -> TiletypeList // RPC GetPlantList : BlockRequest -> PlantList // RPC GetUnitList : EmptyMessage -> UnitList // RPC GetUnitListInside : BlockRequest -> UnitList From 3b6a0738a0d0969d3c6fe481d5cb1eabf198dca7 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 4 Jul 2018 13:22:04 -0500 Subject: [PATCH 6/6] Add check-rpc.py for Travis. --- .travis.yml | 1 + library/Core.cpp | 32 ++++++++++ library/RemoteServer.cpp | 18 ++++++ library/include/RemoteServer.h | 3 + travis/check-rpc.py | 109 +++++++++++++++++++++++++++++++++ travis/dfhack_travis.init | 2 + 6 files changed, 165 insertions(+) create mode 100644 travis/check-rpc.py diff --git a/.travis.yml b/.travis.yml index ede4b132b..d94c030cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,7 @@ script: - cd .. - cp travis/dfhack_travis.init "$DF_FOLDER"/ - python travis/run-tests.py "$DF_FOLDER" +- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" notifications: email: false irc: diff --git a/library/Core.cpp b/library/Core.cpp index 463175e0f..2d046b0f7 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -54,6 +54,7 @@ using namespace std; #include "modules/Graphic.h" #include "modules/Windows.h" #include "RemoteServer.h" +#include "RemoteTools.h" #include "LuaTools.h" #include "DFHackVersion.h" @@ -632,6 +633,9 @@ string getBuiltinCommand(std::string cmd) else if (cmd == "clear") builtin = "cls"; + else if (cmd == "devel/dump-rpc") + builtin = "devel/dump-rpc"; + return builtin; } @@ -1278,6 +1282,34 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } + else if (builtin == "devel/dump-rpc") + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService core; + core.dumpMethods(file); + + for (auto & it : *plug_mgr) + { + Plugin * plug = it.second; + if (!plug) + continue; + + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; + + file << "// Plugin: " << plug->getName() << endl; + svc->dumpMethods(file); + } + } + else + { + con << "Usage: devel/dump-rpc \"filename\"" << endl; + return CR_WRONG_USAGE; + } + } else if (RunAlias(con, first, parts, res)) { return res; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index f110cc8b4..86ef9f40c 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -109,6 +109,24 @@ void RPCService::finalize(ServerConnection *owner, std::vectorp_in_template->GetTypeName(); + size_t last_dot = in_name.rfind('.'); + if (last_dot != std::string::npos) + in_name = in_name.substr(last_dot + 1); + + std::string out_name = fn->p_out_template->GetTypeName(); + last_dot = out_name.rfind('.'); + if (last_dot != std::string::npos) + out_name = out_name.substr(last_dot + 1); + + out << "// RPC " << fn->name << " : " << in_name << " -> " << out_name << endl; + } +} + ServerConnection::ServerConnection(CActiveSocket *socket) : socket(socket), stream(this) { diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 514f91250..85ac463fb 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -151,6 +151,7 @@ namespace DFHack class DFHACK_EXPORT RPCService { friend class ServerConnection; friend class Plugin; + friend class Core; std::vector functions; std::map lookup; @@ -208,6 +209,8 @@ namespace DFHack assert(!owner); functions.push_back(new VoidServerMethod(this, name, flags, fptr)); } + + void dumpMethods(std::ostream & out) const; }; class ServerConnection { diff --git a/travis/check-rpc.py b/travis/check-rpc.py new file mode 100644 index 000000000..7b771ee67 --- /dev/null +++ b/travis/check-rpc.py @@ -0,0 +1,109 @@ +import glob +import sys + +actual = {'': {}} + +with open(sys.argv[1]) as f: + plugin_name = '' + for line in f: + line = line.rstrip() + if line.startswith('// Plugin: '): + plugin_name = line.split(' ')[2] + if plugin_name not in actual: + actual[plugin_name] = {} + elif line.startswith('// RPC '): + parts = line.split(' ') + actual[plugin_name][parts[2]] = (parts[4], parts[6]) + +expected = {'': {}} + +for p in glob.iglob('library/proto/*.proto'): + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// RPC '): + parts = line.split(' ') + expected[''][parts[2]] = (parts[4], parts[6]) + +for p in glob.iglob('plugins/proto/*.proto'): + plugin_name = '' + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// Plugin: '): + plugin_name = line.split(' ')[2] + if plugin_name not in expected: + expected[plugin_name] = {} + break + + if plugin_name == '': + continue + + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// RPC '): + parts = line.split(' ') + expected[plugin_name][parts[2]] = (parts[4], parts[6]) + +error_count = 0 + +for plugin_name in actual: + methods = actual[plugin_name] + + if plugin_name not in expected: + print('Missing documentation for plugin proto files: ' + plugin_name) + print('Add the following lines:') + print('// Plugin: ' + plugin_name) + error_count += 1 + for m in methods: + io = methods[m] + print('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + error_count += 1 + else: + missing = [] + wrong = [] + for m in methods: + io = methods[m] + if m in expected[plugin_name]: + if expected[plugin_name][m] != io: + wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + else: + missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + + if len(missing) > 0: + print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:') + for m in missing: + print(m) + error_count += 1 + + if len(wrong) > 0: + print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Replace the following comments:') + for m in wrong: + print(m) + error_count += 1 + +for plugin_name in expected: + methods = expected[plugin_name] + + if plugin_name not in actual: + print('Incorrect documentation for plugin proto files: ' + plugin_name) + print('The following methods are documented, but the plugin does not provide any RPC methods:') + for m in methods: + io = methods[m] + print('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + error_count += 1 + else: + missing = [] + for m in methods: + io = methods[m] + if m not in actual[plugin_name]: + missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + + if len(missing) > 0: + print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') + for m in missing: + print(m) + error_count += 1 + +sys.exit(min(100, error_count)) diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init index d9bc3e5ba..eb7858bbe 100644 --- a/travis/dfhack_travis.init +++ b/travis/dfhack_travis.init @@ -1,2 +1,4 @@ +devel/dump-rpc dfhack-rpc.txt + :lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) test/main