Commit a78f55e3 authored by Dominik Charousset's avatar Dominik Charousset

Consolidate and clean up steps and transformations

parent 45bfe5f2
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/comparable.hpp"
#include "caf/intrusive_cow_ptr.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include <vector>
namespace caf {
/// A copy-on-write vector implementation that wraps a `std::vector`.
template <class T>
class cow_vector {
public:
// -- member types -----------------------------------------------------------
using std_type = std::vector<T>;
using size_type = typename std_type::size_type;
using const_iterator = typename std_type::const_iterator;
using const_reverse_iterator = typename std_type::const_reverse_iterator;
// -- constants --------------------------------------------------------------
static inline const size_type npos = std_type::npos;
// -- constructors, destructors, and assignment operators --------------------
cow_vector() {
impl_ = make_counted<impl>();
}
explicit cow_vector(std_type std) {
impl_ = make_counted<impl>(std::move(std));
}
cow_vector(cow_vector&&) noexcept = default;
cow_vector(const cow_vector&) noexcept = default;
cow_vector& operator=(cow_vector&&) noexcept = default;
cow_vector& operator=(const cow_vector&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Returns a mutable reference to the managed vector. Copies the vector if
/// more than one reference to it exists to make sure the reference count is
/// exactly 1 when returning from this function.
std_type& unshared() {
return impl_.unshared().std;
}
/// Returns the managed STD container.
const std_type& std() const noexcept {
return impl_->std;
}
/// Returns whether the reference count of the managed object is 1.
[[nodiscard]] bool unique() const noexcept {
return impl_->unique();
}
[[nodiscard]] bool empty() const noexcept {
return impl_->std.empty();
}
size_type size() const noexcept {
return impl_->std.size();
}
size_type max_size() const noexcept {
return impl_->std.max_size();
}
// -- element access ---------------------------------------------------------
T at(size_type pos) const {
return impl_->std.at(pos);
}
T operator[](size_type pos) const {
return impl_->std[pos];
}
T front() const {
return impl_->std.front();
}
T back() const {
return impl_->std.back();
}
const T* data() const noexcept {
return impl_->std.data();
}
// -- iterator access --------------------------------------------------------
const_iterator begin() const noexcept {
return impl_->std.begin();
}
const_iterator cbegin() const noexcept {
return impl_->std.begin();
}
const_reverse_iterator rbegin() const noexcept {
return impl_->std.rbegin();
}
const_reverse_iterator crbegin() const noexcept {
return impl_->std.rbegin();
}
const_iterator end() const noexcept {
return impl_->std.end();
}
const_iterator cend() const noexcept {
return impl_->std.end();
}
const_reverse_iterator rend() const noexcept {
return impl_->std.rend();
}
const_reverse_iterator crend() const noexcept {
return impl_->std.rend();
}
// -- friends ----------------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& f, cow_vector& x) {
if constexpr (Inspector::is_loading) {
return f.apply(x.unshared());
} else {
return f.apply(x.impl_->std);
}
}
private:
struct impl : ref_counted {
std_type std;
impl() = default;
explicit impl(std_type in) : std(std::move(in)) {
// nop
}
impl* copy() const {
return new impl{std};
}
};
intrusive_cow_ptr<impl> impl_;
};
// -- comparison ---------------------------------------------------------------
template <class T>
auto operator==(const cow_vector<T>& xs, const cow_vector<T>& ys)
-> decltype(xs.std() == ys.std()) {
return xs.std() == ys.std();
}
template <class T>
auto operator==(const cow_vector<T>& xs, const std::vector<T>& ys)
-> decltype(xs.std() == ys) {
return xs.std() == ys;
}
template <class T>
auto operator==(const std::vector<T>& xs, const cow_vector<T>& ys)
-> decltype(xs == ys.std()) {
return xs.std() == ys;
}
template <class T>
auto operator!=(const cow_vector<T>& xs, const cow_vector<T>& ys)
-> decltype(xs.std() != ys.std()) {
return xs.std() != ys.std();
}
template <class T>
auto operator!=(const cow_vector<T>& xs, const std::vector<T>& ys)
-> decltype(xs.std() != ys) {
return xs.std() != ys;
}
template <class T>
auto operator!=(const std::vector<T>& xs, const cow_vector<T>& ys)
-> decltype(xs != ys.std()) {
return xs.std() != ys;
}
} // namespace caf
......@@ -38,17 +38,29 @@ class observer;
template <class T>
class observable;
template <class T>
class connectable;
template <class Materializer, class... Steps>
class observable_def;
template <class In, class Out>
class processor;
template <class Generator>
class generation_materializer;
/// A blueprint for an @ref observer that generates items and applies any number
/// of processing steps immediately before emitting them.
template <class Generator, class... Steps>
class generation;
using generation = observable_def<generation_materializer<Generator>, Steps...>;
template <class Input>
class transformation_materializer;
/// A blueprint for an @ref observer that applies a series of transformation
/// steps to its inputs and emits the results.
template <class Step, class... Steps>
class transformation;
using transformation
= observable_def<transformation_materializer<typename Step::input_type>, Step,
Steps...>;
template <class T>
class connectable;
template <class T>
struct is_observable {
......@@ -60,18 +72,8 @@ struct is_observable<observable<T>> {
static constexpr bool value = true;
};
template <class Step, class... Steps>
struct is_observable<transformation<Step, Steps...>> {
static constexpr bool value = true;
};
template <class Generator, class... Steps>
struct is_observable<generation<Generator, Steps...>> {
static constexpr bool value = true;
};
template <class In, class Out>
struct is_observable<processor<In, Out>> {
template <class Materializer, class... Steps>
struct is_observable<observable_def<Materializer, Steps...>> {
static constexpr bool value = true;
};
......@@ -83,29 +85,6 @@ struct is_observable<single<T>> {
template <class T>
constexpr bool is_observable_v = is_observable<T>::value;
template <class T>
struct is_observer {
static constexpr bool value = false;
};
template <class T>
struct is_observer<observer<T>> {
static constexpr bool value = true;
};
template <class Step, class... Steps>
struct is_observer<transformation<Step, Steps...>> {
static constexpr bool value = true;
};
template <class In, class Out>
struct is_observer<processor<In, Out>> {
static constexpr bool value = true;
};
template <class T>
constexpr bool is_observer_v = is_observer<T>::value;
class observable_builder;
template <class T>
......@@ -116,13 +95,19 @@ struct input_type_oracle {
template <class T>
using input_type_t = typename input_type_oracle<T>::type;
template <class...>
struct output_type_oracle;
template <class T>
struct output_type_oracle {
struct output_type_oracle<T> {
using type = typename T::output_type;
};
template <class T>
using output_type_t = typename output_type_oracle<T>::type;
template <class T0, class T1, class... Ts>
struct output_type_oracle<T0, T1, Ts...> : output_type_oracle<T1, Ts...> {};
template <class... Ts>
using output_type_t = typename output_type_oracle<Ts...>::type;
template <class>
struct has_impl_include {
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::flow::gen {
/// A generator that emits nothing and calls `on_complete` immediately.
template <class T>
class empty {
public:
using output_type = T;
template <class Step, class... Steps>
void pull(size_t, Step& step, Steps&... steps) {
step.on_complete(steps...);
}
};
} // namespace caf::flow::gen
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include <type_traits>
namespace caf::flow::gen {
/// A generator that emits values from a function object.
template <class F>
class from_callable {
public:
using callable_res_t = std::invoke_result_t<F>;
static constexpr bool boxed_output = detail::is_optional_v<callable_res_t>;
using output_type = detail::unboxed_t<callable_res_t>;
explicit from_callable(F fn) : fn_(std::move(fn)) {
// nop
}
from_callable(from_callable&&) = default;
from_callable(const from_callable&) = default;
from_callable& operator=(from_callable&&) = default;
from_callable& operator=(const from_callable&) = default;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
for (size_t i = 0; i < n; ++i) {
if constexpr (boxed_output) {
auto val = fn_();
if (!val) {
step.on_complete(steps...);
return;
} else if (!step.on_next(*val, steps...))
return;
} else {
if (!step.on_next(fn_(), steps...))
return;
}
}
}
private:
F fn_;
};
} // namespace caf::flow::gen
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <memory>
namespace caf::flow::gen {
/// A generator that emits values from a vector.
template <class Container>
class from_container {
public:
using output_type = typename Container::value_type;
explicit from_container(Container&& values) {
values_ = std::make_shared<Container>(std::move(values));
pos_ = values_->begin();
}
from_container() = default;
from_container(from_container&&) = default;
from_container(const from_container&) = default;
from_container& operator=(from_container&&) = default;
from_container& operator=(const from_container&) = default;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
auto end = values_->end();
while (pos_ != end && n > 0) {
if (!step.on_next(*pos_++, steps...))
return;
--n;
}
if (pos_ == end)
step.on_complete(steps...);
}
private:
std::shared_ptr<Container> values_;
typename Container::const_iterator pos_;
};
} // namespace caf::flow::gen
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::flow::gen {
/// A generator that emits ascending values.
template <class T>
class iota {
public:
using output_type = T;
explicit iota(T init) : value_(std::move(init)) {
// nop
}
iota(iota&&) = default;
iota(const iota&) = default;
iota& operator=(iota&&) = default;
iota& operator=(const iota&) = default;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
for (size_t i = 0; i < n; ++i)
if (!step.on_next(value_++, steps...))
return;
}
private:
T value_;
};
} // namespace caf::flow::gen
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::flow::gen {
/// A generator that emits a single value once.
template <class T>
class just {
public:
using output_type = T;
explicit just(T value) : value_(std::move(value)) {
// nop
}
just(just&&) = default;
just(const just&) = default;
just& operator=(just&&) = default;
just& operator=(const just&) = default;
template <class Step, class... Steps>
void pull([[maybe_unused]] size_t n, Step& step, Steps&... steps) {
if (step.on_next(value_, steps...))
step.on_complete(steps...);
}
private:
T value_;
};
} // namespace caf::flow::gen
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::flow::gen {
/// A generator that emits the same value repeatedly.
template <class T>
class repeat {
public:
using output_type = T;
explicit repeat(T value) : value_(std::move(value)) {
// nop
}
repeat(repeat&&) = default;
repeat(const repeat&) = default;
repeat& operator=(repeat&&) = default;
repeat& operator=(const repeat&) = default;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
for (size_t i = 0; i < n; ++i)
if (!step.on_next(value_, steps...))
return;
}
private:
T value_;
};
} // namespace caf::flow::gen
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -20,7 +20,7 @@ class from_generator_sub : public subscription::impl_base {
public:
// -- member types -----------------------------------------------------------
using output_type = steps_output_type_t<Generator, Steps...>;
using output_type = output_type_t<Generator, Steps...>;
// -- constructors, destructors, and assignment operators --------------------
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/error.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/hot.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/sec.hpp"
#include <utility>
namespace caf::flow::op {
/// Allows operators to subscribe to an observable immediately to force an eager
/// subscription while the observable that actually consumes the items
/// subscribes later. May only be subscribed once.
template <class T>
class prefetch : public hot<T>, public observer_impl<T> {
public:
// -- member types -----------------------------------------------------------
using super = hot<T>;
using output_type = T;
// -- constructors, destructors, and assignment operators --------------------
explicit prefetch(coordinator* ctx) : super(ctx) {
// nop
}
// -- ref counting (and disambiguation due to multiple base types) -----------
void ref_coordinated() const noexcept override {
this->ref();
}
void deref_coordinated() const noexcept override {
this->deref();
}
friend void intrusive_ptr_add_ref(const prefetch* ptr) noexcept {
ptr->ref_coordinated();
}
friend void intrusive_ptr_release(const prefetch* ptr) noexcept {
ptr->deref_coordinated();
}
// -- implementation of observable_impl<T> -----------------------------------
disposable subscribe(observer<T> out) override {
if (completed_) {
if (err_)
out.on_error(err_);
else
out.on_complete();
return {};
} else if (!out_ && sub_) {
out_ = std::move(out);
out_.on_subscribe(sub_);
return sub_.as_disposable();
} else {
auto err = make_error(sec::invalid_observable,
"prefetch cannot add more than one subscriber");
out.on_error(err);
return {};
}
}
// -- implementation of observer_impl<T> -------------------------------------
void on_next(const T& item) override {
out_.on_next(item);
}
void on_complete() override {
completed_ = true;
if (out_) {
out_.on_complete();
out_ = nullptr;
sub_ = nullptr;
}
}
void on_error(const error& what) override {
completed_ = true;
err_ = what;
if (out_) {
out_.on_error(what);
out_ = nullptr;
sub_ = nullptr;
}
}
void on_subscribe(subscription sub) override {
if (!sub_) {
sub_ = sub;
} else {
sub.dispose();
}
}
// -- convenience functions --------------------------------------------------
static intrusive_ptr<base<T>> apply(intrusive_ptr<base<T>> src) {
auto ptr = make_counted<prefetch>(src->ctx());
src->subscribe(observer<T>{ptr});
return ptr;
}
private:
bool completed_ = false;
error err_;
observer<T> out_;
subscription sub_;
};
} // namespace caf::flow::op
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/cow_tuple.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_decl.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cell.hpp"
#include "caf/flow/subscription.hpp"
#include <cstdint>
namespace caf::flow::op {
template <class T>
class pipe_sub : public detail::plain_ref_counted,
public observer_impl<T>,
public subscription_impl {
public:
// -- constructors, destructors, and assignment operators --------------------
explicit pipe_sub(subscription in) : in_(std::move(in)) {
// nop
}
void init(observer<T> out) {
if (!completed_)
out_ = std::move(out);
else if (err_)
out.on_error(err_);
else
out.on_complete();
}
// -- ref counting -----------------------------------------------------------
void ref_coordinated() const noexcept final {
this->ref();
}
void deref_coordinated() const noexcept final {
this->deref();
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
friend void intrusive_ptr_add_ref(const pipe_sub* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const pipe_sub* ptr) noexcept {
ptr->deref();
}
// -- implementation of observer_impl<Input> ---------------------------------
void on_next(const T& item) override {
if (out_)
out_.on_next(item);
}
void on_complete() override {
if (out_) {
in_ = nullptr;
out_.on_complete();
out_ = nullptr;
completed_ = true;
} else if (!completed_) {
completed_ = true;
}
}
void on_error(const error& what) override {
if (out_) {
in_ = nullptr;
out_.on_error(what);
out_ = nullptr;
completed_ = true;
err_ = what;
} else if (!completed_) {
completed_ = true;
err_ = what;
}
}
void on_subscribe(subscription in) override {
in.dispose();
}
// -- implementation of subscription_impl ------------------------------------
bool disposed() const noexcept override {
return !out_;
}
void dispose() override {
if (in_) {
in_.dispose();
in_ = nullptr;
}
if (out_) {
out_.on_complete();
out_ = nullptr;
}
}
void request(size_t n) override {
if (in_) {
in_.request(n);
}
}
private:
subscription in_;
observer<T> out_;
/// Stores whether on_complete has been called. This may be the case even
/// before init gets called.
bool completed_ = false;
/// Stores the error passed to on_error.
error err_;
};
template <class T>
class pipe : public hot<T> {
public:
using super = hot<T>;
using sub_ptr = intrusive_ptr<pipe_sub<T>>;
explicit pipe(coordinator* ctx, sub_ptr sub)
: super(ctx), sub_(std::move(sub)) {
// nop
}
~pipe() {
if (sub_)
sub_->dispose();
}
disposable subscribe(observer<T> out) override {
if (sub_) {
sub_->init(out);
out.on_subscribe(subscription{sub_});
return disposable{std::move(sub_)};
} else {
auto err = make_error(sec::invalid_observable,
"pipes may only be subscribed once");
out.on_error(err);
return disposable{};
}
}
private:
sub_ptr sub_;
};
template <class T>
class prefix_and_tail_fwd : public detail::plain_ref_counted,
public observer_impl<T> {
public:
// -- constructors, destructors, and assignment operators --------------------
using output_type = cow_tuple<std::vector<T>, observable<T>>;
using cell_ptr = intrusive_ptr<cell<output_type>>;
prefix_and_tail_fwd(coordinator* ctx, size_t prefix_size, cell_ptr sync)
: ctx_(ctx), prefix_size_(prefix_size), sync_(std::move(sync)) {
// nop
}
// -- ref counting -----------------------------------------------------------
void ref_coordinated() const noexcept final {
this->ref();
}
void deref_coordinated() const noexcept final {
this->deref();
}
friend void intrusive_ptr_add_ref(const prefix_and_tail_fwd* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const prefix_and_tail_fwd* ptr) noexcept {
ptr->deref();
}
// -- implementation of observer_impl<T> -------------------------------------
void on_next(const T& item) override {
if (next_) {
next_.on_next(item);
} else if (sync_) {
prefix_.push_back(item);
if (prefix_.size() == prefix_size_) {
auto fwd = make_counted<pipe_sub<T>>(std::move(in_));
auto tmp = make_counted<pipe<T>>(ctx_, fwd);
output_type result{std::move(prefix_), observable<T>{tmp}};
sync_->set_value(std::move(result));
sync_ = nullptr;
next_ = observer<T>{fwd};
}
}
}
void on_complete() override {
if (next_) {
next_.on_complete();
next_ = nullptr;
} else if (sync_) {
sync_->set_null();
sync_ = nullptr;
}
}
void on_error(const error& what) override {
if (next_) {
next_.on_error(what);
next_ = nullptr;
} else if (sync_) {
sync_->set_error(what);
sync_ = nullptr;
}
}
void on_subscribe(subscription in) override {
in_ = std::move(in);
in_.request(prefix_size_);
}
private:
coordinator* ctx_;
size_t prefix_size_ = 0;
subscription in_;
std::vector<T> prefix_;
observer<T> next_;
cell_ptr sync_;
};
template <class T>
class prefix_and_tail : public cold<cow_tuple<std::vector<T>, observable<T>>> {
public:
// -- member types -----------------------------------------------------------
using output_type = cow_tuple<std::vector<T>, observable<T>>;
using super = cold<output_type>;
using observer_type = observer<T>;
// -- constructors, destructors, and assignment operators --------------------
prefix_and_tail(coordinator* ctx, intrusive_ptr<base<T>> decorated,
size_t prefix_size)
: super(ctx), decorated_(std::move(decorated)), prefix_size_(prefix_size) {
// nop
}
disposable subscribe(observer<output_type> out) override {
// The cell acts as a sort of handshake between the dispatcher and the
// observer. After producing the (prefix, tail) pair, the dispatcher goes on
// to forward items from the decorated observable to the tail.
auto sync = make_counted<cell<output_type>>(super::ctx_);
auto fwd = make_counted<prefix_and_tail_fwd<T>>(super::ctx_, prefix_size_,
sync);
std::vector<disposable> result;
result.reserve(2);
result.emplace_back(sync->subscribe(std::move(out)));
result.emplace_back(decorated_->subscribe(observer<T>{std::move(fwd)}));
return disposable::make_composite(std::move(result));
}
private:
intrusive_ptr<base<T>> decorated_;
size_t prefix_size_;
};
} // namespace caf::flow::op
......@@ -49,10 +49,6 @@ public:
// -- implementation of conn<T> ----------------------------------------------
coordinator* ctx() const noexcept override {
return super::ctx_;
}
disposable subscribe(observer<T> out) override {
auto result = super::subscribe(std::move(out));
if (!connected_ && super::observer_count() == auto_connect_threshold_) {
......
......@@ -4,18 +4,90 @@
#pragma once
#include <algorithm>
#include <utility>
#include <variant>
#include "caf/detail/overload.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/flow/observable.hpp"
#include "caf/ref_counted.hpp"
#include <algorithm>
#include <optional>
#include <type_traits>
#include <utility>
namespace caf::flow {
template <class OnSuccess>
struct on_success_orcacle {
using trait = detail::get_callable_trait_t<OnSuccess>;
static_assert(trait::num_args == 1,
"OnSuccess functions must take exactly one argument");
using arg_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
};
template <class OnSuccess>
using on_success_arg_t = typename on_success_orcacle<OnSuccess>::arg_type;
template <class OnSuccess, class OnError>
class single_observer_impl
: public observer_impl_base<on_success_arg_t<OnSuccess>> {
public:
using input_type = on_success_arg_t<OnSuccess>;
single_observer_impl(OnSuccess on_success, OnError on_error)
: on_success_(std::move(on_success)), on_error_(std::move(on_error)) {
// nop
}
void on_subscribe(subscription sub) {
// Request one additional item to detect whether the observable emits more
// than one item.
sub.request(2);
sub_ = std::move(sub);
}
void on_next(const input_type& item) {
if (!result_) {
result_.emplace(item);
} else {
sub_.dispose();
sub_ = nullptr;
auto err = make_error(sec::runtime_error,
"single emitted more than one item");
on_error_(err);
}
}
void on_complete() {
if (sub_) {
sub_ = nullptr;
if (result_) {
on_success_(*result_);
result_ = std::nullopt;
} else {
auto err = make_error(sec::broken_promise,
"single failed to produce an item");
on_error_(err);
}
}
}
void on_error(const error& what) {
if (sub_) {
sub_ = nullptr;
on_error_(what);
}
}
private:
OnSuccess on_success_;
OnError on_error_;
std::optional<input_type> result_;
subscription sub_;
};
/// Similar to an `observable`, but always emits either a single value or an
/// error.
template <class T>
......@@ -46,19 +118,13 @@ public:
return observable<T>{pimpl_};
}
void subscribe(observer<T> what) {
if (pimpl_)
pimpl_->subscribe(std::move(what));
else
what.on_error(make_error(sec::invalid_observable));
}
template <class OnSuccess, class OnError>
void subscribe(OnSuccess on_success, OnError on_error) {
disposable subscribe(OnSuccess on_success, OnError on_error) {
static_assert(std::is_invocable_v<OnSuccess, const T&>);
as_observable().for_each([f{std::move(on_success)}](
const T& item) mutable { f(item); },
std::move(on_error));
static_assert(std::is_invocable_v<OnError, const error&>);
using impl_t = single_observer_impl<OnSuccess, OnError>;
auto ptr = make_counted<impl_t>(std::move(on_success), std::move(on_error));
return pimpl_->subscribe(observer<T>{ptr});
}
bool valid() const noexcept {
......@@ -81,8 +147,7 @@ private:
intrusive_ptr<op::base<T>> pimpl_;
};
/// Convenience function for creating an @ref observable from a concrete
/// operator type.
/// Convenience function for creating a @ref single from a flow operator.
template <class Operator, class... Ts>
single<typename Operator::output_type>
make_single(coordinator* ctx, Ts&&... xs) {
......
This diff is collapsed.
#include "caf/flow/step/distinct.hpp"
#include "caf/flow/step/do_finally.hpp"
#include "caf/flow/step/do_on_complete.hpp"
#include "caf/flow/step/do_on_error.hpp"
#include "caf/flow/step/do_on_next.hpp"
#include "caf/flow/step/filter.hpp"
#include "caf/flow/step/map.hpp"
#include "caf/flow/step/on_error_complete.hpp"
#include "caf/flow/step/reduce.hpp"
#include "caf/flow/step/skip.hpp"
#include "caf/flow/step/take.hpp"
#include "caf/flow/step/take_while.hpp"
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include <unordered_set>
namespace caf::flow::step {
template <class T>
class distinct {
public:
using input_type = T;
using output_type = T;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (prev_.insert(item).second)
return next.on_next(item, steps...);
else
return true;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
std::unordered_set<T> prev_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class T, class F>
class do_finally {
public:
using input_type = T;
using output_type = T;
explicit do_finally(F fn) : fn_(std::move(fn)) {
// nop
}
do_finally(do_finally&&) = default;
do_finally(const do_finally&) = default;
do_finally& operator=(do_finally&&) = default;
do_finally& operator=(const do_finally&) = default;
template <class Next, class... Steps>
bool on_next(const T& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void finally(Next& next, Steps&... steps) {
fn_();
next.finally(steps...);
}
template <class Next, class... Steps>
void finally(const error& what, Next& next, Steps&... steps) {
fn_();
next.finally(what, steps...);
}
private:
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class T, class F>
class do_on_complete {
public:
using input_type = T;
using output_type = T;
explicit do_on_complete(F fn) : fn_(std::move(fn)) {
// nop
}
do_on_complete(do_on_complete&&) = default;
do_on_complete(const do_on_complete&) = default;
do_on_complete& operator=(do_on_complete&&) = default;
do_on_complete& operator=(const do_on_complete&) = default;
template <class Next, class... Steps>
bool on_next(const T& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
fn_();
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class T, class F>
class do_on_error {
public:
using input_type = T;
using output_type = T;
explicit do_on_error(F fn) : fn_(std::move(fn)) {
// nop
}
do_on_error(do_on_error&&) = default;
do_on_error(const do_on_error&) = default;
do_on_error& operator=(do_on_error&&) = default;
do_on_error& operator=(const do_on_error&) = default;
template <class Next, class... Steps>
bool on_next(const T& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
fn_(what);
next.on_error(what, steps...);
}
private:
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class F>
class do_on_next {
public:
using trait = detail::get_callable_trait_t<F>;
static_assert(!std::is_same_v<typename trait::result_type, void>,
"do_on_next functions may not return void");
static_assert(trait::num_args == 1,
"do_on_next functions must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = std::decay_t<typename trait::result_type>;
explicit do_on_next(F fn) : fn_(std::move(fn)) {
// nop
}
do_on_next(do_on_next&&) = default;
do_on_next(const do_on_next&) = default;
do_on_next& operator=(do_on_next&&) = default;
do_on_next& operator=(const do_on_next&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
fn_(item);
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class Predicate>
class filter {
public:
using trait = detail::get_callable_trait_t<Predicate>;
static_assert(std::is_convertible_v<typename trait::result_type, bool>,
"predicates must return a boolean value");
static_assert(trait::num_args == 1,
"predicates must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = input_type;
explicit filter(Predicate fn) : fn_(std::move(fn)) {
// nop
}
filter(filter&&) = default;
filter(const filter&) = default;
filter& operator=(filter&&) = default;
filter& operator=(const filter&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (fn_(item))
return next.on_next(item, steps...);
else
return true;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
Predicate fn_;
};
} // namespace caf::flow::step
namespace caf::flow::step {
template <class>
class distinct;
template <class, class>
class do_finally;
template <class, class>
class do_on_complete;
template <class, class>
class do_on_error;
template <class>
class do_on_next;
template <class>
class filter;
template <class>
class map;
template <class>
class on_error_complete;
template <class>
class reduce;
template <class>
class skip;
template <class>
class take;
template <class>
class take_while;
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class F>
class map {
public:
using trait = detail::get_callable_trait_t<F>;
static_assert(!std::is_same_v<typename trait::result_type, void>,
"map functions may not return void");
static_assert(trait::num_args == 1,
"map functions must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = std::decay_t<typename trait::result_type>;
explicit map(F fn) : fn_(std::move(fn)) {
// nop
}
map(map&&) = default;
map(const map&) = default;
map& operator=(map&&) = default;
map& operator=(const map&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
return next.on_next(fn_(item), steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class T>
class on_error_complete {
public:
using input_type = T;
using output_type = T;
template <class Next, class... Steps>
bool on_next(const T& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_error(Next& next, Steps&... steps) {
next.on_error(steps...);
}
template <class Next, class... Steps>
void on_error(const error&, Next& next, Steps&... steps) {
next.on_complete(steps...);
}
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class F>
class reduce {
public:
using trait = detail::get_callable_trait_t<F>;
static_assert(!std::is_same_v<typename trait::result_type, void>,
"reduce functions may not return void");
static_assert(trait::num_args == 2,
"reduce functions must take exactly two arguments");
using arg_type = typename trait::arg_types;
using input_type = std::decay_t<detail::tl_at_t<arg_type, 1>>;
using output_type = std::decay_t<typename trait::result_type>;
explicit reduce(output_type init, F fn)
: val_(std::move(init)), fn_(std::move(fn)) {
// nop
}
reduce(reduce&&) = default;
reduce(const reduce&) = default;
reduce& operator=(reduce&&) = default;
reduce& operator=(const reduce&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next&, Steps&...) {
val_ = fn_(std::move(val_), item);
return true;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
if (next.on_next(val_, steps...))
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
if (next.on_next(val_, steps...))
next.on_error(what, steps...);
}
private:
output_type val_;
F fn_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include <cstddef>
namespace caf::flow::step {
template <class T>
class skip {
public:
using input_type = T;
using output_type = T;
explicit skip(size_t num) : remaining_(num) {
// nop
}
skip(skip&&) = default;
skip(const skip&) = default;
skip& operator=(skip&&) = default;
skip& operator=(const skip&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (remaining_ == 0) {
return next.on_next(item, steps...);
} else {
--remaining_;
return true;
}
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
size_t remaining_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include <cstddef>
namespace caf::flow::step {
template <class T>
class take {
public:
using input_type = T;
using output_type = T;
explicit take(size_t num) : remaining_(num) {
// nop
}
take(take&&) = default;
take(const take&) = default;
take& operator=(take&&) = default;
take& operator=(const take&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (remaining_ > 0) {
if (next.on_next(item, steps...)) {
if (--remaining_ > 0) {
return true;
} else {
next.on_complete(steps...);
return false;
}
}
}
return false;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
size_t remaining_;
};
} // namespace caf::flow::step
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include <utility>
namespace caf::flow::step {
template <class Predicate>
class take_while {
public:
using trait = detail::get_callable_trait_t<Predicate>;
static_assert(std::is_convertible_v<typename trait::result_type, bool>,
"predicates must return a boolean value");
static_assert(trait::num_args == 1,
"predicates must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = input_type;
explicit take_while(Predicate fn) : fn_(std::move(fn)) {
// nop
}
take_while(take_while&&) = default;
take_while(const take_while&) = default;
take_while& operator=(take_while&&) = default;
take_while& operator=(const take_while&) = default;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (fn_(item)) {
return next.on_next(item, steps...);
} else {
next.on_complete(steps...);
return false;
}
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
private:
Predicate fn_;
};
} // namespace caf::flow::step
......@@ -20,6 +20,7 @@ template <class> class [[nodiscard]] error_code;
template <class> class basic_cow_string;
template <class> class behavior_type_of;
template <class> class callback;
template <class> class cow_vector;
template <class> class dictionary;
template <class> class expected;
template <class> class intrusive_cow_ptr;
......
......@@ -26,19 +26,17 @@ SCENARIO("an empty observable terminates normally") {
GIVEN("an empty<int32>") {
WHEN("an observer subscribes") {
THEN("the observer receives on_complete") {
auto uut = ctx->make_observable().empty<int32_t>();
auto snk = flow::make_passive_observer<int32_t>();
uut.subscribe(snk->as_observer());
ctx->make_observable().empty<int32_t>().subscribe(snk->as_observer());
ctx->run();
if (CHECK(snk->sub)) {
snk->sub.request(42);
CHECK(snk->subscribed());
snk->request(42);
ctx->run();
CHECK_EQ(snk->state, flow::observer_state::completed);
CHECK(snk->completed());
CHECK(snk->buf.empty());
}
}
}
}
}
END_FIXTURE_SCOPE()
......@@ -77,11 +77,9 @@ SCENARIO("for_each iterates all values in a stream") {
.as_observable()
.take(7)
.map([](int x) { return x * 3; })
.for_each([&outputs](int x) { outputs.emplace_back(x); },
[](const error& reason) {
FAIL("on_error called: " << reason);
},
[&completed] { completed = true; });
.do_on_error([](const error& err) { FAIL("on_error: " << err); })
.do_on_complete([&completed] { completed = true; })
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK(completed);
CHECK_EQ(inputs, outputs);
......
......@@ -149,7 +149,7 @@ SCENARIO("callable sources stream values generated from a function object") {
namespace {
class custom_pullable {
class custom_generator {
public:
using output_type = int;
......@@ -171,25 +171,24 @@ private:
} // namespace
SCENARIO("lifting converts a Pullable into an observable") {
GIVEN("a lifted implementation of the Pullable concept") {
SCENARIO("lifting converts a generator into an observable") {
GIVEN("a lifted implementation of the generator concept") {
WHEN("subscribing to its output") {
THEN("the observer receives the generated values") {
using ivec = std::vector<int>;
auto snk = flow::make_passive_observer<int>();
auto f = custom_pullable{};
ctx->make_observable().lift(f).subscribe(snk->as_observer());
auto f = custom_generator{};
ctx->make_observable().from_generator(f).subscribe(snk->as_observer());
CHECK_EQ(snk->state, flow::observer_state::subscribed);
CHECK(snk->buf.empty());
if (CHECK(snk->sub)) {
snk->sub.request(3);
CHECK(snk->subscribed());
snk->request(3);
ctx->run();
CHECK_EQ(snk->buf, ivec({1, 2, 3}));
snk->sub.request(21);
ctx->run();
CHECK_EQ(snk->buf, ivec({1, 2, 3, 4, 5, 6, 7}));
CHECK_EQ(snk->state, flow::observer_state::completed);
}
CHECK(snk->completed());
}
}
}
......
......@@ -34,7 +34,7 @@ auto ls(T x, Ts... xs) {
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("prefix_and_tail splits off initial elements") {
using tuple_t = cow_tuple<std::vector<int>, flow::observable<int>>;
using tuple_t = cow_tuple<cow_vector<int>, flow::observable<int>>;
GIVEN("a generation with 0 values") {
WHEN("calling prefix_and_tail(2)") {
THEN("the observer of prefix_and_tail only receives on_complete") {
......
......@@ -72,13 +72,12 @@ SCENARIO("response handles are convertible to observables and singles") {
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{42})
.as_observable<int32_t>()
.for_each(
[&](int32_t val) {
.do_on_error([&](const error& what) { result = what; })
.do_on_complete([&] { completed = true; })
.for_each([&](int32_t val) {
result = val;
++on_next_calls;
},
[&](const error& what) { result = what; },
[&] { completed = true; });
});
auto aut = actor{self};
launch();
expect((int32_t), from(aut).to(dummy).with(42));
......@@ -117,13 +116,12 @@ SCENARIO("response handles are convertible to observables and singles") {
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{13})
.as_observable<int32_t>()
.for_each(
[&](int32_t val) {
.do_on_error([&](const error& what) { result = what; })
.do_on_complete([&] { completed = true; })
.for_each([&](int32_t val) {
result = val;
++on_next_calls;
},
[&](const error& what) { result = what; },
[&] { completed = true; });
});
auto aut = actor{self};
launch();
expect((int32_t), from(aut).to(dummy).with(13));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment