capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

calculator-client.c++ (13000B)


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