File: example_client.cpp

package info (click to toggle)
r-cran-rinside 0.2.19-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 668 kB
  • sloc: cpp: 3,310; ansic: 117; xml: 57; ruby: 34; makefile: 2
file content (216 lines) | stat: -rw-r--r-- 7,320 bytes parent folder | download | duplicates (5)
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
/*
 * Copyright (c) 2014 Christian Authmann
 */

#include "datatypes/foo.h"
#include "datatypes/bar.h"
#include "common/constants.h"
#include "common/binarystream.h"
#include "client/rinsideclient.h"

#include <limits>
#include <memory>
#include <sstream>
#include <fstream>
#include <string>
#include <mutex>
#include <functional>
#include <cmath>

/*
 * The following examples often talk about "serializable types". These are any
 * user-defined objects with a TYPEID, serialize() and deserialize() methods
 * (in this example: Foo and Bar) as well as std::string, most arithmetic types
 * and vectors of these.
 *
 * See common/typeid.h for a list.
 */

static void test_setting_getting() {
	auto stream = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R(stream);

	/*
	 * We can set variables in the R environment to any object we like, provided
	 * that the object is of a serializable type.
	 * We can get them accordingly.
	 */
	Foo foo("testfoo", 42, 43);
	printf("setting Foo(%s, %d, %d) in the R environment\n", foo.name.c_str(), foo.a, foo.b);
	R.setValue("foo", foo);

	auto foo2 = R.getValue<Foo>("foo");
	printf("got Foo(%s, %d, %d) via getValue\n", foo2.name.c_str(), foo2.a, foo2.b);

	auto foo3 = R.parseEval<Foo>("foo");
	printf("got Foo(%s, %d, %d) via parseEval\n", foo3.name.c_str(), foo3.a, foo3.b);

	try {
		auto foo = R.getValue<Foo>("IDoNotExist");
	}
	catch (const std::runtime_error &e) {
		printf("Getting a nonexistent variable failed with message:\n%s\n", e.what());
	}

	try {
		auto bar = R.getValue<Bar>("foo");
	}
	catch (const std::runtime_error &e) {
		printf("Getting foo as an object of class Bar failed with message:\n%s\n", e.what());
	}
}


static void test_callbacks() {
	// We initialize a new connection. The server will spawn a new process with a clean environment.
	auto stream = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R(stream);

	/*
	 * We can provide C++ functions to the R environment. Parameters and return value
	 * must be of a serializable type.
	 *
	 * This has a bit of an overhead, since each time a function is called, the parameters
	 * are sent over the network from R to C++, then the function is executed, and the
	 * result is sent back from C++ to R.
	 * You will want to avoid sending large objects, and you will want to avoid calling
	 * remote functions hundreds of times per second.
	 */
	std::function<Foo(const std::string &)> loadFoo =
		[] (const std::string &name) -> Foo {
		return Foo(name, name.length(), 1);
	};
	R.setCallback("loadFoo", loadFoo);

	std::function<Foo(const Foo &)> swapFoo =
		[] (const Foo &foo) -> Foo {
		return Foo(foo.name, foo.b, foo.a);
	};
	R.setCallback("swapFoo", swapFoo);

	std::function<Bar(int)> loadBar =
		[] (int id) -> Bar {
		std::string foo_name = std::string("foo_") + std::to_string(id);
		return Bar(foo_name, Foo(foo_name, foo_name.length(), id));
	};
	R.setCallback("loadBar", loadBar);

	std::function<std::vector<float>(float, float, const std::vector<int> &)> calibrate =
		[] (float offset, float scale, const std::vector<int> &in) -> std::vector<float> {
		std::vector<float> out;
		out.reserve(in.size());
		for ( auto &v : in )
			out.push_back(offset + (float) v * scale);
		return out;
	};
	R.setCallback("calibrate", calibrate);

	auto foo = R.parseEval<Foo>("foo = loadFoo('loaded')");
	printf("got Foo(%s, %d, %d) via loadFoo()\n", foo.name.c_str(), foo.a, foo.b);

	auto foo2 = R.parseEval<Foo>("swapFoo(foo)");
	printf("got Foo(%s, %d, %d) after swapFoo()\n", foo2.name.c_str(), foo2.a, foo2.b);

	auto bar = R.parseEval<Bar>("loadBar(42)");
	printf("got Bar(%s, Foo(%s, %d, %d))\n", bar.name.c_str(), bar.foo.name.c_str(), bar.foo.a, bar.foo.b);

	auto vec = R.parseEval<std::vector<float>>("calibrate(1.0, 0.3, c(1,2,3,4,5))");
	printf("Got c(");
	for (auto &v : vec)
		printf("%.2f, ", v);
	printf(") from calibrate()\n");

	try {
		auto foo3 = R.parseEval<Foo>("loadFoo()");
		printf("got Foo(%s, %d, %d) via loadFoo()\n", foo3.name.c_str(), foo3.a, foo3.b);
	}
	catch (const std::runtime_error &e) {
		printf("Calling loadFoo() with wrong parameters failed with message:\n%s\n", e.what());
	}
	catch (...) {
		printf("Calling loadFoo() with wrong parameters lead to an unrecoverable error, ending test\n");
		return;
	}

	// Passing incompatible parameters results in recoverable errors, so we can keep using the connection
	auto x = R.parseEval<int>("x = 42;");
	printf("Got x = %d\n", x);
}


static void test_console_output() {
	auto stream = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R(stream);

	/*
	 * It's probably useful to capture the output of R's console.
	 * So here's how you do it.
	 */
	R.parseEvalQ("print('Hello World')");
	auto output = R.getConsoleOutput();
	printf("Output of the R script:\n%s\n", output.c_str());
}


static void test_plot() {
	auto stream = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R(stream);

	/*
	 * According to a totally representative user survey, the main use of R is to draw fancy plots [citation needed].
	 *
	 * Of course, we can do that.
	 */
	R.initPlot(400,600);
	R.parseEvalQ("plot(c(0,0), type = 'n', xlim=c(0,1), ylim=c(-1,1), xlab = 'x', ylab = 'y', bty='n')");

	R.parseEvalQ("lines(c(0,0), c(-1,1), col='red', add=TRUE)");
	R.parseEvalQ("curve(-x, 0, 1, 200, col='blue', add=TRUE)");
	R.parseEvalQ("curve(0.5+sqrt(1-x^2)/2, 0, 1, 200, col='#00FF00', add=TRUE)");
	R.parseEvalQ("curve(0.5-sqrt(1-x^2)/2, 0, 1, 200, col='#33EE33', add=TRUE)");
	auto png = R.getPlot();
	printf("Got a png from the plot, saving to plot.png\n");

	std::fstream f("plot.png", std::fstream::out | std::fstream::binary | std::fstream::trunc);
	f << png;
	f.close();
}

static void test_multiple() {
	/*
	 * For our last trick, we'd like to show something that cannot be replicated using RInside directly:
	 * Handling multiple R environments at the same time.
	 */
	auto stream1 = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R1(stream1);

	auto stream2 = BinaryStream::connectToUnixSocket(ris_socket_address);
	RInsideClient R2(stream2);

	R1.setValue("id", 1);
	R2.setValue("id", 2);

	auto id1 = R1.getValue<int>("id");
	auto id2 = R2.getValue<int>("id");

	printf("id of environment 1 is: %d, id of environment 2 is: %d\n", id1, id2);
}


int main(void) {
	try {
		printf("==========================\nTesting setting and getting:\n==========================\n");
		test_setting_getting();
		printf("\n==========================\nTesting callbacks:\n==========================\n");
		test_callbacks();
		printf("\n==========================\nTesting console output:\n==========================\n");
		test_console_output();
		printf("\n==========================\nTesting plots:\n==========================\n");
		test_plot();
		printf("\n==========================\nTesting multiple environments:\n==========================\n");
		test_multiple();
	}
	catch (const BinaryStream::stream_exception &e) {
		printf("Error communicating with the server\nDid you start ./example_server?\n");
	}
}