cannam@48: // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors cannam@48: // Licensed under the MIT License: cannam@48: // cannam@48: // Permission is hereby granted, free of charge, to any person obtaining a copy cannam@48: // of this software and associated documentation files (the "Software"), to deal cannam@48: // in the Software without restriction, including without limitation the rights cannam@48: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cannam@48: // copies of the Software, and to permit persons to whom the Software is cannam@48: // furnished to do so, subject to the following conditions: cannam@48: // cannam@48: // The above copyright notice and this permission notice shall be included in cannam@48: // all copies or substantial portions of the Software. cannam@48: // cannam@48: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR cannam@48: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, cannam@48: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE cannam@48: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER cannam@48: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, cannam@48: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN cannam@48: // THE SOFTWARE. cannam@48: cannam@48: #include "calculator.capnp.h" cannam@48: #include cannam@48: #include cannam@48: #include cannam@48: #include cannam@48: cannam@48: class PowerFunction final: public Calculator::Function::Server { cannam@48: // An implementation of the Function interface wrapping pow(). Note that cannam@48: // we're implementing this on the client side and will pass a reference to cannam@48: // the server. The server will then be able to make calls back to the client. cannam@48: cannam@48: public: cannam@48: kj::Promise call(CallContext context) { cannam@48: auto params = context.getParams().getParams(); cannam@48: KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); cannam@48: context.getResults().setValue(pow(params[0], params[1])); cannam@48: return kj::READY_NOW; cannam@48: } cannam@48: }; cannam@48: cannam@48: int main(int argc, const char* argv[]) { cannam@48: if (argc != 2) { cannam@48: std::cerr << "usage: " << argv[0] << " HOST:PORT\n" cannam@48: "Connects to the Calculator server at the given address and " cannam@48: "does some RPCs." << std::endl; cannam@48: return 1; cannam@48: } cannam@48: cannam@48: capnp::EzRpcClient client(argv[1]); cannam@48: Calculator::Client calculator = client.getMain(); cannam@48: cannam@48: // Keep an eye on `waitScope`. Whenever you see it used is a place where we cannam@48: // stop and wait for the server to respond. If a line of code does not use cannam@48: // `waitScope`, then it does not block! cannam@48: auto& waitScope = client.getWaitScope(); cannam@48: cannam@48: { cannam@48: // Make a request that just evaluates the literal value 123. cannam@48: // cannam@48: // What's interesting here is that evaluate() returns a "Value", which is cannam@48: // another interface and therefore points back to an object living on the cannam@48: // server. We then have to call read() on that object to read it. cannam@48: // However, even though we are making two RPC's, this block executes in cannam@48: // *one* network round trip because of promise pipelining: we do not wait cannam@48: // for the first call to complete before we send the second call to the cannam@48: // server. cannam@48: cannam@48: std::cout << "Evaluating a literal... "; cannam@48: std::cout.flush(); cannam@48: cannam@48: // Set up the request. cannam@48: auto request = calculator.evaluateRequest(); cannam@48: request.getExpression().setLiteral(123); cannam@48: cannam@48: // Send it, which returns a promise for the result (without blocking). cannam@48: auto evalPromise = request.send(); cannam@48: cannam@48: // Using the promise, create a pipelined request to call read() on the cannam@48: // returned object, and then send that. cannam@48: auto readPromise = evalPromise.getValue().readRequest().send(); cannam@48: cannam@48: // Now that we've sent all the requests, wait for the response. Until this cannam@48: // point, we haven't waited at all! cannam@48: auto response = readPromise.wait(waitScope); cannam@48: KJ_ASSERT(response.getValue() == 123); cannam@48: cannam@48: std::cout << "PASS" << std::endl; cannam@48: } cannam@48: cannam@48: { cannam@48: // Make a request to evaluate 123 + 45 - 67. cannam@48: // cannam@48: // The Calculator interface requires that we first call getOperator() to cannam@48: // get the addition and subtraction functions, then call evaluate() to use cannam@48: // them. But, once again, we can get both functions, call evaluate(), and cannam@48: // then read() the result -- four RPCs -- in the time of *one* network cannam@48: // round trip, because of promise pipelining. cannam@48: cannam@48: std::cout << "Using add and subtract... "; cannam@48: std::cout.flush(); cannam@48: cannam@48: Calculator::Function::Client add = nullptr; cannam@48: Calculator::Function::Client subtract = nullptr; cannam@48: cannam@48: { cannam@48: // Get the "add" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::ADD); cannam@48: add = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: { cannam@48: // Get the "subtract" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::SUBTRACT); cannam@48: subtract = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: // Build the request to evaluate 123 + 45 - 67. cannam@48: auto request = calculator.evaluateRequest(); cannam@48: cannam@48: auto subtractCall = request.getExpression().initCall(); cannam@48: subtractCall.setFunction(subtract); cannam@48: auto subtractParams = subtractCall.initParams(2); cannam@48: subtractParams[1].setLiteral(67); cannam@48: cannam@48: auto addCall = subtractParams[0].initCall(); cannam@48: addCall.setFunction(add); cannam@48: auto addParams = addCall.initParams(2); cannam@48: addParams[0].setLiteral(123); cannam@48: addParams[1].setLiteral(45); cannam@48: cannam@48: // Send the evaluate() request, read() the result, and wait for read() to cannam@48: // finish. cannam@48: auto evalPromise = request.send(); cannam@48: auto readPromise = evalPromise.getValue().readRequest().send(); cannam@48: cannam@48: auto response = readPromise.wait(waitScope); cannam@48: KJ_ASSERT(response.getValue() == 101); cannam@48: cannam@48: std::cout << "PASS" << std::endl; cannam@48: } cannam@48: cannam@48: { cannam@48: // Make a request to evaluate 4 * 6, then use the result in two more cannam@48: // requests that add 3 and 5. cannam@48: // cannam@48: // Since evaluate() returns its result wrapped in a `Value`, we can pass cannam@48: // that `Value` back to the server in subsequent requests before the first cannam@48: // `evaluate()` has actually returned. Thus, this example again does only cannam@48: // one network round trip. cannam@48: cannam@48: std::cout << "Pipelining eval() calls... "; cannam@48: std::cout.flush(); cannam@48: cannam@48: Calculator::Function::Client add = nullptr; cannam@48: Calculator::Function::Client multiply = nullptr; cannam@48: cannam@48: { cannam@48: // Get the "add" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::ADD); cannam@48: add = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: { cannam@48: // Get the "multiply" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::MULTIPLY); cannam@48: multiply = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: // Build the request to evaluate 4 * 6 cannam@48: auto request = calculator.evaluateRequest(); cannam@48: cannam@48: auto multiplyCall = request.getExpression().initCall(); cannam@48: multiplyCall.setFunction(multiply); cannam@48: auto multiplyParams = multiplyCall.initParams(2); cannam@48: multiplyParams[0].setLiteral(4); cannam@48: multiplyParams[1].setLiteral(6); cannam@48: cannam@48: auto multiplyResult = request.send().getValue(); cannam@48: cannam@48: // Use the result in two calls that add 3 and add 5. cannam@48: cannam@48: auto add3Request = calculator.evaluateRequest(); cannam@48: auto add3Call = add3Request.getExpression().initCall(); cannam@48: add3Call.setFunction(add); cannam@48: auto add3Params = add3Call.initParams(2); cannam@48: add3Params[0].setPreviousResult(multiplyResult); cannam@48: add3Params[1].setLiteral(3); cannam@48: auto add3Promise = add3Request.send().getValue().readRequest().send(); cannam@48: cannam@48: auto add5Request = calculator.evaluateRequest(); cannam@48: auto add5Call = add5Request.getExpression().initCall(); cannam@48: add5Call.setFunction(add); cannam@48: auto add5Params = add5Call.initParams(2); cannam@48: add5Params[0].setPreviousResult(multiplyResult); cannam@48: add5Params[1].setLiteral(5); cannam@48: auto add5Promise = add5Request.send().getValue().readRequest().send(); cannam@48: cannam@48: // Now wait for the results. cannam@48: KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27); cannam@48: KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29); cannam@48: cannam@48: std::cout << "PASS" << std::endl; cannam@48: } cannam@48: cannam@48: { cannam@48: // Our calculator interface supports defining functions. Here we use it cannam@48: // to define two functions and then make calls to them as follows: cannam@48: // cannam@48: // f(x, y) = x * 100 + y cannam@48: // g(x) = f(x, x + 1) * 2; cannam@48: // f(12, 34) cannam@48: // g(21) cannam@48: // cannam@48: // Once again, the whole thing takes only one network round trip. cannam@48: cannam@48: std::cout << "Defining functions... "; cannam@48: std::cout.flush(); cannam@48: cannam@48: Calculator::Function::Client add = nullptr; cannam@48: Calculator::Function::Client multiply = nullptr; cannam@48: Calculator::Function::Client f = nullptr; cannam@48: Calculator::Function::Client g = nullptr; cannam@48: cannam@48: { cannam@48: // Get the "add" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::ADD); cannam@48: add = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: { cannam@48: // Get the "multiply" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::MULTIPLY); cannam@48: multiply = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: { cannam@48: // Define f. cannam@48: auto request = calculator.defFunctionRequest(); cannam@48: request.setParamCount(2); cannam@48: cannam@48: { cannam@48: // Build the function body. cannam@48: auto addCall = request.getBody().initCall(); cannam@48: addCall.setFunction(add); cannam@48: auto addParams = addCall.initParams(2); cannam@48: addParams[1].setParameter(1); // y cannam@48: cannam@48: auto multiplyCall = addParams[0].initCall(); cannam@48: multiplyCall.setFunction(multiply); cannam@48: auto multiplyParams = multiplyCall.initParams(2); cannam@48: multiplyParams[0].setParameter(0); // x cannam@48: multiplyParams[1].setLiteral(100); cannam@48: } cannam@48: cannam@48: f = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: { cannam@48: // Define g. cannam@48: auto request = calculator.defFunctionRequest(); cannam@48: request.setParamCount(1); cannam@48: cannam@48: { cannam@48: // Build the function body. cannam@48: auto multiplyCall = request.getBody().initCall(); cannam@48: multiplyCall.setFunction(multiply); cannam@48: auto multiplyParams = multiplyCall.initParams(2); cannam@48: multiplyParams[1].setLiteral(2); cannam@48: cannam@48: auto fCall = multiplyParams[0].initCall(); cannam@48: fCall.setFunction(f); cannam@48: auto fParams = fCall.initParams(2); cannam@48: fParams[0].setParameter(0); cannam@48: cannam@48: auto addCall = fParams[1].initCall(); cannam@48: addCall.setFunction(add); cannam@48: auto addParams = addCall.initParams(2); cannam@48: addParams[0].setParameter(0); cannam@48: addParams[1].setLiteral(1); cannam@48: } cannam@48: cannam@48: g = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: // OK, we've defined all our functions. Now create our eval requests. cannam@48: cannam@48: // f(12, 34) cannam@48: auto fEvalRequest = calculator.evaluateRequest(); cannam@48: auto fCall = fEvalRequest.initExpression().initCall(); cannam@48: fCall.setFunction(f); cannam@48: auto fParams = fCall.initParams(2); cannam@48: fParams[0].setLiteral(12); cannam@48: fParams[1].setLiteral(34); cannam@48: auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send(); cannam@48: cannam@48: // g(21) cannam@48: auto gEvalRequest = calculator.evaluateRequest(); cannam@48: auto gCall = gEvalRequest.initExpression().initCall(); cannam@48: gCall.setFunction(g); cannam@48: gCall.initParams(1)[0].setLiteral(21); cannam@48: auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send(); cannam@48: cannam@48: // Wait for the results. cannam@48: KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234); cannam@48: KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244); cannam@48: cannam@48: std::cout << "PASS" << std::endl; cannam@48: } cannam@48: cannam@48: { cannam@48: // Make a request that will call back to a function defined locally. cannam@48: // cannam@48: // Specifically, we will compute 2^(4 + 5). However, exponent is not cannam@48: // defined by the Calculator server. So, we'll implement the Function cannam@48: // interface locally and pass it to the server for it to use when cannam@48: // evaluating the expression. cannam@48: // cannam@48: // This example requires two network round trips to complete, because the cannam@48: // server calls back to the client once before finishing. In this cannam@48: // particular case, this could potentially be optimized by using a tail cannam@48: // call on the server side -- see CallContext::tailCall(). However, to cannam@48: // keep the example simpler, we haven't implemented this optimization in cannam@48: // the sample server. cannam@48: cannam@48: std::cout << "Using a callback... "; cannam@48: std::cout.flush(); cannam@48: cannam@48: Calculator::Function::Client add = nullptr; cannam@48: cannam@48: { cannam@48: // Get the "add" function from the server. cannam@48: auto request = calculator.getOperatorRequest(); cannam@48: request.setOp(Calculator::Operator::ADD); cannam@48: add = request.send().getFunc(); cannam@48: } cannam@48: cannam@48: // Build the eval request for 2^(4+5). cannam@48: auto request = calculator.evaluateRequest(); cannam@48: cannam@48: auto powCall = request.getExpression().initCall(); cannam@48: powCall.setFunction(kj::heap()); cannam@48: auto powParams = powCall.initParams(2); cannam@48: powParams[0].setLiteral(2); cannam@48: cannam@48: auto addCall = powParams[1].initCall(); cannam@48: addCall.setFunction(add); cannam@48: auto addParams = addCall.initParams(2); cannam@48: addParams[0].setLiteral(4); cannam@48: addParams[1].setLiteral(5); cannam@48: cannam@48: // Send the request and wait. cannam@48: auto response = request.send().getValue().readRequest() cannam@48: .send().wait(waitScope); cannam@48: KJ_ASSERT(response.getValue() == 512); cannam@48: cannam@48: std::cout << "PASS" << std::endl; cannam@48: } cannam@48: cannam@48: return 0; cannam@48: }