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");
}
}
|