annotate src/capnproto-git-20161025/c++/samples/calculator-client.c++ @ 48:9530b331f8c1

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