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