| 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 } |