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