Compare commits
4 Commits
84bec46a1c
...
6cda20c93c
Author | SHA1 | Date | |
---|---|---|---|
6cda20c93c | |||
52abaeea1b | |||
70c41b3c3b | |||
b93fcb6ac7 |
@ -9,5 +9,25 @@ target_sources(nf7_iface
|
|||||||
PRIVATE
|
PRIVATE
|
||||||
version.cc
|
version.cc
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
common/container.hh
|
||||||
|
common/future.hh
|
||||||
|
common/observer.hh
|
||||||
version.hh
|
version.hh
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(nf7_iface_test)
|
||||||
|
target_sources(nf7_iface_test
|
||||||
|
PRIVATE
|
||||||
|
common/container_test.cc
|
||||||
|
common/future_test.cc
|
||||||
|
common/observer_test.hh
|
||||||
|
common/observer_test.cc
|
||||||
|
)
|
||||||
|
target_link_libraries(nf7_iface_test
|
||||||
|
PRIVATE
|
||||||
|
nf7_iface
|
||||||
|
GTest::gmock_main
|
||||||
|
GTest::gtest_main
|
||||||
|
)
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(nf7_iface_test)
|
||||||
|
123
iface/common/container.hh
Normal file
123
iface/common/container.hh
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <typeindex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "iface/common/exception.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
template <typename I>
|
||||||
|
class Container final {
|
||||||
|
public:
|
||||||
|
using Factory = std::function<std::shared_ptr<I>(Container<I>&)>;
|
||||||
|
|
||||||
|
template <typename I2, typename T>
|
||||||
|
static std::pair<std::type_index, Factory> MakePair() noexcept {
|
||||||
|
static_assert(std::is_base_of_v<I, I2>,
|
||||||
|
"registerable interface must be based on "
|
||||||
|
"container common interface");
|
||||||
|
static_assert(std::is_base_of_v<I2, T>,
|
||||||
|
"registerable concrete type must be based on "
|
||||||
|
"an interface to be being registered");
|
||||||
|
static_assert(std::is_constructible_v<T, Container<I>&>,
|
||||||
|
"registerable concrete type must be "
|
||||||
|
"constructible with container");
|
||||||
|
return std::pair<std::type_index, Factory>{
|
||||||
|
typeid(I2), [](auto& x) { return std::make_shared<T>(x); }};
|
||||||
|
}
|
||||||
|
static std::shared_ptr<Container<I>> Make(
|
||||||
|
std::unordered_map<std::type_index, Factory>&& factories) {
|
||||||
|
try {
|
||||||
|
return std::make_shared<Container<I>>(std::move(factories));
|
||||||
|
} catch (const std::bad_alloc&) {
|
||||||
|
throw Exception {"memory shortage"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container() = delete;
|
||||||
|
explicit Container(std::unordered_map<std::type_index, Factory>&& factories) noexcept
|
||||||
|
: factories_(std::move(factories)) { }
|
||||||
|
|
||||||
|
Container(const Container&) = delete;
|
||||||
|
Container(Container&&) = delete;
|
||||||
|
Container& operator=(const Container&) = delete;
|
||||||
|
Container& operator=(Container&&) = delete;
|
||||||
|
|
||||||
|
template <typename I2>
|
||||||
|
void Get(std::shared_ptr<I2>& out) {
|
||||||
|
out = Get<I2>();
|
||||||
|
}
|
||||||
|
template <typename I2>
|
||||||
|
std::shared_ptr<I2> Get() {
|
||||||
|
auto ptr = GetOr<I2>();
|
||||||
|
if (nullptr == ptr) {
|
||||||
|
throw Exception {"missing dependency"};
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
template <typename I2>
|
||||||
|
void GetOr(std::shared_ptr<I2>& out,
|
||||||
|
const std::shared_ptr<I2>& def = nullptr) {
|
||||||
|
out = GetOr<I2>(def);
|
||||||
|
}
|
||||||
|
template <typename I2>
|
||||||
|
std::shared_ptr<I2> GetOr(const std::shared_ptr<I2>& def = nullptr) {
|
||||||
|
auto ptr = GetOr(typeid(I2), def);
|
||||||
|
auto casted_ptr = std::dynamic_pointer_cast<I2>(ptr);
|
||||||
|
assert(nullptr == ptr || nullptr != casted_ptr);
|
||||||
|
return casted_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I> GetOr(
|
||||||
|
std::type_index idx, const std::shared_ptr<I>& def = nullptr) {
|
||||||
|
const auto obj_itr = objs_.find(idx);
|
||||||
|
if (objs_.end() != obj_itr) {
|
||||||
|
return obj_itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto factory_itr = factories_.find(idx);
|
||||||
|
if (factories_.end() != factory_itr) {
|
||||||
|
assert(nest_ < 1000 &&
|
||||||
|
"circular dependency detected in container factory");
|
||||||
|
++nest_;
|
||||||
|
auto obj = factory_itr->second(*this);
|
||||||
|
--nest_;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto [itr, added] = objs_.insert({idx, std::move(obj)});
|
||||||
|
(void) itr;
|
||||||
|
(void) added;
|
||||||
|
assert(added);
|
||||||
|
return itr->second;
|
||||||
|
} catch (...) {
|
||||||
|
throw Exception {"memory shortage"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool installed() const noexcept {
|
||||||
|
return installed(typeid(T));
|
||||||
|
}
|
||||||
|
bool installed(std::type_index idx) const noexcept {
|
||||||
|
return factories_.contains(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::type_index, Factory> factories_;
|
||||||
|
std::unordered_map<std::type_index, std::shared_ptr<I>> objs_;
|
||||||
|
|
||||||
|
uint32_t nest_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
74
iface/common/container_test.cc
Normal file
74
iface/common/container_test.cc
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// No copyright
|
||||||
|
#include "iface/common/container.hh"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Object {
|
||||||
|
public:
|
||||||
|
virtual ~Object() = default;
|
||||||
|
};
|
||||||
|
using SUT = nf7::Container<Object>;
|
||||||
|
|
||||||
|
class IA : public Object { };
|
||||||
|
class IB : public Object { };
|
||||||
|
|
||||||
|
class A : public IA {
|
||||||
|
public:
|
||||||
|
explicit A(SUT&) noexcept { }
|
||||||
|
};
|
||||||
|
class B : public IB {
|
||||||
|
public:
|
||||||
|
explicit B(SUT& sut) { sut.Get(a_); }
|
||||||
|
private:
|
||||||
|
std::shared_ptr<IA> a_;
|
||||||
|
};
|
||||||
|
class BRecursive : public IB {
|
||||||
|
public:
|
||||||
|
explicit BRecursive(SUT& sut) { sut.Get(b_); }
|
||||||
|
private:
|
||||||
|
std::shared_ptr<IB> b_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(Container, FetchIsolated) {
|
||||||
|
SUT sut {{
|
||||||
|
SUT::MakePair<IA, A>(),
|
||||||
|
}};
|
||||||
|
auto ptr = sut.Get<IA>();
|
||||||
|
EXPECT_TRUE(std::dynamic_pointer_cast<A>(ptr));
|
||||||
|
}
|
||||||
|
TEST(Container, FetchDepending) {
|
||||||
|
SUT sut {{
|
||||||
|
SUT::MakePair<IA, A>(),
|
||||||
|
SUT::MakePair<IB, B>(),
|
||||||
|
}};
|
||||||
|
auto ptr = sut.Get<IB>();
|
||||||
|
EXPECT_TRUE(std::dynamic_pointer_cast<B>(ptr));
|
||||||
|
}
|
||||||
|
TEST(Container, FetchUnknown) {
|
||||||
|
SUT sut {{}};
|
||||||
|
EXPECT_THROW(sut.Get<IA>(), nf7::Exception);
|
||||||
|
}
|
||||||
|
TEST(Container, FetchUnknownDepending) {
|
||||||
|
SUT sut {{
|
||||||
|
SUT::MakePair<IB, B>(),
|
||||||
|
}};
|
||||||
|
EXPECT_THROW(sut.Get<IB>(), nf7::Exception);
|
||||||
|
}
|
||||||
|
TEST(Container, CheckInstalled) {
|
||||||
|
SUT sut {{
|
||||||
|
SUT::MakePair<IA, A>(),
|
||||||
|
}};
|
||||||
|
EXPECT_TRUE(sut.installed<IA>());
|
||||||
|
EXPECT_FALSE(sut.installed<IB>());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
TEST(Container, DeathByFetchRecursive) {
|
||||||
|
SUT sut {{
|
||||||
|
SUT::MakePair<IB, BRecursive>(),
|
||||||
|
}};
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(sut.Get<IB>(), "");
|
||||||
|
}
|
||||||
|
#endif
|
@ -4,7 +4,7 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <source_location>
|
#include <source_location>
|
||||||
|
|
||||||
namespace nf7::iface {
|
namespace nf7 {
|
||||||
|
|
||||||
class Exception : public std::exception, std::nested_exception {
|
class Exception : public std::exception, std::nested_exception {
|
||||||
public:
|
public:
|
||||||
@ -22,4 +22,4 @@ class Exception : public std::exception, std::nested_exception {
|
|||||||
std::source_location location_;
|
std::source_location location_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nf7::iface
|
} // namespace nf7
|
173
iface/common/future.hh
Normal file
173
iface/common/future.hh
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#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 !!
|
||||||
|
using Listener = std::function<void()>;
|
||||||
|
|
||||||
|
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();
|
||||||
|
calling_listener_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 noexcept {
|
||||||
|
assert(done());
|
||||||
|
return std::get<T>(result_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Finalize() noexcept {
|
||||||
|
calling_listener_ = true;
|
||||||
|
for (auto& listener : listeners_) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
listeners_.clear();
|
||||||
|
calling_listener_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Yet {};
|
||||||
|
std::variant<Yet, T, std::exception_ptr> result_;
|
||||||
|
|
||||||
|
std::vector<Listener> listeners_;
|
||||||
|
|
||||||
|
bool calling_listener_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Future() = delete;
|
||||||
|
Future(T&& v) : internal_(Internal(std::move(v))) {
|
||||||
|
}
|
||||||
|
Future(std::exception_ptr e) : internal_(Internal(e)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Future(const Future&) = default;
|
||||||
|
Future(Future&&) = default;
|
||||||
|
Future& operator=(const Future&) = default;
|
||||||
|
Future& operator=(Future&&) = default;
|
||||||
|
|
||||||
|
void Listen(Listener&& listener) {
|
||||||
|
internal().Listen(std::move(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
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() { 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>()) {
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
throw Exception("memory shortage");
|
||||||
|
}
|
||||||
|
~Completer() noexcept {
|
||||||
|
Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Completer(const Completer& src) = delete;
|
||||||
|
Completer(Completer&&) = default;
|
||||||
|
Completer& operator=(const Completer&) = delete;
|
||||||
|
Completer& operator=(Completer&& src) noexcept {
|
||||||
|
if (this != &src) {
|
||||||
|
Finalize();
|
||||||
|
internal_ = std::move(src.internal_);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Complete(T&& v) noexcept {
|
||||||
|
internal_->Complete(std::move(v));
|
||||||
|
}
|
||||||
|
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
|
||||||
|
internal_->Throw(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> future() const noexcept { return {internal_}; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Finalize() noexcept {
|
||||||
|
if (internal_->yet()) {
|
||||||
|
internal_->Throw(std::make_exception_ptr(Exception {"forgotten"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Internal> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
160
iface/common/future_test.cc
Normal file
160
iface/common/future_test.cc
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// No copyright
|
||||||
|
#include "iface/common/future.hh"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "iface/common/exception.hh"
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Future, ImmediateValue) {
|
||||||
|
nf7::Future<int32_t> sut {int32_t {777}};
|
||||||
|
|
||||||
|
EXPECT_FALSE(sut.yet());
|
||||||
|
EXPECT_TRUE(sut.done());
|
||||||
|
EXPECT_FALSE(sut.error());
|
||||||
|
EXPECT_EQ(sut.value(), int32_t {777});
|
||||||
|
}
|
||||||
|
TEST(Future, ImmediateError) {
|
||||||
|
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception("hello"))};
|
||||||
|
|
||||||
|
EXPECT_FALSE(sut.yet());
|
||||||
|
EXPECT_FALSE(sut.done());
|
||||||
|
EXPECT_TRUE(sut.error());
|
||||||
|
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Future, LazyComplete) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
completer.Complete(int32_t {777});
|
||||||
|
|
||||||
|
EXPECT_FALSE(sut.yet());
|
||||||
|
EXPECT_TRUE(sut.done());
|
||||||
|
EXPECT_FALSE(sut.error());
|
||||||
|
EXPECT_EQ(sut.value(), int32_t {777});
|
||||||
|
}
|
||||||
|
TEST(Future, LazyThrow) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(sut.yet());
|
||||||
|
EXPECT_FALSE(sut.done());
|
||||||
|
EXPECT_TRUE(sut.error());
|
||||||
|
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||||
|
}
|
||||||
|
TEST(Future, LazyIncomplete) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
EXPECT_TRUE(sut.yet());
|
||||||
|
EXPECT_FALSE(sut.done());
|
||||||
|
EXPECT_FALSE(sut.error());
|
||||||
|
}
|
||||||
|
TEST(Future, LazyForgotten) {
|
||||||
|
std::optional<nf7::Future<int32_t>> sut;
|
||||||
|
nf7::Future<int32_t> sut2 {*sut};
|
||||||
|
{
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
sut.emplace(completer.future());
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(sut->yet());
|
||||||
|
EXPECT_FALSE(sut->done());
|
||||||
|
EXPECT_TRUE(sut->error());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Future, ListenImmediateValue) {
|
||||||
|
nf7::Future<int32_t> sut {int32_t {777}};
|
||||||
|
|
||||||
|
auto called = int32_t {0};
|
||||||
|
sut.Listen([&]() {
|
||||||
|
++called;
|
||||||
|
EXPECT_EQ(sut.value(), int32_t {777});
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_EQ(called, 1);
|
||||||
|
}
|
||||||
|
TEST(Future, ListenImmediateError) {
|
||||||
|
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception {"hello"})};
|
||||||
|
|
||||||
|
auto called = int32_t {0};
|
||||||
|
sut.Listen([&]() {
|
||||||
|
++called;
|
||||||
|
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_EQ(called, 1);
|
||||||
|
}
|
||||||
|
TEST(Future, ListenLazyComplete) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
auto called = int32_t {0};
|
||||||
|
sut.Listen([&]() {
|
||||||
|
++called;
|
||||||
|
EXPECT_EQ(sut.value(), int32_t {777});
|
||||||
|
});
|
||||||
|
completer.Complete(int32_t {777});
|
||||||
|
|
||||||
|
EXPECT_EQ(called, 1);
|
||||||
|
}
|
||||||
|
TEST(Future, ListenLazyThrow) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
auto called = int32_t {0};
|
||||||
|
sut.Listen([&]() {
|
||||||
|
++called;
|
||||||
|
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||||
|
});
|
||||||
|
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
|
||||||
|
|
||||||
|
EXPECT_EQ(called, 1);
|
||||||
|
}
|
||||||
|
TEST(Future, ListenLazyIncomplete) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
auto called = int32_t {0};
|
||||||
|
sut.Listen([&]() { ++called; });
|
||||||
|
|
||||||
|
EXPECT_EQ(called, 0);
|
||||||
|
}
|
||||||
|
TEST(Future, ListenLazyForgotten) {
|
||||||
|
auto called = int32_t {0};
|
||||||
|
{
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
sut.Listen([&, sut]() {
|
||||||
|
++called;
|
||||||
|
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
EXPECT_EQ(called, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
TEST(Future, DeathByListenInCallback) {
|
||||||
|
nf7::Future<int32_t> sut {int32_t{777}};
|
||||||
|
|
||||||
|
sut.Listen([&]() {
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
TEST(Future, DeathByListenInLazyCallback) {
|
||||||
|
nf7::Future<int32_t>::Completer completer;
|
||||||
|
nf7::Future<int32_t> sut = completer.future();
|
||||||
|
|
||||||
|
sut.Listen([&]() {
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
|
||||||
|
});
|
||||||
|
|
||||||
|
completer.Complete(int32_t{777});
|
||||||
|
}
|
||||||
|
#endif
|
99
iface/common/observer.hh
Normal file
99
iface/common/observer.hh
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// No copyright
|
||||||
|
// ---- this header provides a way to implement Observer pattern easily
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
// T is notified to Observer<T> by Observer<T>::Target.
|
||||||
|
// All observers should be based on this.
|
||||||
|
template <typename T>
|
||||||
|
class Observer {
|
||||||
|
public:
|
||||||
|
class Target;
|
||||||
|
|
||||||
|
inline explicit Observer(Target&);
|
||||||
|
inline virtual ~Observer() noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void Notify(const T&) {}
|
||||||
|
virtual void NotifyWithMove(T&& v) { Notify(v); }
|
||||||
|
virtual void NotifyDestruction(const T* = nullptr) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Target& target_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// All objects which can be observed by Observer<T> must be based on this.
|
||||||
|
template <typename T>
|
||||||
|
class Observer<T>::Target {
|
||||||
|
public:
|
||||||
|
friend class Observer<T>;
|
||||||
|
|
||||||
|
Target() = default;
|
||||||
|
virtual ~Target() noexcept {
|
||||||
|
for (auto obs : obs_) {
|
||||||
|
obs->NotifyDestruction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Target(const Target&) = delete;
|
||||||
|
Target(Target&&) = delete;
|
||||||
|
Target& operator=(const Target&) = delete;
|
||||||
|
Target& operator=(Target&&) = delete;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void Notify(T&& v) noexcept {
|
||||||
|
assert(!calling_observer_ && "do not call Notify from observer callback");
|
||||||
|
|
||||||
|
if (1 == obs_.size()) {
|
||||||
|
auto first = *obs_.begin();
|
||||||
|
calling_observer_ = true;
|
||||||
|
first->NotifyWithMove(std::move(v));
|
||||||
|
calling_observer_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calling_observer_ = true;
|
||||||
|
for (auto obs : obs_) {
|
||||||
|
obs->Notify(v);
|
||||||
|
}
|
||||||
|
calling_observer_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool observed(const T* = nullptr) const noexcept { return !obs_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Register(Observer<T>& obs) {
|
||||||
|
assert(!calling_observer_ &&
|
||||||
|
"do not register any observer from observer callback");
|
||||||
|
obs_.insert(&obs);
|
||||||
|
}
|
||||||
|
void Unregister(const Observer<T>& obs) noexcept {
|
||||||
|
assert(!calling_observer_ &&
|
||||||
|
"do not unregister any observer from observer callback");
|
||||||
|
obs_.erase(&const_cast<Observer<T>&>(obs));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_set<Observer<T>*> obs_;
|
||||||
|
|
||||||
|
bool calling_observer_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Observer<T>::Observer(Target& target) : target_(target) {
|
||||||
|
target_.Register(*this);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
Observer<T>::~Observer() noexcept {
|
||||||
|
target_.Unregister(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7
|
74
iface/common/observer_test.cc
Normal file
74
iface/common/observer_test.cc
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// No copyright
|
||||||
|
#include "iface/common/observer.hh"
|
||||||
|
#include "iface/common/observer_test.hh"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
TEST(Observer, NotifyWithMove) {
|
||||||
|
nf7::test::ObserverTargetMock<int32_t> target;
|
||||||
|
nf7::test::ObserverMock<int32_t> sut {target};
|
||||||
|
|
||||||
|
EXPECT_CALL(sut, NotifyWithMove(111)).Times(1);
|
||||||
|
|
||||||
|
target.Notify(int32_t {111});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Observer, NotifyWithRef) {
|
||||||
|
nf7::test::ObserverTargetMock<int32_t> target;
|
||||||
|
nf7::test::ObserverMock<int32_t> sut1 {target};
|
||||||
|
nf7::test::ObserverMock<int32_t> sut2 {target};
|
||||||
|
|
||||||
|
EXPECT_CALL(sut1, Notify(111)).Times(1);
|
||||||
|
EXPECT_CALL(sut2, Notify(111)).Times(1);
|
||||||
|
|
||||||
|
target.Notify(int32_t {111});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Observer, NotifyDestruction) {
|
||||||
|
std::optional<nf7::test::ObserverTargetMock<int32_t>> target;
|
||||||
|
target.emplace();
|
||||||
|
nf7::test::ObserverMock<int32_t> sut {*target};
|
||||||
|
|
||||||
|
EXPECT_CALL(sut, NotifyDestruction(testing::_)).Times(1);
|
||||||
|
|
||||||
|
target = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
TEST(Observer, DeathByRegisterInCallback) {
|
||||||
|
nf7::test::ObserverTargetMock<int32_t> target;
|
||||||
|
nf7::test::ObserverMock<int32_t> sut {target};
|
||||||
|
|
||||||
|
ON_CALL(sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
|
||||||
|
nf7::test::ObserverMock<int32_t> sut2 {target};
|
||||||
|
(void) sut2;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
|
||||||
|
}
|
||||||
|
TEST(Observer, DeathByUnregisterInCallback) {
|
||||||
|
nf7::test::ObserverTargetMock<int32_t> target;
|
||||||
|
std::optional<nf7::test::ObserverMock<int32_t>> sut;
|
||||||
|
sut.emplace(target);
|
||||||
|
|
||||||
|
ON_CALL(*sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
|
||||||
|
sut = std::nullopt;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
|
||||||
|
}
|
||||||
|
TEST(Observer, DeathByNotifyInCallback) {
|
||||||
|
nf7::test::ObserverTargetMock<int32_t> target;
|
||||||
|
nf7::test::ObserverMock<int32_t> sut {{target}};
|
||||||
|
|
||||||
|
ON_CALL(sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
|
||||||
|
target.Notify(111);
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
|
||||||
|
}
|
||||||
|
#endif
|
29
iface/common/observer_test.hh
Normal file
29
iface/common/observer_test.hh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "iface/common/observer.hh"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::test {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class ObserverMock : public Observer<T> {
|
||||||
|
public:
|
||||||
|
explicit ObserverMock(Observer<T>::Target& target) : Observer<T>(target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD1(Notify, void(const T&));
|
||||||
|
MOCK_METHOD1(NotifyWithMove, void(T&&));
|
||||||
|
MOCK_METHOD1(NotifyDestruction, void(const T*));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class ObserverTargetMock : public Observer<T>::Target {
|
||||||
|
public:
|
||||||
|
using Observer<T>::Target::Target;
|
||||||
|
using Observer<T>::Target::Notify;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7::test
|
163
iface/future.hh
163
iface/future.hh
@ -1,163 +0,0 @@
|
|||||||
// No copyright
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "iface/exception.hh"
|
|
||||||
|
|
||||||
namespace nf7 {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class InternalFuture final :
|
|
||||||
public std::enable_shared_from_this<InternalFuture<T>> {
|
|
||||||
public:
|
|
||||||
using Listener =
|
|
||||||
std::function<void(std::shared_ptr<InternalFuture<T>>&&) noexcept>;
|
|
||||||
|
|
||||||
InternalFuture() = default;
|
|
||||||
InternalFuture(T&& v) noexcept : result_(std::move(v)) { }
|
|
||||||
explicitInternalFuture(std::exception_ptr e) noexcept : result_(e) { }
|
|
||||||
|
|
||||||
void Complete(T&& v) noexcept {
|
|
||||||
assert(yet());
|
|
||||||
result_ = std::move(v);
|
|
||||||
Finalize();
|
|
||||||
}
|
|
||||||
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
|
|
||||||
assert(yet());
|
|
||||||
result_ = e;
|
|
||||||
Finalize();
|
|
||||||
}
|
|
||||||
void Listen(Listener&& listener) {
|
|
||||||
if (yet()) {
|
|
||||||
try {
|
|
||||||
listeners_.push_back(std::move(listener));
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
throw Exception("memory shortage");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
listener(shared_from_this());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Ref() noexcept {
|
|
||||||
++refcnt_;
|
|
||||||
}
|
|
||||||
void Unref() noexcept {
|
|
||||||
--refcnt_;
|
|
||||||
if (0 == refcnt_ && yet()) {
|
|
||||||
Throw(std::make_exception_ptr(Exception("future is forgotten")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool yet() const noexcept {
|
|
||||||
return std::nullopt == result_;
|
|
||||||
}
|
|
||||||
bool done() const noexcept {
|
|
||||||
return !yet() && std::holds_alternative<T>(*result_);
|
|
||||||
}
|
|
||||||
bool error() const noexcept {
|
|
||||||
return !yet() && std::holds_alternative<std::exception_ptr>(*result_);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Finalize() noexcept {
|
|
||||||
for (const auto& listener : listeners_) {
|
|
||||||
listener(shared_from_this());
|
|
||||||
}
|
|
||||||
listeners_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::variant<T, std::exception_ptr>> result_;
|
|
||||||
|
|
||||||
std::vector<Listener> listeners_;
|
|
||||||
|
|
||||||
uint32_t refcnt_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Future final {
|
|
||||||
public:
|
|
||||||
class Completer;
|
|
||||||
|
|
||||||
Future() = delete;
|
|
||||||
Future(std::shared_ptr<InternalFuture>&& in) noexcept
|
|
||||||
: internal_(std::move(in));
|
|
||||||
|
|
||||||
void Listen(std::function<void(const Future<T>&)>&& listener) {
|
|
||||||
internal().Listen(std::move(listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool yet() const noexcept { return internal().yet(); }
|
|
||||||
bool done() const noexcept { return internal().done(); }
|
|
||||||
bool error() const noexcept { return internal().error(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
InternalFuture& internal() noexcept {
|
|
||||||
struct Visitor {
|
|
||||||
InternalFuture& operator(InernalFuture& i) noexcept {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
InternalFuture& operator(std::shared_ptr<InternalFuture>& i) noexcept {
|
|
||||||
return *i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return std::visit(InternalGetter(), internal_);
|
|
||||||
}
|
|
||||||
const InternalFuture& internal() const noexcept {
|
|
||||||
struct Visitor {
|
|
||||||
const InternalFuture& operator(const InernalFuture& i) noexcept {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
const InternalFuture& operator(
|
|
||||||
const std::shared_ptr<InternalFuture>& i) noexcept {
|
|
||||||
return *i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return std::visit(InternalGetter(), internal_);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::variant<InternalFuture, std::shared_ptr<InternalFuture>> internal_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Future<T>::Completer final {
|
|
||||||
public:
|
|
||||||
Completer() : Completer(std::make_shared<InternalFuture>())
|
|
||||||
try {
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
throw Exception("memory shortage");
|
|
||||||
}
|
|
||||||
explicit Completer(std::shared_ptr<InternalFuture>&& internal) noexcept
|
|
||||||
: internal_(std::move(internal)) {
|
|
||||||
internal_->Ref();
|
|
||||||
}
|
|
||||||
~Completer() noexcept {
|
|
||||||
if (nullptr != internal_) {
|
|
||||||
internal_->Unref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer(const Completer&) = default;
|
|
||||||
Completer(Completer&&) = default;
|
|
||||||
Completer& operator=(const Completer&) = default;
|
|
||||||
Completer& operator=(Completer&&) = default;
|
|
||||||
|
|
||||||
void Complete(T&& v) noexcept {
|
|
||||||
internal_->Complete(std::move(v));
|
|
||||||
}
|
|
||||||
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
|
|
||||||
internal_->Throw(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<InternalFuture> internal_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace nf7
|
|
Loading…
x
Reference in New Issue
Block a user