Merge remote-tracking branch 'BenLubar/proto-docs' into develop

develop
lethosor 2018-07-10 11:26:31 -04:00
commit 06511340ab
16 changed files with 240 additions and 26 deletions

@ -51,6 +51,7 @@ script:
- cd .. - cd ..
- cp travis/dfhack_travis.init "$DF_FOLDER"/ - cp travis/dfhack_travis.init "$DF_FOLDER"/
- python travis/run-tests.py "$DF_FOLDER" - python travis/run-tests.py "$DF_FOLDER"
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
notifications: notifications:
email: false email: false
irc: irc:

@ -54,6 +54,7 @@ using namespace std;
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "modules/Windows.h" #include "modules/Windows.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "RemoteTools.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "DFHackVersion.h" #include "DFHackVersion.h"
@ -655,6 +656,9 @@ string getBuiltinCommand(std::string cmd)
else if (cmd == "clear") else if (cmd == "clear")
builtin = "cls"; builtin = "cls";
else if (cmd == "devel/dump-rpc")
builtin = "devel/dump-rpc";
return builtin; return builtin;
} }
@ -1301,6 +1305,34 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_WRONG_USAGE; 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<RPCService> 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)) else if (RunAlias(con, first, parts, res))
{ {
return res; return res;

@ -236,7 +236,7 @@ void RemoteClient::disconnect()
} }
bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, 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()) if (!active || !socket->IsSocketValid())
return false; return false;
@ -247,8 +247,8 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function,
auto in = bind_call.in(); auto in = bind_call.in();
in->set_method(name); in->set_method(name);
if (!proto.empty()) if (!plugin.empty())
in->set_plugin(proto); in->set_plugin(plugin);
in->set_input_msg(function->p_in_template->GetTypeName()); in->set_input_msg(function->p_in_template->GetTypeName());
in->set_output_msg(function->p_out_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, 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 (isValid())
{ {
if (p_client == client && this->name == name && this->proto == proto) if (p_client == client && this->name == name && this->plugin == plugin)
return true; return true;
out.printerr("Function already bound to %s::%s\n", 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; return false;
} }
this->name = name; this->name = name;
this->proto = proto; this->plugin = plugin;
this->p_client = client; 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) 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()) if (!isValid())
{ {
out.printerr("Calling an unbound RPC function %s::%s.\n", 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; return CR_NOT_IMPLEMENTED;
} }
if (!p_client->socket->IsSocketValid()) if (!p_client->socket->IsSocketValid())
{ {
out.printerr("In call to %s::%s: invalid socket.\n", 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; return CR_LINK_FAILURE;
} }
@ -387,14 +387,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE)
{ {
out.printerr("In call to %s::%s: message too large: %d.\n", 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; return CR_LINK_FAILURE;
} }
if (!sendRemoteMessage(p_client->socket, id, input, true)) if (!sendRemoteMessage(p_client->socket, id, input, true))
{ {
out.printerr("In call to %s::%s: I/O error in send.\n", 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; return CR_LINK_FAILURE;
} }
@ -409,7 +409,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!readFullBuffer(p_client->socket, &header, sizeof(header))) if (!readFullBuffer(p_client->socket, &header, sizeof(header)))
{ {
out.printerr("In call to %s::%s: I/O error in receive header.\n", 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; 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) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)
{ {
out.printerr("In call to %s::%s: invalid received size %d.\n", 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; return CR_LINK_FAILURE;
} }
@ -430,7 +430,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!readFullBuffer(p_client->socket, buf, header.size)) 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", 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; return CR_LINK_FAILURE;
} }
@ -439,7 +439,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!output->ParseFromArray(buf, header.size)) if (!output->ParseFromArray(buf, header.size))
{ {
out.printerr("In call to %s::%s: error parsing received result.\n", 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; delete[] buf;
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -453,7 +453,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
text_decoder.decode(&text_data); text_decoder.decode(&text_data);
else else
out.printerr("In call to %s::%s: received invalid text data.\n", 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; break;
default: default:

@ -109,6 +109,24 @@ void RPCService::finalize(ServerConnection *owner, std::vector<ServerFunctionBas
} }
} }
void RPCService::dumpMethods(std::ostream & out) const
{
for (auto fn : functions)
{
std::string in_name = fn->p_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) ServerConnection::ServerConnection(CActiveSocket *socket)
: socket(socket), stream(this) : socket(socket), stream(this)
{ {

@ -150,10 +150,10 @@ namespace DFHack
class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase { class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase {
public: public:
bool bind(RemoteClient *client, const std::string &name, 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, bool bind(color_ostream &out,
RemoteClient *client, const std::string &name, RemoteClient *client, const std::string &name,
const std::string &proto = std::string()); const std::string &plugin = std::string());
bool isValid() { return (id >= 0); } bool isValid() { return (id >= 0); }
@ -167,7 +167,7 @@ namespace DFHack
inline color_ostream &default_ostream(); inline color_ostream &default_ostream();
command_result execute(color_ostream &out, const message_type *input, message_type *output); command_result execute(color_ostream &out, const message_type *input, message_type *output);
std::string name, proto; std::string name, plugin;
RemoteClient *p_client; RemoteClient *p_client;
int16_t id; int16_t id;
}; };
@ -227,7 +227,7 @@ namespace DFHack
friend class RemoteFunctionBase; friend class RemoteFunctionBase;
bool bind(color_ostream &out, RemoteFunctionBase *function, bool bind(color_ostream &out, RemoteFunctionBase *function,
const std::string &name, const std::string &proto); const std::string &name, const std::string &plugin);
public: public:
RemoteClient(color_ostream *default_output = NULL); RemoteClient(color_ostream *default_output = NULL);
@ -269,8 +269,8 @@ namespace DFHack
} }
inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name, inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name,
const std::string &proto) { const std::string &plugin) {
return bind(client->default_output(), client, name, proto); return bind(client->default_output(), client, name, plugin);
} }
class RemoteSuspender { class RemoteSuspender {

@ -151,6 +151,7 @@ namespace DFHack
class DFHACK_EXPORT RPCService { class DFHACK_EXPORT RPCService {
friend class ServerConnection; friend class ServerConnection;
friend class Plugin; friend class Plugin;
friend class Core;
std::vector<ServerFunctionBase*> functions; std::vector<ServerFunctionBase*> functions;
std::map<std::string, ServerFunctionBase*> lookup; std::map<std::string, ServerFunctionBase*> lookup;
@ -208,6 +209,8 @@ namespace DFHack
assert(!owner); assert(!owner);
functions.push_back(new VoidServerMethod<Svc,In>(this, name, flags, fptr)); functions.push_back(new VoidServerMethod<Svc,In>(this, name, flags, fptr));
} }
void dumpMethods(std::ostream & out) const;
}; };
class ServerConnection { class ServerConnection {

@ -5,6 +5,8 @@ option optimize_for = LITE_RUNTIME;
import "RemoteFortressReader.proto"; import "RemoteFortressReader.proto";
// Plugin: RemoteFortressReader
enum AdvmodeMenu enum AdvmodeMenu
{ {
Default = 0; Default = 0;

@ -3,6 +3,8 @@ package ItemdefInstrument;
//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization
option optimize_for = LITE_RUNTIME; option optimize_for = LITE_RUNTIME;
// Plugin: RemoteFortressReader
message InstrumentFlags message InstrumentFlags
{ {
optional bool indefinite_pitch = 1; optional bool indefinite_pitch = 1;

@ -3,8 +3,46 @@ package RemoteFortressReader;
//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization
option optimize_for = LITE_RUNTIME; option optimize_for = LITE_RUNTIME;
// Plugin: RemoteFortressReader
import "ItemdefInstrument.proto"; import "ItemdefInstrument.proto";
// RPC GetMaterialList : EmptyMessage -> MaterialList
// RPC GetGrowthList : EmptyMessage -> MaterialList
// RPC GetBlockList : BlockRequest -> BlockList
// RPC CheckHashes : EmptyMessage -> EmptyMessage
// RPC GetTiletypeList : EmptyMessage -> 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. //We use shapes, etc, because the actual tiletypes may differ between DF versions.
enum TiletypeShape enum TiletypeShape
{ {

@ -3,6 +3,8 @@ package isoworldremote;
//Describes a very basic material structure for the map embark //Describes a very basic material structure for the map embark
option optimize_for = LITE_RUNTIME; option optimize_for = LITE_RUNTIME;
// Plugin: isoworldremote
enum BasicMaterial { enum BasicMaterial {
AIR = 0; AIR = 0;
OTHER = 1; OTHER = 1;
@ -50,6 +52,7 @@ message EmbarkTile {
optional bool is_valid = 7; optional bool is_valid = 7;
} }
// RPC GetEmbarkTile : TileRequest -> EmbarkTile
message TileRequest { message TileRequest {
optional int32 want_x = 1; optional int32 want_x = 1;
optional int32 want_y = 2; optional int32 want_y = 2;
@ -59,6 +62,7 @@ message MapRequest {
optional string save_folder = 1; optional string save_folder = 1;
} }
// RPC GetEmbarkInfo : MapRequest -> MapReply
message MapReply { message MapReply {
required bool available = 1; required bool available = 1;
optional int32 region_x = 2; optional int32 region_x = 2;
@ -69,6 +73,7 @@ message MapReply {
optional int32 current_season = 7; optional int32 current_season = 7;
} }
// RPC GetRawNames : MapRequest -> RawNames
message RawNames { message RawNames {
required bool available = 1; required bool available = 1;
repeated string inorganic = 2; repeated string inorganic = 2;

@ -2,6 +2,8 @@ package dfproto;
option optimize_for = LITE_RUNTIME; option optimize_for = LITE_RUNTIME;
// Plugin: rename
// RPC RenameSquad : RenameSquadIn -> EmptyMessage // RPC RenameSquad : RenameSquadIn -> EmptyMessage
message RenameSquadIn { message RenameSquadIn {
required int32 squad_id = 1; required int32 squad_id = 1;

@ -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))

@ -1,2 +1,4 @@
devel/dump-rpc dfhack-rpc.txt
:lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) :lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR'))
test/main test/main