forked from robotology/yarp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paththrift_tutorial_portable.dox
277 lines (204 loc) · 7.51 KB
/
thrift_tutorial_portable.dox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
\page thrift_tutorial_portable Thrift IDL in YARP: writing a portable
\tableofcontents
YARP built-in types can be sent through Ports. Sometimes you need to send custom
datatypes, in YARP objects that can be sent through ports are called `Portable`
objects.
\ref port_expert shows how you can manually implement a portable object.
This tutorial shows how you can automate this process using the Thrift compiler.
\section thrift_tutorial_portable_intro Introduction
Suppose we have two modules that wish to share a `SharedData` struct that
contains a string and a vector.
Normally you would define the data in a C++ header file and write
serialization/deserialization methods to allow it to be sent and received
to/from a port (this process is called marshalling/demarshalling).
This process requires some YARP specific expertise, is error-prone and tedious.
The idea here is that instead of manually writing the class you define the
structure using an intermediate language (the Thrift IDL language), then you ask
a compiler (the yarpidl_thrift compiler) to generate the class for you,
including all the required code.
Let's see how to do it.
\section thrift_tutorial_portable_thirft Thrift definition for `SharedData`
We start by defining our `ShareData` structure. Open a text editor and type
the following:
~~~{.thrift}
struct SharedData {
1: string text;
2: list<double> content;
}
~~~
name this file `SharedData.thrift` save and close it.
In thrift's syntax this specifies that `SharedData` is a struct that contains a
\em text field of type \em string and a \em content field which type is a
\em vector of floats.
Thrift in fact supports much more options, see \ref thrift_tutorial.
Now the `yarpidl_thrift` compiler can be invoked to generate both `SharedData.h`
and `SharedData.cpp`.
Type:
~~~{.sh}
yarpidl_thrift --gen yarp --out ./ SharedData.thrift
~~~
This will generate two files: `SharedData.h` and `SharedData.cpp`.
In case you are interested you can inspect them to see that they define a C++
class with some extra code.
The good thing is that you don't actually need to bother about the details, but
you can readily use the class in your code.
Now we can use these files in a YARP program.
\section thrift_tutorial_portable_code Code
This code is straightforward. We define a simple sender executable that open a
port and periodically write a `SharedData` object.
As usual we start with the CMake code, write your `CMakeLists.txt`:
~~~{.cmake}
cmake_minimum_required(VERSION 3.12)
find_package(YARP COMPONENTS os sig REQUIRED)
add_executable(sender)
target_sources(sender PRIVATE sender.cpp SharedData.cpp)
target_include_directories(sender PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(sender PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)
~~~
Now we write the code for the sender in `sender.cpp`:
~~~{.cpp}
#include "SharedData.h"
#include <iostream>
#include <yarp/os/Network.h>
#include <yarp/os/BufferedPort.h>
#include <yarp/os/Time.h>
using namespace std;
int main()
{
yarp::os::Network network;
yarp::os::Port port;
if (!port.open("/sender"))
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
cout<<"Starting sender\n";
while(true)
{
SharedData d;
// d.text is a string
d.text="Hello from sender";
//d.content is a vector, let's push some data
d.content.push_back(0.0);
d.content.push_back(0.0);
port.write(d);
yarp::os::Time::delay(0.1);
}
return 0;
}
~~~
Now compile the code
~~~{.sh}
mkdir build
cd build
cmake ../
make
./sender
~~~
Now you can run yarp read on a separate console to inspect the content that is
being transmitted on the port, the output should be something like this:
~~~{.sh}
yarp read ... /sender
~~~
\verbatim
yarp: Port /tmp/port/1 active at tcp://127.0.0.1:10003
yarp: Receiving input from /sender to /tmp/port/1 using tcp
"Hello from sender" (0.0 0.0)
"Hello from sender" (0.0 0.0)
\endverbatim
It is simple to write a receiver:
Append the following to the `CMakeLists.txt`
~~~{.cmake}
add_executable(receiver)
target_sources(receiver PRIVATE receiver.cpp
SharedData.cpp)
target_link_libraries(receiver PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)
~~~
This is the receiver code in `receiver.cpp`:
~~~{.cpp}
#include <SharedData.h>
#include <iostream>
#include <yarp/os/Network.h>
#include <yarp/os/BufferedPort.h>
using namespace std;
int main()
{
yarp::os::Network network;
cout<<"Starting receiver\n";
yarp::os::Port port;
if (!port.open("/receiver"))
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
while(true)
{
SharedData d;
port.read(d);
//access d
}
return 0;
}
~~~
\section thrift_tutorial_portable_using_cmake Using CMake
YARP provides CMake supports to automate the invocation of `yarpidl_thrift`.
This is convenient in large projects when we generate several files and we do
not want to keep track of all of them individually.
We can use `yarp_idl_to_dir` to tell CMake to parse `SharedData.thrift` with the
thrift compiler.
All generated files will be placed in separate `include` and `src` directories.
~~~{.cmake}
# compile definition file to generate source code into the desired directory
set(generated_libs_dir "${CMAKE_CURRENT_SOURCE_DIR}")
yarp_idl_to_dir(INPUT_FILES SharedData.thrift
OUTPUT_DIR ${generated_libs_dir}
SOURCES_VAR sources
HEADERS_VAR headers
INCLUDE_DIRS_VAR include_dirs)
# create the sender
add_executable(sender)
target_sources(sender PRIVATE sender.cpp
${headers}
${sources})
target_include_directories(sender PRIVATE ${include_dirs})
target_link_libraries(sender PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)
# create the receiver
add_executable(receiver)
target_sources(receiver PRIVATE receiver.cpp
${headers}
${sources})
target_include_directories(receiver PRIVATE ${include_dirs})
target_link_libraries(receiver PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)
~~~
Now you just have to execute CMake.
The variable `ALLOW_IDL_GENERATION` controls if the thrift compiler is executed
to generate `SharedData.h/cpp` or not.
It will be off by default if there is already generated code in the desired
directory, you'll need to turn it on to overwrite that.
~~~{.sh}
cd build
cmake ../ -DALLOW_IDL_GENERATION=TRUE
~~~
The code generation step is required only when `SharedData.thrift` is modified.
\section thrift_tutorial_portable_related_tutorials Related Tutorials
The Thrift IDL also allows defining modules interfaces, this is explained here:
\li \ref thrift_tutorial_simple
\li \ref thrift_tutorial
YARP supports ROS types.
An alternative method to define portable objects in YARP is to use the ROS
syntax (i.e. through a .msg file).
The disadvantage of this approach is that you cannot use native YARP types, but
the advantage is that your messages will be compatible with ROS and readable
from a topic. This tutorial shows how you do this:
\li \ref rostypes_tutorial_portable
Code for this tutorial can be found here: `example/idl/portable`
*/