cannam@62
|
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
cannam@62
|
2 // Licensed under the MIT License:
|
cannam@62
|
3 //
|
cannam@62
|
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
|
cannam@62
|
5 // of this software and associated documentation files (the "Software"), to deal
|
cannam@62
|
6 // in the Software without restriction, including without limitation the rights
|
cannam@62
|
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
cannam@62
|
8 // copies of the Software, and to permit persons to whom the Software is
|
cannam@62
|
9 // furnished to do so, subject to the following conditions:
|
cannam@62
|
10 //
|
cannam@62
|
11 // The above copyright notice and this permission notice shall be included in
|
cannam@62
|
12 // all copies or substantial portions of the Software.
|
cannam@62
|
13 //
|
cannam@62
|
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
cannam@62
|
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
cannam@62
|
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
cannam@62
|
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
cannam@62
|
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
cannam@62
|
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
cannam@62
|
20 // THE SOFTWARE.
|
cannam@62
|
21
|
cannam@62
|
22 #include "calculator.capnp.h"
|
cannam@62
|
23 #include <capnp/ez-rpc.h>
|
cannam@62
|
24 #include <kj/debug.h>
|
cannam@62
|
25 #include <math.h>
|
cannam@62
|
26 #include <iostream>
|
cannam@62
|
27
|
cannam@62
|
28 class PowerFunction final: public Calculator::Function::Server {
|
cannam@62
|
29 // An implementation of the Function interface wrapping pow(). Note that
|
cannam@62
|
30 // we're implementing this on the client side and will pass a reference to
|
cannam@62
|
31 // the server. The server will then be able to make calls back to the client.
|
cannam@62
|
32
|
cannam@62
|
33 public:
|
cannam@62
|
34 kj::Promise<void> call(CallContext context) {
|
cannam@62
|
35 auto params = context.getParams().getParams();
|
cannam@62
|
36 KJ_REQUIRE(params.size() == 2, "Wrong number of parameters.");
|
cannam@62
|
37 context.getResults().setValue(pow(params[0], params[1]));
|
cannam@62
|
38 return kj::READY_NOW;
|
cannam@62
|
39 }
|
cannam@62
|
40 };
|
cannam@62
|
41
|
cannam@62
|
42 int main(int argc, const char* argv[]) {
|
cannam@62
|
43 if (argc != 2) {
|
cannam@62
|
44 std::cerr << "usage: " << argv[0] << " HOST:PORT\n"
|
cannam@62
|
45 "Connects to the Calculator server at the given address and "
|
cannam@62
|
46 "does some RPCs." << std::endl;
|
cannam@62
|
47 return 1;
|
cannam@62
|
48 }
|
cannam@62
|
49
|
cannam@62
|
50 capnp::EzRpcClient client(argv[1]);
|
cannam@62
|
51 Calculator::Client calculator = client.getMain<Calculator>();
|
cannam@62
|
52
|
cannam@62
|
53 // Keep an eye on `waitScope`. Whenever you see it used is a place where we
|
cannam@62
|
54 // stop and wait for the server to respond. If a line of code does not use
|
cannam@62
|
55 // `waitScope`, then it does not block!
|
cannam@62
|
56 auto& waitScope = client.getWaitScope();
|
cannam@62
|
57
|
cannam@62
|
58 {
|
cannam@62
|
59 // Make a request that just evaluates the literal value 123.
|
cannam@62
|
60 //
|
cannam@62
|
61 // What's interesting here is that evaluate() returns a "Value", which is
|
cannam@62
|
62 // another interface and therefore points back to an object living on the
|
cannam@62
|
63 // server. We then have to call read() on that object to read it.
|
cannam@62
|
64 // However, even though we are making two RPC's, this block executes in
|
cannam@62
|
65 // *one* network round trip because of promise pipelining: we do not wait
|
cannam@62
|
66 // for the first call to complete before we send the second call to the
|
cannam@62
|
67 // server.
|
cannam@62
|
68
|
cannam@62
|
69 std::cout << "Evaluating a literal... ";
|
cannam@62
|
70 std::cout.flush();
|
cannam@62
|
71
|
cannam@62
|
72 // Set up the request.
|
cannam@62
|
73 auto request = calculator.evaluateRequest();
|
cannam@62
|
74 request.getExpression().setLiteral(123);
|
cannam@62
|
75
|
cannam@62
|
76 // Send it, which returns a promise for the result (without blocking).
|
cannam@62
|
77 auto evalPromise = request.send();
|
cannam@62
|
78
|
cannam@62
|
79 // Using the promise, create a pipelined request to call read() on the
|
cannam@62
|
80 // returned object, and then send that.
|
cannam@62
|
81 auto readPromise = evalPromise.getValue().readRequest().send();
|
cannam@62
|
82
|
cannam@62
|
83 // Now that we've sent all the requests, wait for the response. Until this
|
cannam@62
|
84 // point, we haven't waited at all!
|
cannam@62
|
85 auto response = readPromise.wait(waitScope);
|
cannam@62
|
86 KJ_ASSERT(response.getValue() == 123);
|
cannam@62
|
87
|
cannam@62
|
88 std::cout << "PASS" << std::endl;
|
cannam@62
|
89 }
|
cannam@62
|
90
|
cannam@62
|
91 {
|
cannam@62
|
92 // Make a request to evaluate 123 + 45 - 67.
|
cannam@62
|
93 //
|
cannam@62
|
94 // The Calculator interface requires that we first call getOperator() to
|
cannam@62
|
95 // get the addition and subtraction functions, then call evaluate() to use
|
cannam@62
|
96 // them. But, once again, we can get both functions, call evaluate(), and
|
cannam@62
|
97 // then read() the result -- four RPCs -- in the time of *one* network
|
cannam@62
|
98 // round trip, because of promise pipelining.
|
cannam@62
|
99
|
cannam@62
|
100 std::cout << "Using add and subtract... ";
|
cannam@62
|
101 std::cout.flush();
|
cannam@62
|
102
|
cannam@62
|
103 Calculator::Function::Client add = nullptr;
|
cannam@62
|
104 Calculator::Function::Client subtract = nullptr;
|
cannam@62
|
105
|
cannam@62
|
106 {
|
cannam@62
|
107 // Get the "add" function from the server.
|
cannam@62
|
108 auto request = calculator.getOperatorRequest();
|
cannam@62
|
109 request.setOp(Calculator::Operator::ADD);
|
cannam@62
|
110 add = request.send().getFunc();
|
cannam@62
|
111 }
|
cannam@62
|
112
|
cannam@62
|
113 {
|
cannam@62
|
114 // Get the "subtract" function from the server.
|
cannam@62
|
115 auto request = calculator.getOperatorRequest();
|
cannam@62
|
116 request.setOp(Calculator::Operator::SUBTRACT);
|
cannam@62
|
117 subtract = request.send().getFunc();
|
cannam@62
|
118 }
|
cannam@62
|
119
|
cannam@62
|
120 // Build the request to evaluate 123 + 45 - 67.
|
cannam@62
|
121 auto request = calculator.evaluateRequest();
|
cannam@62
|
122
|
cannam@62
|
123 auto subtractCall = request.getExpression().initCall();
|
cannam@62
|
124 subtractCall.setFunction(subtract);
|
cannam@62
|
125 auto subtractParams = subtractCall.initParams(2);
|
cannam@62
|
126 subtractParams[1].setLiteral(67);
|
cannam@62
|
127
|
cannam@62
|
128 auto addCall = subtractParams[0].initCall();
|
cannam@62
|
129 addCall.setFunction(add);
|
cannam@62
|
130 auto addParams = addCall.initParams(2);
|
cannam@62
|
131 addParams[0].setLiteral(123);
|
cannam@62
|
132 addParams[1].setLiteral(45);
|
cannam@62
|
133
|
cannam@62
|
134 // Send the evaluate() request, read() the result, and wait for read() to
|
cannam@62
|
135 // finish.
|
cannam@62
|
136 auto evalPromise = request.send();
|
cannam@62
|
137 auto readPromise = evalPromise.getValue().readRequest().send();
|
cannam@62
|
138
|
cannam@62
|
139 auto response = readPromise.wait(waitScope);
|
cannam@62
|
140 KJ_ASSERT(response.getValue() == 101);
|
cannam@62
|
141
|
cannam@62
|
142 std::cout << "PASS" << std::endl;
|
cannam@62
|
143 }
|
cannam@62
|
144
|
cannam@62
|
145 {
|
cannam@62
|
146 // Make a request to evaluate 4 * 6, then use the result in two more
|
cannam@62
|
147 // requests that add 3 and 5.
|
cannam@62
|
148 //
|
cannam@62
|
149 // Since evaluate() returns its result wrapped in a `Value`, we can pass
|
cannam@62
|
150 // that `Value` back to the server in subsequent requests before the first
|
cannam@62
|
151 // `evaluate()` has actually returned. Thus, this example again does only
|
cannam@62
|
152 // one network round trip.
|
cannam@62
|
153
|
cannam@62
|
154 std::cout << "Pipelining eval() calls... ";
|
cannam@62
|
155 std::cout.flush();
|
cannam@62
|
156
|
cannam@62
|
157 Calculator::Function::Client add = nullptr;
|
cannam@62
|
158 Calculator::Function::Client multiply = nullptr;
|
cannam@62
|
159
|
cannam@62
|
160 {
|
cannam@62
|
161 // Get the "add" function from the server.
|
cannam@62
|
162 auto request = calculator.getOperatorRequest();
|
cannam@62
|
163 request.setOp(Calculator::Operator::ADD);
|
cannam@62
|
164 add = request.send().getFunc();
|
cannam@62
|
165 }
|
cannam@62
|
166
|
cannam@62
|
167 {
|
cannam@62
|
168 // Get the "multiply" function from the server.
|
cannam@62
|
169 auto request = calculator.getOperatorRequest();
|
cannam@62
|
170 request.setOp(Calculator::Operator::MULTIPLY);
|
cannam@62
|
171 multiply = request.send().getFunc();
|
cannam@62
|
172 }
|
cannam@62
|
173
|
cannam@62
|
174 // Build the request to evaluate 4 * 6
|
cannam@62
|
175 auto request = calculator.evaluateRequest();
|
cannam@62
|
176
|
cannam@62
|
177 auto multiplyCall = request.getExpression().initCall();
|
cannam@62
|
178 multiplyCall.setFunction(multiply);
|
cannam@62
|
179 auto multiplyParams = multiplyCall.initParams(2);
|
cannam@62
|
180 multiplyParams[0].setLiteral(4);
|
cannam@62
|
181 multiplyParams[1].setLiteral(6);
|
cannam@62
|
182
|
cannam@62
|
183 auto multiplyResult = request.send().getValue();
|
cannam@62
|
184
|
cannam@62
|
185 // Use the result in two calls that add 3 and add 5.
|
cannam@62
|
186
|
cannam@62
|
187 auto add3Request = calculator.evaluateRequest();
|
cannam@62
|
188 auto add3Call = add3Request.getExpression().initCall();
|
cannam@62
|
189 add3Call.setFunction(add);
|
cannam@62
|
190 auto add3Params = add3Call.initParams(2);
|
cannam@62
|
191 add3Params[0].setPreviousResult(multiplyResult);
|
cannam@62
|
192 add3Params[1].setLiteral(3);
|
cannam@62
|
193 auto add3Promise = add3Request.send().getValue().readRequest().send();
|
cannam@62
|
194
|
cannam@62
|
195 auto add5Request = calculator.evaluateRequest();
|
cannam@62
|
196 auto add5Call = add5Request.getExpression().initCall();
|
cannam@62
|
197 add5Call.setFunction(add);
|
cannam@62
|
198 auto add5Params = add5Call.initParams(2);
|
cannam@62
|
199 add5Params[0].setPreviousResult(multiplyResult);
|
cannam@62
|
200 add5Params[1].setLiteral(5);
|
cannam@62
|
201 auto add5Promise = add5Request.send().getValue().readRequest().send();
|
cannam@62
|
202
|
cannam@62
|
203 // Now wait for the results.
|
cannam@62
|
204 KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27);
|
cannam@62
|
205 KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29);
|
cannam@62
|
206
|
cannam@62
|
207 std::cout << "PASS" << std::endl;
|
cannam@62
|
208 }
|
cannam@62
|
209
|
cannam@62
|
210 {
|
cannam@62
|
211 // Our calculator interface supports defining functions. Here we use it
|
cannam@62
|
212 // to define two functions and then make calls to them as follows:
|
cannam@62
|
213 //
|
cannam@62
|
214 // f(x, y) = x * 100 + y
|
cannam@62
|
215 // g(x) = f(x, x + 1) * 2;
|
cannam@62
|
216 // f(12, 34)
|
cannam@62
|
217 // g(21)
|
cannam@62
|
218 //
|
cannam@62
|
219 // Once again, the whole thing takes only one network round trip.
|
cannam@62
|
220
|
cannam@62
|
221 std::cout << "Defining functions... ";
|
cannam@62
|
222 std::cout.flush();
|
cannam@62
|
223
|
cannam@62
|
224 Calculator::Function::Client add = nullptr;
|
cannam@62
|
225 Calculator::Function::Client multiply = nullptr;
|
cannam@62
|
226 Calculator::Function::Client f = nullptr;
|
cannam@62
|
227 Calculator::Function::Client g = nullptr;
|
cannam@62
|
228
|
cannam@62
|
229 {
|
cannam@62
|
230 // Get the "add" function from the server.
|
cannam@62
|
231 auto request = calculator.getOperatorRequest();
|
cannam@62
|
232 request.setOp(Calculator::Operator::ADD);
|
cannam@62
|
233 add = request.send().getFunc();
|
cannam@62
|
234 }
|
cannam@62
|
235
|
cannam@62
|
236 {
|
cannam@62
|
237 // Get the "multiply" function from the server.
|
cannam@62
|
238 auto request = calculator.getOperatorRequest();
|
cannam@62
|
239 request.setOp(Calculator::Operator::MULTIPLY);
|
cannam@62
|
240 multiply = request.send().getFunc();
|
cannam@62
|
241 }
|
cannam@62
|
242
|
cannam@62
|
243 {
|
cannam@62
|
244 // Define f.
|
cannam@62
|
245 auto request = calculator.defFunctionRequest();
|
cannam@62
|
246 request.setParamCount(2);
|
cannam@62
|
247
|
cannam@62
|
248 {
|
cannam@62
|
249 // Build the function body.
|
cannam@62
|
250 auto addCall = request.getBody().initCall();
|
cannam@62
|
251 addCall.setFunction(add);
|
cannam@62
|
252 auto addParams = addCall.initParams(2);
|
cannam@62
|
253 addParams[1].setParameter(1); // y
|
cannam@62
|
254
|
cannam@62
|
255 auto multiplyCall = addParams[0].initCall();
|
cannam@62
|
256 multiplyCall.setFunction(multiply);
|
cannam@62
|
257 auto multiplyParams = multiplyCall.initParams(2);
|
cannam@62
|
258 multiplyParams[0].setParameter(0); // x
|
cannam@62
|
259 multiplyParams[1].setLiteral(100);
|
cannam@62
|
260 }
|
cannam@62
|
261
|
cannam@62
|
262 f = request.send().getFunc();
|
cannam@62
|
263 }
|
cannam@62
|
264
|
cannam@62
|
265 {
|
cannam@62
|
266 // Define g.
|
cannam@62
|
267 auto request = calculator.defFunctionRequest();
|
cannam@62
|
268 request.setParamCount(1);
|
cannam@62
|
269
|
cannam@62
|
270 {
|
cannam@62
|
271 // Build the function body.
|
cannam@62
|
272 auto multiplyCall = request.getBody().initCall();
|
cannam@62
|
273 multiplyCall.setFunction(multiply);
|
cannam@62
|
274 auto multiplyParams = multiplyCall.initParams(2);
|
cannam@62
|
275 multiplyParams[1].setLiteral(2);
|
cannam@62
|
276
|
cannam@62
|
277 auto fCall = multiplyParams[0].initCall();
|
cannam@62
|
278 fCall.setFunction(f);
|
cannam@62
|
279 auto fParams = fCall.initParams(2);
|
cannam@62
|
280 fParams[0].setParameter(0);
|
cannam@62
|
281
|
cannam@62
|
282 auto addCall = fParams[1].initCall();
|
cannam@62
|
283 addCall.setFunction(add);
|
cannam@62
|
284 auto addParams = addCall.initParams(2);
|
cannam@62
|
285 addParams[0].setParameter(0);
|
cannam@62
|
286 addParams[1].setLiteral(1);
|
cannam@62
|
287 }
|
cannam@62
|
288
|
cannam@62
|
289 g = request.send().getFunc();
|
cannam@62
|
290 }
|
cannam@62
|
291
|
cannam@62
|
292 // OK, we've defined all our functions. Now create our eval requests.
|
cannam@62
|
293
|
cannam@62
|
294 // f(12, 34)
|
cannam@62
|
295 auto fEvalRequest = calculator.evaluateRequest();
|
cannam@62
|
296 auto fCall = fEvalRequest.initExpression().initCall();
|
cannam@62
|
297 fCall.setFunction(f);
|
cannam@62
|
298 auto fParams = fCall.initParams(2);
|
cannam@62
|
299 fParams[0].setLiteral(12);
|
cannam@62
|
300 fParams[1].setLiteral(34);
|
cannam@62
|
301 auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send();
|
cannam@62
|
302
|
cannam@62
|
303 // g(21)
|
cannam@62
|
304 auto gEvalRequest = calculator.evaluateRequest();
|
cannam@62
|
305 auto gCall = gEvalRequest.initExpression().initCall();
|
cannam@62
|
306 gCall.setFunction(g);
|
cannam@62
|
307 gCall.initParams(1)[0].setLiteral(21);
|
cannam@62
|
308 auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send();
|
cannam@62
|
309
|
cannam@62
|
310 // Wait for the results.
|
cannam@62
|
311 KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234);
|
cannam@62
|
312 KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244);
|
cannam@62
|
313
|
cannam@62
|
314 std::cout << "PASS" << std::endl;
|
cannam@62
|
315 }
|
cannam@62
|
316
|
cannam@62
|
317 {
|
cannam@62
|
318 // Make a request that will call back to a function defined locally.
|
cannam@62
|
319 //
|
cannam@62
|
320 // Specifically, we will compute 2^(4 + 5). However, exponent is not
|
cannam@62
|
321 // defined by the Calculator server. So, we'll implement the Function
|
cannam@62
|
322 // interface locally and pass it to the server for it to use when
|
cannam@62
|
323 // evaluating the expression.
|
cannam@62
|
324 //
|
cannam@62
|
325 // This example requires two network round trips to complete, because the
|
cannam@62
|
326 // server calls back to the client once before finishing. In this
|
cannam@62
|
327 // particular case, this could potentially be optimized by using a tail
|
cannam@62
|
328 // call on the server side -- see CallContext::tailCall(). However, to
|
cannam@62
|
329 // keep the example simpler, we haven't implemented this optimization in
|
cannam@62
|
330 // the sample server.
|
cannam@62
|
331
|
cannam@62
|
332 std::cout << "Using a callback... ";
|
cannam@62
|
333 std::cout.flush();
|
cannam@62
|
334
|
cannam@62
|
335 Calculator::Function::Client add = nullptr;
|
cannam@62
|
336
|
cannam@62
|
337 {
|
cannam@62
|
338 // Get the "add" function from the server.
|
cannam@62
|
339 auto request = calculator.getOperatorRequest();
|
cannam@62
|
340 request.setOp(Calculator::Operator::ADD);
|
cannam@62
|
341 add = request.send().getFunc();
|
cannam@62
|
342 }
|
cannam@62
|
343
|
cannam@62
|
344 // Build the eval request for 2^(4+5).
|
cannam@62
|
345 auto request = calculator.evaluateRequest();
|
cannam@62
|
346
|
cannam@62
|
347 auto powCall = request.getExpression().initCall();
|
cannam@62
|
348 powCall.setFunction(kj::heap<PowerFunction>());
|
cannam@62
|
349 auto powParams = powCall.initParams(2);
|
cannam@62
|
350 powParams[0].setLiteral(2);
|
cannam@62
|
351
|
cannam@62
|
352 auto addCall = powParams[1].initCall();
|
cannam@62
|
353 addCall.setFunction(add);
|
cannam@62
|
354 auto addParams = addCall.initParams(2);
|
cannam@62
|
355 addParams[0].setLiteral(4);
|
cannam@62
|
356 addParams[1].setLiteral(5);
|
cannam@62
|
357
|
cannam@62
|
358 // Send the request and wait.
|
cannam@62
|
359 auto response = request.send().getValue().readRequest()
|
cannam@62
|
360 .send().wait(waitScope);
|
cannam@62
|
361 KJ_ASSERT(response.getValue() == 512);
|
cannam@62
|
362
|
cannam@62
|
363 std::cout << "PASS" << std::endl;
|
cannam@62
|
364 }
|
cannam@62
|
365
|
cannam@62
|
366 return 0;
|
cannam@62
|
367 }
|