#pragma once #include "StarEither.hpp" #include "StarString.hpp" namespace Star { STAR_EXCEPTION(RpcPromiseException, StarException); // The other side of an RpcPromise, can be used to either fulfill or fail a // paired promise. Call either fulfill or fail function exactly once, any // further invocations will result in an exception. template class RpcPromiseKeeper { public: void fulfill(Result result); void fail(Error error); private: template friend class RpcPromise; function m_fulfill; function m_fail; }; // A generic promise for the result of a remote procedure call. It has // reference semantics and is implicitly shared, but is not thread safe. template class RpcPromise { public: static pair> createPair(); static RpcPromise createFulfilled(Result result); static RpcPromise createFailed(Error error); // Has the respoonse either failed or succeeded? bool finished() const; // Has the response finished with success? bool succeeded() const; // Has the response finished with failure? bool failed() const; // Returns the result of the rpc call on success, nothing on failure or when // not yet finished. Maybe const& result() const; // Returns the error of a failed rpc call. Returns nothing if the call is // successful or not yet finished. Maybe const& error() const; // Wrap this RpcPromise into another promise which returns instead the result // of this function when fulfilled template decltype(auto) wrap(Function function); private: template friend class RpcPromise; struct Value { Maybe result; Maybe error; }; RpcPromise() = default; function m_getValue; }; template void RpcPromiseKeeper::fulfill(Result result) { m_fulfill(std::move(result)); } template void RpcPromiseKeeper::fail(Error error) { m_fail(std::move(error)); } template pair, RpcPromiseKeeper> RpcPromise::createPair() { auto valuePtr = std::make_shared(); RpcPromise promise; promise.m_getValue = [valuePtr]() { return valuePtr.get(); }; RpcPromiseKeeper keeper; keeper.m_fulfill = [valuePtr](Result result) { if (valuePtr->result || valuePtr->error) throw RpcPromiseException("fulfill called on already finished RpcPromise"); valuePtr->result = std::move(result); }; keeper.m_fail = [valuePtr](Error error) { if (valuePtr->result || valuePtr->error) throw RpcPromiseException("fail called on already finished RpcPromise"); valuePtr->error = std::move(error); }; return {std::move(promise), std::move(keeper)}; } template RpcPromise RpcPromise::createFulfilled(Result result) { auto valuePtr = std::make_shared(); valuePtr->result = std::move(result); RpcPromise promise; promise.m_getValue = [valuePtr]() { return valuePtr.get(); }; return promise; } template RpcPromise RpcPromise::createFailed(Error error) { auto valuePtr = std::make_shared(); valuePtr->error = std::move(error); RpcPromise promise; promise.m_getValue = [valuePtr]() { return valuePtr.get(); }; return promise; } template bool RpcPromise::finished() const { auto val = m_getValue(); return val->result || val->error; } template bool RpcPromise::succeeded() const { return m_getValue()->result.isValid(); } template bool RpcPromise::failed() const { return m_getValue()->error.isValid(); } template Maybe const& RpcPromise::result() const { return m_getValue()->result; } template Maybe const& RpcPromise::error() const { return m_getValue()->error; } template template decltype(auto) RpcPromise::wrap(Function function) { typedef RpcPromise()))>::type, Error> WrappedPromise; WrappedPromise wrappedPromise; wrappedPromise.m_getValue = [wrapper = std::move(function), valuePtr = std::make_shared(), otherGetValue = m_getValue]() { if (!valuePtr->result && !valuePtr->error) { auto otherValue = otherGetValue(); if (otherValue->result) valuePtr->result.set(wrapper(*otherValue->result)); else if (otherValue->error) valuePtr->error.set(*otherValue->error); } return valuePtr.get(); }; return wrappedPromise; } }