Files
nf7/iface/common/future.hh

252 lines
6.2 KiB
C++

// No copyright
#pragma once
#include <cassert>
#include <exception>
#include <functional>
#include <memory>
#include <utility>
#include <variant>
#include <vector>
#include "iface/common/exception.hh"
namespace nf7 {
template <typename T>
class Future final {
public:
// !! Listener MUST NOT throw any exceptions !!
class Internal;
using Listener = std::function<void(const Internal&)>;
class Completer;
class Internal final {
public:
Internal() = default;
Internal(T&& v) noexcept : result_(std::move(v)) { }
explicit Internal(std::exception_ptr e) noexcept : result_(e) { }
Internal(const Internal& src) = default;
Internal(Internal&& src) = default;
Internal& operator=(const Internal& src) = default;
Internal& operator=(Internal&& src) = default;
void Complete(T&& v) noexcept {
assert(yet());
result_ = std::move(v);
Finalize();
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
assert(yet());
assert(nullptr != e);
result_ = e;
Finalize();
}
void Listen(Listener&& listener) {
assert(!calling_listener_ &&
"do not add listener while listener callback");
if (yet()) {
try {
listeners_.push_back(std::move(listener));
} catch (const std::exception&) {
throw Exception("memory shortage");
}
} else {
calling_listener_ = true;
listener(*this);
calling_listener_ = false;
}
}
void Ref() noexcept {
++refcnt_;
}
void Unref() noexcept {
if (--refcnt_ == 0 && yet()) {
Throw(std::make_exception_ptr(Exception {"forgotten"}));
}
}
bool yet() const noexcept { return std::holds_alternative<Yet>(result_); }
bool done() const noexcept { return std::holds_alternative<T>(result_); }
std::exception_ptr error() const noexcept {
return std::holds_alternative<std::exception_ptr>(result_)
? std::get<std::exception_ptr>(result_)
: std::exception_ptr {};
}
const T& value() const {
assert(!yet());
if (auto err = error()) {
std::rethrow_exception(err);
}
assert(done());
return std::get<T>(result_);
}
private:
void Finalize() noexcept {
calling_listener_ = true;
for (auto& listener : listeners_) {
listener(*this);
}
listeners_.clear();
calling_listener_ = false;
}
struct Yet {};
std::variant<Yet, T, std::exception_ptr> result_;
std::vector<Listener> listeners_;
uint64_t refcnt_ = 0;
bool calling_listener_ = false;
};
Future() = delete;
explicit Future(T&& v) : internal_(Internal(std::move(v))) {
}
explicit Future(std::exception_ptr e) : internal_(Internal(e)) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
Future<T>& Listen(Listener&& listener) {
internal().Listen(std::move(listener));
return *this;
}
Future<T>& Then(std::function<void(const T&)>&& f) {
Listen([f = std::move(f)](auto& fu) noexcept {
if (fu.done()) {
f(fu.value());
}
});
return *this;
}
Future<T>& Catch(std::function<void(const Exception&)>&& f) {
Listen([f = std::move(f)](auto& fu) noexcept {
try {
fu.value();
} catch (const Exception& e) {
f(e);
}
});
return *this;
}
template <typename R>
Future<R> ThenAnd(std::function<R(const T&)>&& f) {
typename Future<R>::Completer comp;
auto fu = comp.future();
Listen([f = std::move(f), comp = std::move(comp)]
(auto& fu) mutable noexcept {
try {
comp.Complete(f(fu.value()));
} catch (...) {
comp.Throw();
}
});
return fu;
}
bool yet() const noexcept { return internal().yet(); }
bool done() const noexcept { return internal().done(); }
std::exception_ptr error() const noexcept { return internal().error(); }
const T& value() const { return internal().value(); }
private:
Future(std::shared_ptr<Internal>&& in) noexcept
: internal_(std::move(in)) { }
Future(const std::shared_ptr<Internal>& in) noexcept
: internal_(std::move(in)) { }
Internal& internal() noexcept {
return std::holds_alternative<Internal>(internal_)
? std::get<Internal>(internal_)
: *std::get<std::shared_ptr<Internal>>(internal_);
}
const Internal& internal() const noexcept {
return std::holds_alternative<Internal>(internal_)
? std::get<Internal>(internal_)
: *std::get<std::shared_ptr<Internal>>(internal_);
}
std::variant<Internal, std::shared_ptr<Internal>> internal_;
};
template <typename T>
class Future<T>::Completer final {
public:
Completer()
try : internal_(std::make_shared<Internal>()) {
internal_->Ref();
} catch (const std::exception&) {
throw Exception("memory shortage");
}
~Completer() noexcept {
if (nullptr != internal_) {
internal_->Unref();
}
}
Completer(const Completer& src) noexcept : internal_(src.internal_) {
if (nullptr != internal_) {
internal_->Ref();
}
}
Completer(Completer&&) = default;
Completer& operator=(const Completer& src) noexcept {
if (this != &src) {
if (nullptr != internal_) {
internal_->Unref();
}
internal_ = src.internal_;
if (nullptr != internal_) {
internal_->Ref();
}
}
return *this;
}
Completer& operator=(Completer&& src) noexcept {
if (this != &src) {
if (nullptr != internal_) {
internal_->Unref();
}
internal_ = std::move(src.internal_);
}
return *this;
}
void Complete(T&& v) noexcept {
assert(nullptr != internal_);
internal_->Complete(std::move(v));
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
assert(nullptr != internal_);
internal_->Throw(e);
}
void Run(std::function<T()>& f) noexcept {
assert(nullptr != internal_);
try {
Complete(f());
} catch (...) {
Throw();
}
}
Future<T> future() const noexcept {
assert(nullptr != internal_);
return {internal_};
}
private:
std::shared_ptr<Internal> internal_;
};
} // namespace nf7